I'm using Java AWS SDK v1 to send messages to a SQS Queue, and I want to intercept this event to add some MessageAttributes in the message.
I created a new RequestHandler class that extend the RequestHandler2 from the SDK to modify it before the request:
class CustomMessagesAttributesRequestHandler : RequestHandler2() {
override fun beforeRequest(request: Request<*>) {
val originalRequest = request.originalRequestObject
if (originalRequest is SendMessageRequest) {
originalRequest.messageAttributes["my-custom-attribute"] =
MessageAttributeValue().withStringValue("123456789")
}
}
}
The solution above kinda works, it's called before the request, the Message Attribute is added in the originalRequestObject, but when the SQS client send the request it throws an exception:
Unable to calculate the MD5 hash of the message attributes
Digging into AWS SDK code, I see there is a default handler to check the message request with the result to compare both body and attributes MD5, and of course, since I modified the original object they does not match.
Is there a better way to achieve that besides the custom RequestHandler? Or there is a way to recalculate the originalRequestObject MD5?
Related
I have a standard SNS topic and I´ve set the "subscription filter policy" like this:
{
"event": [
"eventName"
]
}
When I publish a message through the AWS console using the attributes message, the message goes to the right SQS subscriber. So the subscription filter worked just fine.
Now I'm trying to do the same on my java code (Spring Boot).
I'm using the lib spring-cloud-aws-messaging which I understand is just a Wrapper of AWS JDK.
The problem is I can't figured out how to set the message attributes just like I did on AWS console.
Doesn´t matter the JSON format I sent to SNS the attributes always are in the body of the SNS message. I guess there is a specific method to set those attributes.
I found com.amazonaws.services.sns.model.MessageAttributeValue
I'm not sure if is the right class, also I couldn't understand how to publish the message and the attributes as the method publish doesn't accept it .
this.amazonSNS.publish(this.snsTopicARN, message, messageAttributes ???);
According to official documentation, there is MessageAttributeValue.Builder, which matches what you need.
https://javadoc.io/static/software.amazon.awssdk/sns/2.10.37/software/amazon/awssdk/services/sns/model/MessageAttributeValue.Builder.html
Map<String, MessageAttributeValue> attributes = new HashMap<>();
attributes.put("event", MessageAttributeValue.builder()
.dataType("String")
.stringValue("eventName")
.build());
PublishRequest request = PublishRequest.builder()
.topicArn("yourTopic")
.message("YourMessageBody")
.messageAttributes(attributes)
.build();
yourDefinedSnsClient.publish(request);
If you want to use spring-cloud-aws you can do something like this:
SnsMessage snsMessage = SnsMessage.builder()
.message("test")
.build();
Map<String, Object> headers = new HashMap<>();
headers.put("event", "eventName");
this.notificationMessagingTemplate.convertAndSend(topicName, snsMessage, headers);
how can I access a custom header from a server response when using TransferManager ?
we have a custom header added in the response from our server, from the client side we use multi part upload with default transfer manager
any suggestion how in how i could hook up it ?
so basically i want to pass over the response from the return response.getAwsResponse(); found in the class: AmazonS3Client on the method
private <X, Y extends AmazonWebServiceRequest> X invoke(Request<Y> request,
HttpResponseHandler<AmazonWebServiceResponse<X>> responseHandler,
String bucket, String key, boolean isAdditionalHeadRequestToFindRegion) {
that response will have the HTTP response from the server containing the custom heather which I'm after, basically is a unique Id send back when the file was 100% completed so than i can manipulate it.
I need to pass over this custom header from the response to the very beginning where I use the transfer manager and the upload.waitForCompletion(),
also i don't want to edit the amazon's,
so does anyone know if there is an interface or some other object which provides me access to it ?
After some debug into the framework I strongly believe that there is no way to have access to the http response when using the TransferManager
for what we are trying to do we need to send an unique id from the server to the client when the file upload is completed and assembled
** therefore if you don't mind in do not use the beauty of the TransferManager you could write "your own TransferMananger" than you will have full control, but again on the client side we don't really want to add custom code but have a standard and simple approach (but that is for my scenario), if you decide to do it manually it can be done I have already tried and works !
So as a alternative we though in send from the server via the eTag, which is not great but will do the job and will keep the client side simple and clean
Any suggestion in how to send this value back in a better way ?
Upload up = tm.upload(bucketName, file.getName(), file);
UploadResult result = (UploadResult) ((UploadImpl) up).getMonitor().getFuture().get();
String uniqueIdFromServer = result.getETag();
I would like to replace Location header in one condition. I use the following to enable Redirect
client.prepareGet(request.getUrl())
.setFollowRedirect(true)
What I did is to extended AsyncCompletionHandler class, and from there I #Override onHeadersReceived(),
#Override
public State onHeadersReceived(final HttpResponseHeaders headers) throws Exception {
String location = headers.getHeaders().get("Location").replace("itmss", "https"
);
DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
httpHeaders.add(HEADER, location);
httpHeaders.add(headers.getHeaders());
return inner.onHeadersReceived(new HttpResponseHeaders(httpHeaders));
}
However, I see that the request still uses itms instead of https, which means the Location header is not replaced.
The current plan is to implement a interceptor that runs before Redirect30xInterceptor.java(https://github.com/AsyncHttpClient/async-http-client/blob/dd459294434a408cff3c65c9f5c402b82d60aaa2/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java), and change the url.
The other plan is to do a while loop for the following code, and whenever receiving 3XX, set a new url in the next request.
client.prepareGet(request.getUrl())
.addHeader("User-Agent", request.getUserAgent())
.setProxyServer(proxyServer)
.execute(new ResponseAsyncCompletionHandler(request))
.toCompletableFuture();
The ultimate goal is to replace itms:// to https://, so the next request uses https instead of itms.
After studying more, okhttp supports interceptor easily.
If wanted to use AsyncHttp it's much more tricky. You have to implement the ResposeFilter, and from there you change the request, and setup replayRequest(true), which means a new request is made.
example code
builder.request(new RequestBuilder(nextRequest).build()).replayRequest(true);
https://github.com/AsyncHttpClient/async-http-client/blob/master/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ResponseFiltersInterceptor.java#L62
I'm using cucumber and citrus together, and in my #Then definition, I have the citrus HTTP response:
#CitrusResource TestRunner runner;
runner.http(builder -> {
final HttpClientResponseActionBuilder rab =
builder.client("citrusEndpointAPI").receive()
.response(HttpStatus.OK).messageType(MessageType.JSON)
.contentType(MediaType.APPLICATION_JSON_VALUE);
Is there a way to store the returned JSON message body into a java JSON object?
You can use the local message store. Each message should be saved to that local in memory storage during the test. You can access the stored messages later in that test case via its name:
receive(action -> action.endpoint("sampleEndpoint")
.name("sampleMessage")
.payload("..."));
echo("citrus:message(sampleMessage.payload())");
Please note that we named the received message sampleMessage. You can access the message store via test context in custom test actions, too.
context.getMessageStore().getMessage("sampleMessage");
Besides that you could also use a custom message validation callback in Java DSL. Here you have full access to the received message content.
receive(action -> action.endpoint("sampleEndpoint")
.validationCallback((message, context) -> {
//Do something with message content
message.getPayload(String.class);
}));
Currently, I have two handlers, one for logging and one for signing the SOAP message (which inherently tampers with the SOAP message). Without the handler chain, MTOM works as expected, inserting a reference to the binary content, rather than inlining the base64 binary content.
As soon as I introduce a handler, the MTOM content is now included inline.
Is it possible to use handlers to sign a SOAP message or is there a more appropriate means of doing this?
Update 1
Unable to post the full source. Essentially though, custom SOAPHandler implementation. It performs some basic XMLDsig type operations on a timestamp (in header), custom header and SOAP body. The resultant digest values are then injected into a signature element in the header.
With respect to the logger, it is again a simple SOAPHandler. If either it or the signing handler are used exclusively, the result is the same, an MTOM message with the byte content inlined. The only progress I made was using a MessageHandler for logging. This allowed me to output the SOAP envelope (albeit with the byte content inlined) and still maintain the MTOM separation. So not really a solution but an indication that any modification of the SOAP message needs to occur at a lower level. This is leading me down the path of tubes.
Update 2
The following is an example of the MessageHandler approach. You can see that the raw HTTP dump will contain the multiple part message whereas the actually output inlines the base64. The only difference between this impementation and a SOAPHandler implementation is that the actual HTTP request changes to be a single part inlined MTOM message.
#Override
public boolean handleMessage(MessageHandlerContext context) {
HttpTransportPipe.dump = true;
Boolean isOutgoing = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (isOutgoing) {
System.out.println("\nOutbound message:");
XMLStreamWriter writer = XMLStreamWriterFactory.create(System.out);
try {
context.getMessage().writeTo(writer);
} catch (XMLStreamException e) {
throw new IllegalStateException("Unable to write");
}
} else {
System.out.println("\nInbound message:");
}
return true;
}
I tried to replicate your problem by putting together a simple service that accepts an image transferred by MTOM. I found that if I put the MTOM-enabling code before setting the handler, it encodes the message properly. If I set the handler first, it does not. Here is where I set up the properly functioning client code:
Service service = Service.create(url, qname);
Greeting greeting = service.getPort(Greeting.class);
BindingProvider bp = (BindingProvider) greeting;
SOAPBinding binding = (SOAPBinding) bp.getBinding();
binding.setMTOMEnabled(true);
service.setHandlerResolver(new HandlerResolver() {
#SuppressWarnings("rawtypes")
public List<Handler> getHandlerChain(PortInfo portInfo) {
List<Handler> handlerList = new ArrayList<Handler>();
handlerList.add(new RGBSOAPHandler());
return handlerList;
}
});
Where RGBSOAPHandler is just some example code I took from another SO answer.
EDIT: Also, if I try setting the handler on the binding and not the service then I get the same problem that you do. So if it looks like this:
Service service = Service.create(url, qname);
Greeting greeting = service.getPort(Greeting.class);
BindingProvider bp = (BindingProvider) greeting;
SOAPBinding binding = (SOAPBinding) bp.getBinding();
binding.setMTOMEnabled(true);
List<Handler> handlerList = new ArrayList<Handler>();
handlerList.add(new RGBSOAPHandler());
binding.setHandlerChain(handlerList);
Then my file is encoded in-line. I don't know why this is, but I suppose the answer is "don't do that". Set your handlers on the Service object.
Looks like I'm limited by the framework and the way in which the handlers work. I think at this stage, my only option is to go to a lower level. I did take a look at using tubes but the same behaviour exhibited itself so it looks as though any attempt to work with the XML of the request fails. As such, I'm going to have to abandon handlers for the time being and investigate at a lower level whether I can make use of codecs to do what I'm after. An MTOM implementation sounds like it may do what I'm after at the byte level:
http://jax-ws.java.net/nonav/jax-ws-20-fcs/arch/com/sun/xml/ws/encoding/MtomCodec.html
I imagined this would be a lot less complex to get working but will update with my progress on the codec front.
#David: Thanks for your help on the handler front but looks as though there is no solution at that level.
Update 1
Came up with an alternate solution that works for my purposes.
I sign the necessary parts of the SOAP message using my SOAPHandler.
Wrote a new SOAPHandler that then takes resultant message and manually extracts the incorrectly inlined binary content.
I then create an AttachmentPart and inject the content from Step 2 into that. It takes Base64 encoded text too which is handy. That AttachmentPart then has a reference UUID assigned to it for Content-Id.
I then create a new element in place of the Base64 content in the SOAP body that reference the UUID, along the lines of the following:
<xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="cid:UUID!!!"></xop:Include>
Will probably write a blog post on this as it's been a bit of an epic journey to this point. It was not the best solution but it was certainly easier than going down the tubes/codec path.