Search code examples
spring-bootrabbitmqamqpspring-amqpspring-rabbit

Using HttpHeaders in org.springframework.amqp.core.Message class


I am trying to save incoming HttpHeaders inside a org.springframework.amqp.core.Message to later pull out of the Message from a queue and cleanly and use as HttpHeaders in a RestTemplate. It just doesn't seem very clean to me.

I have a @PostMapping in a spring rest controller which takes inbound HttpHeaders like this:

@PostMapping
public ResponseEntity<String> inbound(@PathVariable String flow, @RequestHeader HttpHeaders headers, @RequestBody byte[] payload) {

Which then calls createMessage (below) method inside a org.springframework.amqp.core.Message to store the HttpHeaders inside Message.

Retrieving the headers from the org.springframework.amqp.core.Message like I am doing doesn't seem very clean because:

  • I assume each value is a String which may not be the case
  • I have to remove "[" and "]" sometimes because of the List object that may come back from Map<String, Object> headers = message.getMessageProperties.getHeaders();

Does anyone know a cleaner way to pull the headers out of Message and add them to an HttpHeaders object? What I have seems rather assuming and clunky as I don't want to:

  • assuming each value is a String (like I am doing below)
  • using instanceof and typecasting each datatype individually?
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.http.HttpHeaders;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Component;

@Component
public class MessageHelper {

    public Message createMessage(HttpHeaders httpHeaders, byte[] payload) {
        Map<String, Object> headers = new HashMap<>();
        headers.putAll(httpHeaders);

        return MessageBuilder
                .withBody(payload)
                .copyHeaders(new MessageHeaders(headers))
                .setContentType(httpHeaders.getContentType().toString())
                .setContentEncoding(StandardCharsets.UTF_8.displayName()).build();
    }

    public HttpHeaders getHeaders(Message message) {
        HttpHeaders httpHeaders = new HttpHeaders();
        Map<String, Object> headers = message.getMessageProperties().getHeaders();
        for (Map.Entry<String, Object> header : headers.entrySet()) {
            String value = header.getValue().toString().replace("]", "").replace("[", "");
            httpHeaders.addIfAbsent(header.getKey(), value);
        }

        return httpHeaders;
    }
}

Note: I am not using spring-integrations which seems to have other options like MessageHeaderAccessor or spring-messaging which seems to have MessageHeaders - but what could I be missing?


Solution

  • See here https://github.com/spring-projects/spring-amqp/blob/main/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/DefaultMessagePropertiesConverter.java#L176-L223 for how Spring maps arbitrary headers when writing to AMQP.

    If a header value is not one of the types that AMQP supports, a simple toString() operation is performed on it (hence the [...], if it is something like a Set).

    Supported types:

            boolean valid = (value instanceof String) || (value instanceof byte[])
                    || (value instanceof Boolean) || (value instanceof Class)
                    || (value instanceof LongString) || (value instanceof Integer) || (value instanceof Long)
                    || (value instanceof Float) || (value instanceof Double) || (value instanceof BigDecimal)
                    || (value instanceof Short) || (value instanceof Byte) || (value instanceof Date)
                    || (value instanceof List) || (value instanceof Map) || (value instanceof Object[]);
    

    For container types (arrays, lists) the method is called recursively.

    One solution might be to serialize the headers into a single byte[] header.