【问题标题】:Spring Stomp over Websocket: Stream large filesSpring Stomp over Websocket:流式传输大文件
【发布时间】:2016-10-20 04:13:38
【问题描述】:

我的 SockJs 客户端在网页中,发送帧大小为 16K 的消息。消息大小限制决定了我可以传输的文件的最大大小。

以下是我在文档中找到的内容。

/**
 * 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;
}

我的问题: 我可以设置部分消息传递,以便文件部分传输,而不是像现在那样作为单个消息传输吗?

更新: 仍在寻找带有部分消息传递的解决方案 同时,现在使用 HTTP 处理大型消息(即我的应用程序中的文件上传/下载)。

【问题讨论】:

  • 我已经登陆这里,因为我自己正在尝试这个。我有您正在寻找的解决方案,但在服务器端我不想接收部分,而是要接收流(仍然是部分接收,但使用内部缓冲区)想知道您是否找到了更好的解决方案或至少需要那个解决方案。
  • 我现在切换到 HTTP 只是为了传输文件,所以如果你有一个通过 websockets 的 stream 解决方案,那就太好了..
  • 我已经发布了答案,虽然我的直接动机是减少在我的单页应用程序上上传大文件的内存峰值,这在测量上满足我的要求,但我链接的实验项目充其量是原始的,它是只是一个 PoC。

标签: java spring stomp spring-websocket sockjs


【解决方案1】:

我可以设置部分消息传递,以便文件被部分传输,而不是像现在那样作为单个消息传输吗?

是的。这是我的 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);
    }
}

UploadWShandler 如下。抱歉这里的代码太多 - 关键点

  • supportsPartialMessage 返回 true。
  • handleBinaryMessage 将被多次调用并带有部分消息,因此我们需要组装字节。因此afterConnectionEstablished 使用 websocket URL 查询建立身份。但是您不必使用此机制。我选择这种机制的原因是为了保持客户端简单,以便我只调用一次webSocket.send(files[0]),即我没有在 javascript 端切片文件 blob 对象。 (旁白:我想在客户端使用普通的 websocket - 没有 stomp/socks)
  • 内部客户端分块机制提供message.isLast()最后一条消息
  • 出于演示目的,我将其写入文件系统并使用 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());
        }
    }
}

顺便说一句,一个工作项目也是sprint-boot-with-websocked-chunking-assembly-and-fetchwith-websocked-chunking-assembly-and-fetch 分支

【讨论】:

  • 实际上 AbstractWebSocketMessageBrokerConfigurer 已被弃用,Spring 建议使用 WebSocketMessageBrokerConfigurer,但你知道如何添加 WebSocketHandlerRegistry 吗?
  • 我通过在类描述中添加“implements WebSocketConfigurer”并在类上添加@EnableWebSocket注解来做到这一点。
  • @bhantol 顺便说一句,使用线程安全的 Map 实现不是更好吗?
  • @BullshitPingu 线程安全将是一个过度杀伤,因为handleBinaryMessagehandleMessage 的一部分,在消息开始到达时被调用,并且在连接准备好之前不会被调用afterConnectionEstablished。如果afterConnectionEstablished 是异步的,我会感到惊讶,因为这会破坏它的目的。通常在联网中,此类事件的目的是让您为接收消息做好准备。您可以进行防御并使其线程安全。 (PS。我没有看实现源代码。)
猜你喜欢
  • 2016-06-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-13
  • 1970-01-01
  • 2016-12-22
  • 2016-05-06
  • 2020-06-17
相关资源
最近更新 更多