How do I keep only specific headers but get rid of all other headers?
I'm trying to remove irrelevant headers that were set by an upstream HttpRequestHandlingMessagingGateway.
I tried specifying a handle() function that returns a new message containing only the headers I'm interested in but it does not seem to work. The log message contains a bunch of HTTP headers that were set from the upstream HttpRequestHandlingMessagingGateway.
IntegrationFlows.from(myChannel())
// Strip off the HTTP specific headers
.handle((payload, headers) -> MessageBuilder
.withPayload(payload)
.setHeader("myCustomHeader1", headers.get("myCustomHeader1", String.class))
.setHeader("myCustomHeader2", headers.get("myCustomHeader2", String.class))
.build()
)
.log()
I see there is a HeaderFilter but it requires you to know the name of the headers you want to remove. In my case I only want to keep 2 custom headers and remove everything else.
class HeaderStripper {
public Message<?> strip(Message<?> msg) {
return org.springframework.integration.support.MessageBuilder.withPayload(msg.getPayload())
.setHeader("foo", msg.getHeaders().get("foo"))
.setHeader("bar", msg.getHeaders().get("bar"))
.build();
}
}
and then
.transform(new HeaderStripper())
Artem Bilan's comment pointed me in the right direction for how I would do it inline. I just could not get the syntax correct previously, here's how it looks with an inline transform():
IntegrationFlows.from(myChannel())
// Strip off the HTTP specific headers
.transform(Message.class, message -> MessageBuilder
.withPayload(message.getPayload())
.setHeader("myCustomHeader1", message.getHeaders().get("myCustomHeader1", String.class))
.setHeader("myCustomHeader2", message.getHeaders().get("myCustomHeader2", String.class))
.build()
)
.log()
Related
I was using java.net.http package to create custom request. i could set additional headers to pass but can't set a header map in the request, is there any way i can do this in this particular way of making http request, or in any other method of making request.
HttpRequest.newBuilder()
.uri(URI.create(targetUrl)
.setHeader("Content-Type", "application/json")
.method(myMethod, HttpRequest.BodyPublishers.ofString(body))
.build();
I want to set HeaderMap => Map<String,List> just like we are setting a single header above.
It would be great if anyone could help me in this.
The HttpRequestBuilder doesn't allow a direct set of the Map<String, List<String>> into the headers nor exposes an equivalent of putAll.
However, provided you have a Map<String, List<String>> called yourHeaders, then you can simply do the following:
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(targetUrl)
.setHeader("Content-Type", "application/json")
.method(myMethod, HttpRequest.BodyPublishers.ofString(body));
yourHeaders.forEach((headerKey, headerValues) -> headerValues.forEach(value -> builder.header(headerKey, value)));
return builder.build();
A little background
I would like to call service's APIs, while doing retries on 5xx errors. Also, I would like to get an access to every failed request (for logging purposes).
Code
getClient()
.get()
.uri("http://example.com")
.retrieve()
.onStatus(HttpStatus::is5xxServerError, rsp -> Mono.error(new ApiServerException("Server error", rsp.rawStatusCode())))
.bodyToMono(ReportListResponse.class)
.retryWhen(
Retry
.backoff(3, Duration.ofSeconds(1))
.filter(throwable -> throwable instanceof ApiServerException)
)
.block();
Issue
How can I achieve the goal of being able to access the response of every failed request? I was trying to retrieve the body while using rsp.bodyToMono(String.class) in onStatus method. Unfortunately it didn't give me an expected output.
You would need to use response.bodyToMono in the onStatus to get response body. The following example shows how to deserialize body into String but you could define POJO as well.
getClient()
.get()
.uri("http://example.com")
.retrieve()
.onStatus(HttpStatus::isError, response ->
response.bodyToMono(String.class)
.doOnNext(responseBody ->
log.error("Error response from server: {}", responseBody)
)
// throw original error
.then(response.createException())
)
.bodyToMono(ReportListResponse.class)
}
I've read quite a few documentations and other stackoverflow questions regarding this matter but I can't seem to get my code working.
So essentially I have a WebClient making a POST request.
IF the response status is 200, then I make another call to another endpoint using a different WebClient. After second webclient call, return a string.
ELSE I just return a String from the method e.g. "failed to create order.".
Simple enough. (this is all done in a seperate thread fyi, not the main thread.)
But I've noticed that if i do get back a 500 error code, WebClient throws an exception. What I want to do is capture the exception and handle that gracefully and return a String like "Error calling first endpoint etc."
This is what I have so far:
private String generateOrder(ImportedOrderDetails importedOrderDetails)
{
Order requestBody = generateRequestBody(importedOrderDetails);
OrderResponse responseForCreatingOrder = orderWebClient()
.post()
.body(Mono.just(requestBody), Order.class)
.retrieve()
.bodyToMono(OrderResponse.class)
.block();
if (responseForCreatingOrder.getResponseStatus().equals(SUCCESS))
{...other call using different webclient}
else{ return "Error creating order."}
This works fine when the response status is 200 but when its 500 it blows up.
OrderResponse is a custom object. orderWebClient() is just a method that returns a prebuilt WebClient containing the baseUrl and headers etc.
I came across this:
Spring WebClient - How to handle error scenarios I did try implementing it but couldn't figure out where to put the block method since I kept on getting the following:
reactor.core.Exceptions$ReactiveException: java.lang.Exception
at reactor.core.Exceptions.propagate(Exceptions.java:393)
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:97)
at reactor.core.publisher.Mono.block(Mono.java:1680)
I had to edit my code a bit to try and implement the answer to that question:
private Mono<? extends Throwable> handleError(String message) {
log.error("====---"+message);
return Mono.error(Exception::new);
}
private String generateOrder(ImportedOrderDetails importedOrderDetails)
{
Order requestBody = generateRequestBody(importedOrderDetails);
Mono<OrderResponse> responseForCreatingDemo = orderWebClient()
.post()
.body(Mono.just(requestBody), Order.class)
.retrieve()
.onStatus(
(HttpStatus::is5xxServerError),
(it -> handleError(it.statusCode().getReasonPhrase()))
)
.bodyToMono(OrderResponse.class);
System.out.println("-=-"+responseForCreatingDemo);
if (responseForCreatingOrder != null && responseForCreatingOrder.block().getHeader().getResponseStatus().equals(SUCCESS)){...}
The error was coming from the .block part in the if condition. I believe this is something pretty trivial and missing the big picture.
Any suggestions?
It seems you have two kinds of statuses:
Http status, defined by the protocol itself (see HTTP response status codes)
Something specific to the application you're working on, encapsulated into the OrderResponse class.
So you have to handle two "errors" instead of one, one of the possible solutions might look like
.retrieve()
.bodyToMono(OrderResponse.class)
// 4xx, 5xx errors and return "Unable to create order" String instead
.onErrorContinue(WebClientResponseException.class, (ex, v) ->
Mono.just("Unable to create order"))
// if application specific status is not "ok" return "Unable to create order"
.map(it -> it.ok ? "Ok response" : "Unable to create order")
.block();
Please note that this code sample ignores exception and does not even log it
I receive a Request from the Client which returns a SendRequest-Object that has a HttpMethod, a path and data to send.
Now I would like to send the Request depending on the object I get to the API.
After sending I will get a Response.
The problem now is how can I send the payload and receive the response.
#Bean
public IntegrationFlow httpPostSendRawData() {
return IntegrationFlows.from(
Http.inboundGateway("/api/data/send")
.requestMapping(r -> r.methods(HttpMethod.POST))
.statusCodeExpression(dataParser().parseExpression("T(org.springframework.http.HttpStatus).BAD_REQUEST"))
.requestPayloadType(ResolvableType.forClass(DataSend.class))
.crossOrigin(cors -> cors.origin("*"))
.headerMapper(dataHeaderMapper())
)
.channel("http.data.send.channel")
.handle("rawDataEndpoint", "send")
.transform(/* here i have some transformations*/)
.handle(Http.outboundGateway((Message<SendRequest> r)->r.getPayload().getPath())
.httpMethod(/* Here I would like to get the Method of SendRequest*/)
//add payload
.extractPayload(true))
.get();
It's not clear what is your SendRequest, but the idea for the method is exactly the same what you have done for the url:
.httpMethodFunction((Message<SendRequest> r)->r.getPayload().getMethod())
Although, since you want to have some extraction for the request body, you need to do that in advance in the transform() and move values for url and method to the headers.
There is just no any payload extraction in the Http.outboundGateway: it deals with the whole request payload as an entity for HTTP request body.
I have tried this to set expiration time of a message and converting and sending it using RabbitMessagingTemplate:
Map<String,Object> headers = new HashMap<>();
headers.put("expiration", "20000");
rabbitMessagingTemplate.convertAndSend(exchange.getName(),routingKey, event, headers);
but it does not work because expiration shall be set as a property and NOT as a header. Unfortunately RabbitMessagingTemplate does not provide a way to pass message properties but only headers. On the other hand I need to convert the message becasue I use JecksonMessageConverter.
How can I add message properties before sending the message with RabbitMessagingTemplate?
Add a MessagePostProcessor to the underlying RabbitRemplate's beforePublishPostProcessors.
I can't look at the code right now but I am surprised it's not mapped.
EDIT
Use header name amqp_expiration. See AmqpHeaders.EXPIRATION. It is mapped to the message property.
Unrecognized headers are mapped to headers.
EDIT2
In any case, given your requirements, you might be better off not using the RabbitMessagingTemplate but use the RabbitTemplate and a MessagePostProcessor instead; it will be a little more efficient...
rabbitTemplate.convertAndSend(exchange.getName(), routingKey, event, m -> {
m.getMessageProperties().setExpiration(...);
...
return m;
};