smallrye-reactive-messaging icon indicating copy to clipboard operation
smallrye-reactive-messaging copied to clipboard

RabbitMQ - print "SRMSG17038: No valid content_type set" when content encode set

Open xiyuanpingtadi opened this issue 1 year ago • 6 comments

I use quarkus-smallrye-reactive-messaging-rabbitmq in my quarkus project

provide:

    @Incoming("test-a")
    @Outgoing("test-b")
    public Message<Price> provide(Message<byte[]> message) {
        final OutgoingRabbitMQMetadata metadata = new OutgoingRabbitMQMetadata.Builder()
            .withContentEncoding(StandardCharsets.UTF_8.name())
            .withContentType("application/json")
            .build();
        System.out.printf(new String(message.getPayload()));
        message.ack();
        return Message.of(new Price(new String(message.getPayload()),1.1),
            Metadata.of(metadata));
    }

consume:

    @Incoming("test-b")
    public Uni<Void> consume(Message<JsonObject> price) {
        Optional<IncomingRabbitMQMetadata> metadata = price.getMetadata(
            IncomingRabbitMQMetadata.class);
        metadata.ifPresent(meta -> {
            final Optional<String> contentEncoding = meta.getContentEncoding();
            System.out.println(contentEncoding.orElse("empty content"));
            final Optional<String> contentType = meta.getContentType();
            System.out.println(contentType.orElse(""));
        });

        System.out.println(price.getPayload().toString());
        price.ack();
        return Uni.createFrom().voidItem();
    }

log with:

2023-06-29 11:13:49,514 WARN  [io.sma.rea.mes.rabbitmq] (vert.x-eventloop-thread-0) SRMSG17038: No valid content_type set, failing back to byte[]. If that's wanted, set the content type to application/octet-stream with "content-type-override"
UTF-8
application/json
2023-06-29 11:13:49,530 ERROR [io.sma.rea.mes.provider] (vert.x-eventloop-thread-0) SRMSG00200: The method RabbitMQPriceConsumer#consume has thrown an exception: java.lang.ClassCastException: class [B cannot be cast to class io.vertx.core.json.JsonObject ([B is in module java.base of loader 'bootstrap'; io.vertx.core.json.JsonObject is in unnamed module of loader io.quarkus.bootstrap.classloading.QuarkusClassLoader @5606c0b)

When I remove this line .withContentEncoding(StandardCharsets.UTF_8.name()) It's right.

2023-06-29 11:34:02,828 INFO  [io.sma.rea.mes.rabbitmq] (vert.x-eventloop-thread-0) SRMSG17000: RabbitMQ Receiver listening address user-sync.test-b
empty content
application/json
{"name":"no utf-8","price":1.1}

And if I keep .withContentEncoding(StandardCharsets.UTF_8.name()) and use byte[] in consume param :

    @Incoming("test-b")
    public Uni<Void> consume(Message<byte[]> price) {
        Optional<IncomingRabbitMQMetadata> metadata = price.getMetadata(
            IncomingRabbitMQMetadata.class);
        metadata.ifPresent(meta -> {
            final Optional<String> contentEncoding = meta.getContentEncoding();
            System.out.println(contentEncoding.orElse("empty content"));
            final Optional<String> contentType = meta.getContentType();
            System.out.println(contentType.orElse(""));
        });

        System.out.println(new String(price.getPayload()));
        price.ack();
        return Uni.createFrom().voidItem();
    }

the print is right but have a warnging in log :

2023-06-29 11:36:44,890 WARN  [io.sma.rea.mes.rabbitmq] (vert.x-eventloop-thread-0) SRMSG17038: No valid content_type set, failing back to byte[]. If that's wanted, set the content type to application/octet-stream with "content-type-override"
UTF-8
application/json
{"name":"use utf-8 and consume param use byte","price":1.1}

xiyuanpingtadi avatar Jun 29 '23 03:06 xiyuanpingtadi

If remove .withContentEncoding(StandardCharsets.UTF_8.name()) and use byte[] in consume param is same as use utf-8 use JsonObject into consume param

empty content
application/json
2023-06-29 11:42:16,074 ERROR [io.sma.rea.mes.provider] (vert.x-eventloop-thread-0) SRMSG00200: The method RabbitMQPriceConsumer#consume has thrown an exception: java.lang.ClassCastException: class io.vertx.core.json.JsonObject cannot be cast to class [B (io.vertx.core.json.JsonObject is in unnamed module of loader io.quarkus.bootstrap.classloading.QuarkusClassLoader @21a947fe; [B is in module java.base of loader 'bootstrap')

xiyuanpingtadi avatar Jun 29 '23 03:06 xiyuanpingtadi

This has always annoyed me. I'm not sure what the reasoning for this behavior is but...

If you set contentEncoding, it ignores contentType, and warns you if contentEncoding is not application/octet-stream. Finally it always returns a byte[]

You can silence the warning (as the log states) by setting content-type-override in the connector configuration.

If you set contentType it must be application/json or text/plain exactly (no media-type arguments like charset). For application/json it returns a Vert.x JsonObject, JsonArray, String, Number or Bool; the Vert.x JSON types. For text/plain it returns String and assumes it's UTF-8 encoded.

So, according to those complicated rules...

In your first example you are receiving a JsonObject and setting contentEncoding, this violates the rule that setting contentEncoding will return a byte[], and you get the casting exception.

In your second example, you are receiving a byte[] and setting contentEncoding, but you are setting it to UTF-8; so this works but since as the log states you need to set content-type-override to application/octet-stream for the connector configuration. Basically by setting content-type-override you are stating that even though you've set the contentType to UTF-8 (something it doesn't understand), you realize you will always get back a byte[] value.

kdubb avatar Jul 26 '23 00:07 kdubb

@ozangunalp The rules governing this are not great. I know there's a chart in the docs but maybe it needs to be admonished with a warning or something; I think this is a common mistake. Also, the need to add content-type-override always seemed weird to me.

Could there be a way to interrogate the @Incoming annotated method and make this more friendly. If IncomingRabbitMQMessage.convertValue() could see the target type it could convert based on that.

It would also need to know about MessageConverters as well but those are already passed to ConverterUtils.convert. IncomingRabbitMQMessage.convertValue() could check if there's a matching converter and if so, just send the Buffer. Alternatively, maybe allow annotating the MessageConverter with a @PayloadType annotation to ensure the it's providing the right value to the MessageConvert (but then we are double converting which doesn't seem right).

I think there are options here for a much more developer friendly interface.

kdubb avatar Jul 26 '23 00:07 kdubb

I guess I missed the obvious and easy solution (that is also a good developer experience), always just send Buffer and remove convertValue. MessageConverters exist and are well documented. You get Buffer use a MessageConverter to receive anything else.

kdubb avatar Jul 26 '23 00:07 kdubb

Who defined these rules? Is that in the connector or in RabbitMQ?

About conversion, I would instead use converters and as you say, always send Buffers in the incoming connector.

cescoffier avatar Dec 20 '23 08:12 cescoffier

The rules were created by whoever created the original RabbitMQ connector code. They are not related to RabbitMQ in any way.

I think it was just a way to do some conversion before conversion existed.

kdubb avatar Dec 20 '23 08:12 kdubb