简体   繁体   English

Spring Stomp over Websocket:流式传输大文件

[英]Spring Stomp over Websocket: Stream large files

My SockJs client in webpage, sends message with a frame size of 16K.我在网页中的 SockJs 客户端发送帧大小为 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).更新:仍在寻找具有部分消息传递的解决方案 同时现在使用 HTTP 处理大消息(这是我的应用程序中的文件上传/下载)。

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.这是我的 Spring boot 实验项目中的相关配置 - 基本上UploadWSHandler已注册并设置了WebSocketTransportRegistration.setMessageSizeLimit

@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. UploadWShandler 如下所示。 Sorry too much code here - Key points抱歉这里代码太多 - 关键点

  • supportsPartialMessage which returns true. supportsPartialMessage返回 true 的PartialMessage。
  • handleBinaryMessage will be called multiple times with partial message so we need to assemble the bytes. handleBinaryMessage将使用部分消息多次调用,因此我们需要组装字节。 So afterConnectionEstablished establishes an identity using the websocket URL query.所以afterConnectionEstablished使用 websocket URL 查询建立一个身份。 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 ie I am not slicing the file blob object on the javascript side.我选择这种机制的原因是保持客户端简单,以便我只调用webSocket.send(files[0])一次,即我没有在 javascript 端切片文件 blob 对象。 (side point: I want to use plain websocket on client side - no stomp/socks) (旁注:我想在客户端使用普通的 websocket - 没有跺脚/袜子)
  • The iternal client side chunking mechanism provides message.isLast() last message内部客户端分块机制提供message.isLast()最后一条消息
  • 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.出于演示目的,我将其写入文件系统并使用FileUploadInFlight在内存中累积这些字节,但您不必这样做,并且可以随时在其他地方流式传输。
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顺便说一句,一个工作项目也是sprint-boot-with-websocked-chunking-assembly-and-fetch in with-websocked-chunking-assembly-and-fetch分支

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM