Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to send Uint8List (version 3.8) #56

Closed
SimonServant opened this issue Jun 29, 2021 · 13 comments
Closed

Unable to send Uint8List (version 3.8) #56

SimonServant opened this issue Jun 29, 2021 · 13 comments

Comments

@SimonServant
Copy link

Unable to send ByteArray in binary body property.

Version: 3.8

Cause: _TypeError (type 'Uint8List' is not a subtype of type 'String')

Where: sock_js_parser.dart

When: sending StompFrame with Uint8List property non null.

  @override
  dynamic serializeFrame(StompFrame frame) {
    dynamic serializedFrame = _stompParser.serializeFrame(frame);

    serializedFrame = _encapsulateFrame(serializedFrame);

    return serializedFrame;
  }

Use Case: Selecting image with FilePicker package and dart.io then sending it as byte array (non encoded for performance reasons)

  void sendImage() async {
    var file = await selectImage();
    Uint8List bytes = await file.readAsBytes();
    if (file != null) {
      stompClient.send(
        destination: '/app/imageQueue',
        body: "demo",
        binaryBody: bytes,
      );
    }
  }

  Future<File> selectImage() async {
    var result = await FilePicker.platform.pickFiles();

    if (result != null) {
      var file = File(result.files.single.path);
      return file;
    } else {
      return null;
    }
  }
@KammererTob
Copy link
Contributor

Hey,

it looks like the SockJS protocol does not support sending binary messages:
sockjs/sockjs-protocol#74

So our parser does not support this. My suggestion if you need binary support: Use plain Stomp without SockJS as a wrapper protocol

@SimonServant
Copy link
Author

SimonServant commented Jun 30, 2021

Thanks a lot.

I am sorry for bothering. It seems my issue was based on my faulty understanding of websockets and sockjs.

I will therefore close this issue.

Might i ask a small question ?

I noticed that large text messages such as base64 encoded images are not recieved from my spring backend. Neither do they cause any response nor exception from the following callbacks:

    if (stompClient == null) {
      stompClient = StompClient(
          config: StompConfig(
        url: socketUrl,
        onConnect: onConnect,
        onWebSocketError: (dynamic error) => print(error.toString()),
        onStompError: (dynamic error) => print(error.toString()),
        onUnhandledMessage: (dynamic error) => print(error.toString()),
      ));

Now i have already tried to increase the message size in my spring backend by overriding the configureWebSocketTransport method.

    override fun configureWebSocketTransport(registration: WebSocketTransportRegistration) {
        registration.setMessageSizeLimit(1024 * 1024 * 200) // default : 64 * 1024
        registration.setSendTimeLimit(20 * 10000) // default : 10 * 10000
        registration.setSendBufferSizeLimit(1024 * 1024 * 200) // default : 512 * 1024
        super.configureWebSocketTransport(registration)
    }

Yet this did not have the expected effect and when sending a large text message there is not even a error / response from the server. Am i missing another configuration step in the client or is there another possible cause.

I have read here: https://stackoverflow.com/questions/38325807/java-sockjs-spring-client-and-message-size that the cause might be a faulty configured client.

Since this is possibly not the right place to ask such a question i fully understand if there is now answer.

Thanks for your help.

@KammererTob
Copy link
Contributor

KammererTob commented Jun 30, 2021

Hey,

do happen to have a sample body i could use to test this out? In the meantime, could you try to enable TRACE logging for the your WebSockets in spring:
Here is an example for logback:
<logger name="org.springframework.web.socket" level="TRACE"/>

This could help to see errors/problems when the server receives your message.

@SimonServant
Copy link
Author

SimonServant commented Jun 30, 2021

Hi,

the message body is simply this string formed by base64 encoding a public available image found in the internet
https://static4.depositphotos.com/1012814/288/i/600/depositphotos_2885331-stock-photo-blue-butterfly.jpg:

since the image is about 85 kb in size and base64 encoding increases the size by around 30 percent i would assume that the string contains around 110 kb.

As for the flutter way how i encoded this:

 void sendImage() async {
    var file = await selectImage();
    List<int> imageBytes = file.readAsBytesSync();
    var base64Image = base64.encode(imageBytes);
    if (file != null) {
      stompClient.send(
        destination: '/app/imageQueue',
        body: base64Image,
      );
      print("done sending");
    }
  }

I have listed the resulting string below if you want to paste it somewhere.

base64EncodedButterfly.txt

@SimonServant
Copy link
Author

SimonServant commented Jun 30, 2021

After checking with the Postman feature i get the following error message when trying the same with byte Uploads and plain STOMP.

I dont get this message when using the stomp client in flutter. This might be an issue or once again a fault on my end.

image

@KammererTob
Copy link
Contributor

Hey,

i've looked into this a bit and it might be that Spring does need another configuration to accept those large buffer sizes. I've tested it on my own server and it also dropped the connection without showing any errors (even in the socket trace). The close reason given on the client was: "The decoded text message was too big for the output buffer and the endpoint does not support partial messages".

Here is what i've changed (you probably also need your configuration from the first post):

    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        container.setMaxTextMessageBufferSize(200000);
        return container;
    }

