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

时间:2016-06-18 11:44:40

标签: java spring stomp spring-websocket sockjs

我的网页中的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现在用于大型消息(在我的应用程序中是文件上传/下载)。

1 个答案:

答案 0 :(得分:3)

  

我是否可以设置部分消息传递,以便逐个传输文件,而不是像现在一样将其作为单个消息传输?

是。以下是我的Spring启动实验项目中的相关配置 - 基本上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)
  • iternal客户端分块机制提供message.isLast()最后一条消息
  • 出于演示目的,我将其写入文件系统并使用FileUploadInFlight在内存中累积这些字节,但您不必这样做,并且可以在其他地方流式传输。

公共类UploadWSHandler扩展了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 with-websocked-chunking-assembly-and-fetch分支

中的工作项目也是sprint-boot-with-websocked-chunking-assembly-and-fetch