I'm trying to write a Spring-WS client for a preexisting service. The endpoint offers two very similar actions, they both consume my data and respond with a simple object status; I need to use both. The difference is, one of them responds with HTTP status code 200 and the other with 202. In the first case the status object is decoded correctly and returned from WebServiceTemplate.marshallSendAndReceive(), but in the second case it's not decoded at all, and the method returns null.
According to the HTTP spec, the HTTP status 202 is Accepted, and its purpose is to indicate that the request has been received but not yet acted upon. However, the response may still contain useful information, like current request status or a completion estimate. I want to get this response.
I tried to debug the exact process and noticed my program executing the following code in the org.springframework.ws.transport.http.AbstractHttpSenderConnection.hasResponse() method:
protected final boolean hasResponse() throws IOException {
int responseCode = getResponseCode();
if (HttpTransportConstants.STATUS_ACCEPTED == responseCode ||
HttpTransportConstants.STATUS_NO_CONTENT == responseCode) {
return false;
}
...
}
This code fragment seems responsible for never getting the status object from the 202 Accepted response. (Or a 204 No Content response, but that's obviously acceptable.)
Is there a way around this? It doesn't seem possible to override this method in a bean, it's marked final.
The closest thing to an answer I could find was the following SWS JIRA ticket. It's marked "Resolved" since August 2012, but it's really not, as the comment from 2015 says.
my workaround:
Implement a custom HttpResponseInterceptor to handle a HTTP202:
public class MyInterceptor implements HttpResponseInterceptor {
#Override
public void process(HttpResponse httpResponse, HttpContext arg1) throws HttpException, IOException {
if (202 == httpResponse.getStatusLine().getStatusCode()) {
httpResponse.setStatusLine(new BasicStatusLine(httpResponse.getStatusLine().getProtocolVersion(),200,httpResponse.getStatusLine().getReasonPhrase()));
}
}
}
Now, add the interceptor to my http client builder when creating the webServiceTemplate
public CloseableHttpClient httpClient() throws Exception {
return HttpClientBuilder.create().addInterceptorLast(new MyInterceptor()).setSSLSocketFactory(sslConnectionSocketFactory()).build();
}
Related
I am writing a org.springframework.cloud.gateway.filter.GatewayFilter (spring-cloud-gateway), that attempts to consume a response that follow a certain arbitrary protocol. The response body goes along the lines of:
200\n
header1:value1\n
header2:header2\n
\n
<the actual body>
The idea is, the entire response metadata from the downstream service is in the response body (much like how a SOAP envelope encloses the real body). The client of the gateway should receive a modified unwrapped response.
I've been following how ModifyRequestBodyGatewayFilterFactory and ModifyResponseBodyGatewayFilterFactory as guide, but I don't think they fit my use case.
I believe I can achieve this by returning a subclass of ServerHttpResponseDecorator, but I can't wrap my head yet on how to go about implementing:
#Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
// unwrap status code
// unwrap headers
// let it continue as usual
}
I was able to achieve this with:
#Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return Mono.from(body).flatMap { buffer ->
val size = readUtf8Line(buffer)
// recompute 'Content-Length', if applicable
super.writeWith(Mono.just(buffer))
}
}
The readUtf8Line is just my own method, that consumes the DataBuffer until it reaches a line break. It is also important to return the number of bytes read (i.e., size), because the Content-Length needs to be recomputed, if present.
I make a call from my frontend to the userPrivateProfile controller.The route is /api/user/private/:id so let's say I make a call at /api/user/private/65. Before I excecute the controller the request is intecepted by SecurityAuthAction where I make sure that the request headers have the token and if that's the case I want to change the :id to something different.
Controller.java
#With(SecurityAuthAction.class)
public Result userPrivateProfile(Long id) {
//LOGIC
}
SecurityAuthAction.java
public Promise<SimpleResult> call(Http.Context ctx) throws Throwable {
String[] authTokenHeaderValues = ctx.request().headers()
.get(AUTH_TOKEN_HEADER);
if ((authTokenHeaderValues != null) && (authTokenHeaderValues.length == 1) && (authTokenHeaderValues[0] != null)) {
Long userId = sessionService
.findUserByToken(authTokenHeaderValues[0]);
ctx.args.put("id",userId.toString());
return delegate.call(ctx);
}
My problems are
that I cannot retrieve the :id specified from the original call using ctx
Since I cannot find where the request parameter is I cannot change it as well
I tried iterating through the ctx.args Map but I didn't find something there.The output is:
ROUTE_VERB ROUTE_
ACTION_METHOD
ROUTE_CONTROLLER
ROUTE_COMMENTS
ROUTE_PATTERN
GET
userPrivateProfile
controllers.Controller
/api/user/private/$id<[^/]+>
Thanx for your help :)
Unfortunately the Play Framework (certainly in version 2.1) does not give you easy access to URL query parameters when performing action composition. This discussion on the Play Google group may be of interest to you. One workaround mentioned there is to parse the URL in SecurityAuthAction to get the value of the id query parameter. However this is a little messy, and doesn't help you with the next part of your problem, which is changing the id before it gets to the downstream action.
Changing the details of the request as it's being handled by the server seems uncommon and wrong to me. Typically if you wanted to change what a client is requesting, you'd issue a HTTP 303 response redirecting them to the URL you want them to go to. But this doesn't feel like a situation for redirection. What I reckon you should do is just push your call to sessionService down to your main controller class:
SecurityAuthAction.java
public Promise<SimpleResult> call(Http.Context ctx) throws Throwable {
if (authorisation token header is present in request) {
return delegate.call(ctx);
}
return unauthorised();
}
Controller.java
#With(SecurityAuthAction.class)
public Result userPrivateProfile(Long id) {
// We've already established that an auth token header is present in the request
final String authToken = ctx.request().headers().get(AUTH_TOKEN_HEADER)[0];
final Long userId = sessionService.findUserByToken(authToken);
// TODO: Proceed...
}
If userId is something that you need all over your application, then it might be a candidate for inclusion in your application's cookie.
I am trying to nest two request factory calls in each other. I retrieve a post object and in the success-method i use the same object again (just for testing purposes, I get the same behavior for other request like for example persisting).
The problem is: Only the first request reaches the server.
I don't get any error message. If I debug the code, everything works until the second request is fired. Nothing happens then. The method on the backend is not called, the frontend shows no error, even if I implement the "onFailure"-method for the receiver of the second request.
public class RequestFactoryFindTest extends GWTTestCase{
/**
* must refer to a valid module that sources this class.
*/
public String getModuleName() {
return "com.Test.MyTest";
}
public void test(){
final ClientFactory clientFactory = GWT.create(ClientFactoryImpl.class);
final MyRequestFactory requestFactory = clientFactory.getRequestFactory();
final PostRequest request = requestFactory.postRequest();
request.findPost(1l).fire(new Receiver<PostProxy>() {
#Override
public void onSuccess(PostProxy response) {
final ClientFactory clientFactory = GWT.create(ClientFactoryImpl.class);
final MyRequestFactory requestFactory = clientFactory.getRequestFactory();
final PostRequest request = requestFactory.postRequest();
System.out.println("outer success");
request.findPost(1l).fire(new Receiver<PostProxy>() {
#Override
public void onSuccess(PostProxy response) {
System.out.println("inner success");
}
});
}
});
}
}
Can someone explain this?
Edit:
I tried a lot of stuff like to fire an event on the event bus, catch the event and do my inner request factory call there. But nothing worked. I think this is some Issue with the GWTTestcase in combination with RequestFactory.
I also changed my code, so i use only one clientFactory.
Try to create an event in the first onSuccess method. When your event is handled, you could send another request to the server.
Check out How to use the GWT EventBus to use the eventbus.
Thomas Broyer statement is also right. You should only use one RequestFactory and one ClientFactory!
This may be a problem when you are constructing your second client factory as per Thomas Broyer. You should probably go into your ClientFactory.java interface and at the top add the the single client factory instance. Also put a GWT.log("ON SUCCESS") at the top of your onSuccess(PostProxy response) to make sure it is getting there.
public interface ClientFactory {
public static final ClientFactory INSTANCE = GWT.create(ClientFactory.class);
...
Then you can simple do somehting like the following
final PostRequest request = ClientFactory.INSTANCE.getRequestFactory().postRequest();
This is my method for creating Response with header parameters and body:
public Response sendOKResponse(request req)
{
ResponseBuilderImpl builder = new ResponseBuilderImpl();
// set the header params.
for(int index =0; index<req.headerParameters.size(); index++)
{
builder.header(req.headerParameters.get(index).getName(), req.headerParameters.get(index).getBody());
}
// set the body and response code
builder.status(Response.Status.OK).entity(req.getBody());
Response r = builder.build();
return r;
}
And this is how i return the Response:
Response response;
response = sendBadMesseage();
return response;
This code returns code 204(No content) instead of 200.
Any ideas why?
You shouldn't be instantiating your response builder with new, the whole point of the JAX-RS abstraction layer is to hide implementation details away from calling clients. This is what makes it possible to have various vendor implementations which can be interchanged at will. Also, if you are using JEE6, or hope to migrate to it, this code will almost certainly fail. Most JEE6 vendor implementations utilize CDI, which is concept-incompatible with usage of new. But, closer to the topic, the JAX-RS implementation specifies that a 204 status code be returned if a responses wrapped entity is null. You might want to verify this is not the case in any of your methods. Also, you might want to make some changes to your code:
public Response sendOKResponse(request req) {
ResponseBuilder response = Response.ok();
// set the header params.
for(Header h: req.headerParameters()) {
builder = builder.header(h.getName(), h.getValue());
}
// set the body and response code
builder = builder.entity(req.getBody());
return builder.build();
}
Your sendBadMessage method should also look similar to above. You can log your entity before adding it to the builder, to verify that you only get a 204 when it's null.
Update: The issue is with the setting used for the MaxAge. Setting it to zero will cause the cookie to be deleted, hence it was shown in the response header but was then deleted and did not show in the request. Setting it to -1 so that it will not be stored persistently
I am doing some work on a filter in which I want it to set a cookie to indicate that the user qualifies to take a survey and that the next time he comes back to the site a survey popup window will be displayed.
I am not seeing any errors in the logs but the cookie never gets set.
Can a filter be used this way to add a cookie? Is there where a HttpServletResponseWrapper comes into play?
The thought here is that when the user comes to the the site there is a check to see if the cookie is present. If it is not then a cookie is created and added to the response. As the user navigates the site, the cookie check method is called to make sure that the hit counter is not increased for that given user.
The cookie check method never sees the cookie. Using web developer plugin for firefox, the cookie in question is not present.
Below is the filter class with the relevant methods.
public class HitCounterFilter extends TemplateFilter {
public void doMainProcessing(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) {
HttpServletRequest httpRequest = (HttpServletRequest) pRequest;
HttpServletResponse httpResponse = (HttpServletResponse) pResponse;
// prevent thread interference and memory consistency errors
synchronized (lock) {
int hitCounter = this.readFile(localFile);
// if user has not been counted
if (!this.checkForCookie(httpRequest, "gtgo_visitor")) {
this.writeFile(localFile, ++hitCounter);
this.createCookie(httpRequest, httpResponse, String.valueOf(hitCounter), "gtgo_visitor");
}
}
}
private void createCookie(HttpServletRequest pHttpRequest, HttpServletResponse pHttpResponse, String pCookieValue, String pCookieName) {
try {
Cookie cookie = new Cookie(pCookieName, pCookieValue);
URL url = new URL(pHttpRequest.getRequestURL().toString());
cookie.setDomain(url.getHost());
cookie.setPath(this.getCookiePath(pHttpRequest));
cookie.setComment("user is not eligible to take the survey this time");
cookie.setMaxAge(0);
pHttpResponse.addCookie(cookie);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
private boolean checkForCookie(HttpServletRequest pHttpRequest, String pCookieName) {
for (Cookie cookie : pHttpRequest.getCookies()) {
if (StringUtils.equalsIgnoreCase(cookie.getName(), pCookieName)) {
return true;
}
}
return false;
}
}
The issue is with the setting used for the MaxAge. Setting it to zero will cause the cookie to be deleted, hence it was shown in the response header but was then deleted and did not show in the request. Setting it to -1 so that it will not be stored persistently - it will be present as long as the session is active (removed once the session is closed)
I haven't worked with the SCAM project before, but it looks like that is what you're using. I'm not sure if the super class's implementation of doFilter calls your implementation's doMainProcessing method before calling the FilterChain's doFilter method or not.
Because the framework passes the FilterChain into your doMainProcessing method, it is likely that it expects that your implementation will call pChain.doFilter(pRequest, pResponse, pChain).
I might be wrong about this, but if the filter chain is abandoned, its possible that the headers written to the ServletResponse object, which include the cookie you have attached, will not be returned to the client.
In any case, unless you are specifically blocking access to the requested resource, its wise to propagate the request using the FilterChain.