This then at least gave me another error related to Stomp, so i am confident that this is no longer a limit on the client, but on the server.

@SimonServant
Copy link
Author

Hi,

Thanks a lot. I found a similar post indicating the same on stackoverflow:

https://stackoverflow.com/questions/21730566/how-to-increase-output-buffer-for-spring-sockjs-websocket-server-implementation
https://stackoverflow.com/questions/57768226/spring-websocket-unable-to-receive-large-data

Interestingly it seems that by adjusting the configureWebSocketTransport one can only adjust outgoing buffer not adjusting the actual buffer sizes for incoming messages.

One more thing to add: I tested the websockets in Spring boot integration tests. They also did not return error messages on to low buffer sizes simply not returning anything. Likewise the server did not log anything even on log level trace.

Since the problem (not recieving error messages) exists for Kotlin / Java Implementations of the client too, it seems to be non specific for the stomp client.

It makes me wonder how the error was recieved on the experimental Postman feature tho.

Anyway. Thank you for your kind help.

@KammererTob
Copy link
Contributor

It makes me wonder how the error was recieved on the experimental Postman feature tho.

I think it just notices that the connection was closed with a closeCode/reason which is erroneous, the same as i was able to determine why the connection was closed.

@SimonServant
Copy link
Author

Hello again,

this kind of question is kind of unrelated to the message before and goes over the topic of simply using the flutter extension. Of course you can therefore simply chose to not answer.

I am trying to utilize the binaryBody property of your package. The sending is done by a spring boot application which send the message via a SimpMessagingTemplate and the method: sendAndConvert.

Neither sending the BinaryMessage (org.springframework.web.socket.BinaryMessage) nor sending plain Bytes (byte[] or ByteArray in Kotlin) nor sending a GenericMessage with byte Payload results in the binaryBody being filled.

The result ist always the bytes being casted to a body property.
(example kotlin)

    fun messageClientQueueBytes(taskId: String, bytes: ByteArray) {
        try {
            var generic: GenericMessage<ByteArray> = GenericMessage<ByteArray>(bytes)
            simpMessagingTemplate.convertAndSend("/queue/task/test", generic)
        } catch (ex: Exception) {
            logger.error("StreamService_messageClientQueue_ ${ex.message})")
        }
    }

(example dart) (note i am not using sockjs config on server or client anymore)

    client.subscribe(
        // destination: '/queue/task/${stream.id}',
        destination: '/queue/task/test',
        callback: (frame) {
          /// Case: Its a image or file message
          if (frame.binaryBody != null) {
            BlocProvider.of<StreamBloc>(context)
                .add(StreamUpdateRecieved(update: frame.binaryBody));
          }

          /// Case: Status Message sent
          if (frame.body != null) {
            Map<String, dynamic> result = json.decode(frame.body);
            if (result['status'] == "SETUP FINISHED") {
              BlocProvider.of<StreamBloc>(context).add(StreamSetupFinished());
              startTimer();
            }
          }
        });

do you happen to know what kind of message i have to send here to access the binaryBody or am i misunderstanding something ?

Thanks in advance if you spend your time on this :)

@KammererTob
Copy link
Contributor

Hey,

i think you need to specifically set the content type to "application/octet-stream" when converting and sending your message.
In your example:

Map<String, Object> headers = new HashMap();
headers.put("contentType", "application/octet-stream");
simpMessagingTemplate.convertAndSend("/queue/task/test", generic, headers)

Note: I haven't tested this code, so consider this more or less pseudo code.

@SimonServant
Copy link
Author

0:"content-type" -> "application/octet-stream"
1:"destination" -> "/queue/task/test"
2:"subscription" -> "sub-0"
3:"message-id" -> "08ccf41b-cdf0-9444-bd0e-df16f688162f-0"
4:"content-length" -> "6"

Did sadly still not work. Actually the content type was even set to application/octet-stream without changing the headers.

Stillm thank you for your effort :) .

@KammererTob
Copy link
Contributor

Hey,

i will check later today or in the next few days if i can set up a test case and see if i can make this work.

@KammererTob
Copy link
Contributor

KammererTob commented Jul 7, 2021

Hey,

i've looked into this a bit. It seems like our parser does never provide the binaryBody, even if the content type is an octet stream. This is not consistent with the spec and we need to fix this in a future version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants