Spring Stomp over Websocket: Stream large files

我与影子孤独终老i 提交于 2019-12-23 21:21:18

问题


My SockJs client in webpage, sends message with a frame size of 16K. The message size limit is what determines the max size of the file that I can transfer.

Below is what I found in the doc.

/**
 * Configure the maximum size for an incoming sub-protocol message.
 * For example a STOMP message may be received as multiple WebSocket messages
 * or multiple HTTP POST requests when SockJS fallback options are in use.
 *
 * <p>In theory a WebSocket message can be almost unlimited in size.
 * In practice WebSocket servers impose limits on incoming message size.
 * STOMP clients for example tend to split large messages around 16K
 * boundaries. Therefore a server must be able to buffer partial content
 * and decode when enough data is received. Use this property to configure
 * the max size of the buffer to use.
 *
 * <p>The default value is 64K (i.e. 64 * 1024).
 *
 * <p><strong>NOTE</strong> that the current version 1.2 of the STOMP spec
 * does not specifically discuss how to send STOMP messages over WebSocket.
 * Version 2 of the spec will but in the mean time existing client libraries
 * have already established a practice that servers must handle.
 */
public WebSocketTransportRegistration setMessageSizeLimit(int messageSizeLimit) {
    this.messageSizeLimit = messageSizeLimit;
    return this;
}

MY QUESTION: Can I setup a partial messaging so that a file is transferred part by part and is not getting transferred as a single message as it is been done now?

Update: Still looking for a solution with partial messaging Meanwhile using HTTP now for large messages (which is file uploads/downloads in my application).


回答1:


Can I setup a partial messaging so that a file is transferred part by part and is not getting transferred as a single message as it is been done now?

Yes. Here is the relevant Config from my Spring boot experimental project - basically UploadWSHandler is registered and WebSocketTransportRegistration.setMessageSizeLimit is set.

@Configuration
@EnableWebSocket
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer implements WebSocketConfigurer {
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new UploadWSHandler(), "/binary");
    }

    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
        registration.setMessageSizeLimit(50 * 1024 * 1024);
    }
}

The UploadWShandler is as follows. Sorry too much code here - Key points

  • supportsPartialMessage which returns true.
  • handleBinaryMessage will be called multiple times with partial message so we need to assemble the bytes. So afterConnectionEstablished establishes an identity using the websocket URL query. But you don't have to use this mechanism. The reason why I choose this mechanism is to keep the client side simple so that I call webSocket.send(files[0]) only once i.e. I am not slicing the file blob object on the javascript side. (side point: I want to use plain websocket on client side - no stomp/socks)
  • The iternal client side chunking mechanism provides message.isLast() last message
  • For demo purposes I am writing this to file system and accumulating those bytes in memory with FileUploadInFlight but you don't have to do this and can stream somewhere else as you go.

public class UploadWSHandler extends BinaryWebSocketHandler {

Map<WebSocketSession, FileUploadInFlight> sessionToFileMap = new WeakHashMap<>();

@Override
public boolean supportsPartialMessages() {
    return true;
}

@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
    ByteBuffer payload = message.getPayload();
    FileUploadInFlight inflightUpload = sessionToFileMap.get(session);
    if (inflightUpload == null) {
        throw new IllegalStateException("This is not expected");
    }
    inflightUpload.append(payload);

    if (message.isLast()) {
        Path basePath = Paths.get(".", "uploads", UUID.randomUUID().toString());
        Files.createDirectories(basePath);
        FileChannel channel = new FileOutputStream(
                Paths.get(basePath.toString() ,inflightUpload.name).toFile(), false).getChannel();
        channel.write(ByteBuffer.wrap(inflightUpload.bos.toByteArray()));
        channel.close();
        session.sendMessage(new TextMessage("UPLOAD "+inflightUpload.name));
        session.close();
        sessionToFileMap.remove(session);
    }
    String response = "Upload Chunk: size "+ payload.array().length;
    System.out.println(response);

}

@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
    sessionToFileMap.put(session, new FileUploadInFlight(session));
}



static class FileUploadInFlight {
    String name;
    String uniqueUploadId;
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    /**
     * Fragile constructor - beware not prod ready
     * @param session
     */
    FileUploadInFlight(WebSocketSession session) {
        String query = session.getUri().getQuery();
        String uploadSessionIdBase64 = query.split("=")[1];
        String uploadSessionId = new String(Base64Utils.decodeUrlSafe(uploadSessionIdBase64.getBytes()));
        System.out.println(uploadSessionId);
        List<String> sessionIdentifiers = Splitter.on("\\").splitToList(uploadSessionId);
        String uniqueUploadId = session.getRemoteAddress().toString()+sessionIdentifiers.get(0);
        String fileName = sessionIdentifiers.get(1);
        this.name = fileName;
        this.uniqueUploadId = uniqueUploadId;
    }
    public void append(ByteBuffer byteBuffer) throws IOException{
        bos.write(byteBuffer.array());
    }
}

BTW a working project is also sprint-boot-with-websocked-chunking-assembly-and-fetch in with-websocked-chunking-assembly-and-fetch branch



来源:https://stackoverflow.com/questions/37896551/spring-stomp-over-websocket-stream-large-files

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!