Spring MVC with Spring Integration HTTP: how to pass the queryString avoiding the "?" encoding with "%3F" ? - spring

I have two Spring MVC applications interconneted between each others with Spring Integration HTTP.
I have an "application1" and an "application2".
The application1 receive this HTTP GET request:
http://localhost:8080/application1/api/persons/search/findByName?name=Name
The application1 manage the request with this #Controller:
#Controller
public class ApplicationController {
#RequestMapping(value = "/api/{repository}/search/{methodName}", method = RequestMethod.GET)
public void search(#PathVariable(value="repository") String repository,
#PathVariable(value="methodName") String methodName,
ModelMap model,
HttpServletRequest request,
HttpServletResponse response) {
// handling the request ...
}
}
Here is what I can see in the request properties:
getRequestURI=/application1/api/persons/search/findByName
getRequestedSessionId=null
getContextPath=/application1
getPathTranslated=null
getAuthType=null
getMethod=GET
getQueryString=name=Name
getServletPath=/api/persons/search/findByName
getPathInfo=null
getRemoteUser=null
I want to "transfer" this request to the application2 using Spring Integration HTTP with an <int-http:outbound-gateway>.
The message for the channel used by the outbound-gateway is originated in the #Controller in this way:
MessagingChannel messagingChannel = (MessagingChannel)appContext.getBean("requestChannelBean");
String payload = repository+"/search/"+methodName;
Message<String> message = MessageBuilder.withPayload(payload).build();
MessageChannel requestChannel = messagingChannel.getRequestChannel();
MessagingTemplate messagingTemplate = new MessagingTemplate();
Message<?> response = messagingTemplate.sendAndReceive(requestChannel, message);
This is the <int-http:outbound-gateway> configuration:
<int-http:outbound-gateway id="gateway" rest-template="restTemplate"
url="http://localhost:8080/application2/api/service/{pathToCall}"
http-method="POST" header-mapper="headerMapper" extract-request-payload="true"
expected-response-type="java.lang.String">
<int-http:uri-variable name="pathToCall" expression="payload"/>
</int-http:outbound-gateway>
This gateway produces an HTTP POST request towards the url:
http://localhost:8080/application2/api/service/persons/search/findByName
But in this request I lose the original QueryString received by the application1.
I have tried to add the queryString directly to the payload as follows:
String queryString = "";
if (request.getQueryString()!=null)
queryString = request.getQueryString();
String payload = repository+"/search/"+methodName+"?"+queryString;
But this doesn't work: the produced url is:
http://localhost:8080/application2/api/service/persons/search/findByName%3Fname=Name
The "?" symbol is replaced by "%3F", so the called method is "/service/persons/search/findByName%3Fname=Name", instead of "/service/persons/search/findByName"
I suppose that it depends on the http-method="POST"; I want to use the POST method in any case, because I want to use this "service" for general requests.
So what I have to do in order to transfer the queryString of the original request to the other side in the simplest way as possible?
Thanks in advance.

I have found the answer.
The encoding of the "?" into "%3F" was made by the <int-http:outbound-gateway> beacuse the encode-uri="false" attribute was missing.
The outbound gateway encodes the URI by default, because the encode-uri attribute is setted to true by default.

Related

Supporting application/json and application/x-www-form-urlencoded simultaneously from Spring's rest controller

Am writing a REST endpoint which needs to support both application/x-www-form-urlencoded and application/json as request body simultaneously. I have made below configuration,
#RequestMapping(method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE }, consumes = {
MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.APPLICATION_JSON_VALUE }, path = Constants.ACCESS_TOKEN_V1_ENDPOINT)
public OAuth2Authorization createAccessTokenPost(
#RequestBody(required = false) MultiValueMap<String, String> paramMap) { ..
While it supports application/x-www-form-urlencoded or application/json individually (when I comment out one content type from consumes = {}), but it does not support both simultaneously. Any ideas ?
So RestControllers by default can handle application/json fairly easily and can create a request pojo from a #RequestBody annotated parameter, while application/x-www-form-urlencoded takes a little more work. A solution could be creating an extra RestController method that has the same mapping endpoint to handle the different kinds of requests that come in (application/json, application/x-www-form-urlencoded, etc). This is because application/x-www-form-urlencoded endpoints need to use the #RequestParam instead of the #RequestBody annotation (for application/json).
For instance if I wanted to host a POST endpoint for /emp that takes either application/json or application/x-www-form-urlencoded as Content-Types and uses a service to do something, I could create Overload methods like so
#Autowired
private EmpService empService;
#PostMapping(path = "/emp", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public ResponseEntity createEmp(final #RequestHeader(value = "Authorization", required = false) String authorizationHeader,
final #RequestParam Map<String, String> map) {
//After receiving a FORM URLENCODED request, change it to your desired request pojo with ObjectMapper
final ObjectMapper mapper = new ObjectMapper();
final TokenRequest tokenRequest = mapper.convertValue(map, CreateEmpRequest.class);
return empService.create(authorizationHeader, createEmpRequest);
}
#PostMapping(path = "/emp", consumes = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity createEmp(final #RequestHeader(value = "Authorization", required = false) String authorizationHeader,
final #RequestBody CreateEmpRequest createEmpRequest) {
//Receieved a JSON request, the #RequestBody Annotation can handle turning the body of the request into a request pojo without extra lines of code
return empService.create(authorizationHeader, createEmpRequest);
}
As per my findings, spring does not support content types "application/x-www-form-urlencoded", "application/json" and "application/xml" together.
Reason I figured: Spring processes JSON and XML types by parsing and injecting them into the java pojo marked with #RequestBody spring annotation. However, x-www-form-urlencoded must be injected into a MultiValueMap<> object marked with #RequestBody. Two different java types marked with #RequestBody will not be supported simultaneously, as spring may not know where to inject the payload.
A working solution:
"application/x-www-form-urlencoded" can be supported as it is in the API. That is, it can be injected into spring's MultiValueMap<> using an #RequestBody annotation.
To support JSON and XML on the same method, we can leverage servlet specification and spring's class built on top of them to extract the payload as stream.
Sample code:
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.MultiValueMap;
// usual REST service class
#Autowired
private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
#Autowired
private Jaxb2RootElementHttpMessageConverter jaxb2RootElementHttpMessageConverter;
public ResponseEntity<Object> authorizationRequestPost(HttpServletResponse response, HttpServletRequest request,#RequestBody(required = false) MultiValueMap<String, String> parameters) {
// this MultiValueMap<String,String> will contain key value pairs of "application/x-www-form-urlencoded" parameters.
// payload object to be populated
Authorization authorization = null;
HttpInputMessage inputMessage = new ServletServerHttpRequest(request) {
#Override
public InputStream getBody() throws IOException {
return request.getInputStream();
}
};
if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
authorization = (Authorization) mappingJackson2HttpMessageConverter.read(Authorization.class, inputMessage);
}
else if (request.getContentType().equals(MediaType.APPLICATION_XML_VALUE)) {
authorization = (Authorization)jaxb2RootElementHttpMessageConverter.read(Authorization.class, inputMessage);
}
else{
// extract values from MultiValueMap<String,String> and populate Authorization
}
// remaining method instructions
}
Point to note that any custom data type/markup/format can be supported using this approach. Spring's org.springframework.http.converter.HttpMessageConverter<> can be extended to write the parsing logic.
Another possible approach could be an AOP style solution which would execute the same logic: parse payload by extracting it from HttpServlet input stream and inject into the payload object.
A third approach will be to write a filter for executing the logic.
It's not possible to handle application/json and application/x-www-form-urlencoded requests simultaneously with a single Spring controller method.
Spring get application/x-www-form-urlencoded data by ServletRequest.getParameter(java.lang.String), the document said:
For HTTP servlets, parameters are contained in the query string or posted form data.
If the parameter data was sent in the request body, such as occurs with an HTTP POST request, then reading the body directly via getInputStream() or getReader() can interfere with the execution of this method.
So, if your method parameter is annotated with #RequestBody, Spring will read request body and parse it to the method parameter object. But application/x-www-form-urlencoded leads Spring to populate the parameter object by invoking ServletRequest.getParameter(java.lang.String).
Just to make it, the above answer doesn't work as even if you do not annotate MultiValueMap with #RequestBody it would always check for contentType==MediaType.APPLICATION_FORM_URLENCODED_VALUE which again in rest of the cases resolves to 415 Unsupported Media Type.

Spring Integration | HTTP outbound gateway | Multipart

Service A receives a file
Service A performs business logic.
Service B exposed an HTTP inbound to receive the file and post it
to S3.
Service A calling Service B using http outbound gateway.
I am getting an error, unable to find suitable message converter when service A calls Service B using http outbound gateway.
<int:header-enricher input-channel="addHeader" output-channel="s3publishWithHeader">`
<int:header name="Content-Type" value="multipart/form-data" overwrite="true"/> </int:header-enricher>`
<util:list id="converters">
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
<bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
</util:list>`
<http:outbound-gateway request-channel="s3publishWithHeader"
http-method="POST" url="http://localhost:8090/com/api/upload"
extract-request-payload="true" message-converters="converters"
></http:outbound-gateway>
Version of Spring Integration: 4.3.12
When using a Resource multipart it seems crucial that this resource has a file name, otherwise I found that the receiving server complains that the part is not present.
The MultiValueMap which will become the multipart request body can contain HttpEntity parts where you can specify the parts more precisely, e.g. you can specify a file name.
Assuming you have two parts, request and file, and file is an InputStreamResource (which never has a file name), you must define the filename by means of the Content-Disposition header. Use the resulting map as outgoing message payload, like so:
MultiValueMap payload = new LinkedMultiValueMap();
// some json part named "request"
HttpHeaders requestPartHeaders = new HttpHeaders();
requestPartHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<ConversionRequest> requestEntity =
new HttpEntity<>(requestBean, requestPartHeaders);
// InputStreamResource part named "file"
HttpHeaders fileRequestHeaders = new HttpHeaders();
fileRequestHeaders.setContentDisposition(ContentDisposition.builder("form-data")
.name("file") // name of the part in the map
.filename("my-file-name.pdf") // <-- mandatory file name
.build());
fileRequestHeaders.setContentType(MediaType.parseMediaType(fileMimeType));
HttpEntity<InputStreamResource> fileEntity =
new HttpEntity<>(new InputStreamResource(fileOriginalStream),
fileRequestHeaders);
payload.add("request", requestEntity);
payload.add("file", fileEntity);
You don't need to specify that Content-Type header and you don't need to configure custom converters.
Only what you need is a payload as Map<String, Object>:
else if (content instanceof Map) {
// We need to check separately for MULTIPART as well as URLENCODED simply because
// MultiValueMap<Object, Object> is actually valid content for serialization
if (this.isFormData((Map<Object, ?>) content)) {
if (this.isMultipart((Map<String, ?>) content)) {
contentType = MediaType.MULTIPART_FORM_DATA;
}
else {
contentType = MediaType.APPLICATION_FORM_URLENCODED;
}
}
}
Nonetheless you have to show the StackTrace for error because I think there is something with response parsing.

Spring boot/Spring MVC - How to forward a response from another request

I need a rest end point whose response is HTML. But instead of a view defined in my project, i would like to forward the HTML response from another request made inside that rest end point.
For example, my rest end point makes a http request to an internal service and returns the HTML returned from that service? Is it possible? Any thoughts?
Here is a code example
#RequestMapping("/test")
public String testMe(Model model, #RequestParam("param1") String param1, #RequestParam("param2") String param2)
{
//Make a Http call to an internal service and return the response from that call
return "<RESPONSE_FROM_THAT_CALL>";
}
I would like to return the HTML response from the internal service
You can use a RestTemplate to fetch the result from the other service and just return it as a String:
#Controller
public class MyController {
private RestTemplate restTemplate = new RestTemplate();
#ResponseBody
#RequestMapping("/test")
public String testMe(Model model, #RequestParam("param1") String param1, #RequestParam("param2") String param2) {
URI uri = UriComponentsBuilder.fromHttpUrl("http://www.example.com");
.queryParam("param1", param1)
.queryParam("param2", param2)
.build()
.toUri());
return restTemplate.getForObject(uri, String.class);
}
}
If you'll have more endpoints that you wanna proxy to another service, you should consider using e.g. Zuul as a micro proxy. See e.g. this blog post explaining how you can easily create such a proxy.

Spring-ws SoapHeader fields access in endpoint method

We are developing a contract-first WebService using spring-ws 2.2.0. We are trying to manage the authentication using a custom tag, named AuthToken, located in the SoapHeader.
The AuthToken has the following structure:
<authToken>
<username>USERNAME</xa:username>
<password>PASSWORD</xa:password>
</authToken>
We are able to generate a WSDL schema containing the specified custom authentication tag inside the SoapHeader.
The problem is that when the client performs the call towards our server we are not able to unmarshal the AuthToken tag (located in the SoapHeader) in our Ws Endpoint implementation.
Using the #RequestPayload annotation in the binding method signature (handleMethodRequest as specified in the example below), we are able to access the unmarshalled payload content (located in the SoapBody).
We tried to make the same thing with the SoapHeader content without success.
In the following code examples we show you what we would like to obtain:
1
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, #SoapHeader(value = "authToken") AuthToken authToken) { }
2
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, AuthToken authToken) { }
3
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, org.springframework.ws.soap.SoapHeader header) { }
4
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, MessageContext messageContext) { }
In case 1, 2 we obtain the following error:
No adapter for endpoint [MethodResponse EndpointImplementation.handleMethodRequest(MethodRequest, AuthToken) throws java.lang.Exception]: Is your endpoint annotated with #Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?
In case 3, 4 we have no errors but we are not able to handle SoapHeader or MessageContext (respectively in case 3 and 4) to reach our purposes, accessing the AuthToken to retrieve username and password sub element.
Looking for a solution in the web we found that many people having the same problem uses Spring Interceptors to handle the authentication.
Following the "Interceptors-way" we should access the AuthToken inside the interceptor. Unfortunately we need to use AuthToken field inside handleMethodRequest method for other purposes, for example loading user specific data, not accessible outside handleMethodRequest.
Therefore we cannot follow this way because we need to refer user specific data inside the handleMethodRequest method.
Does anyone know how can we solve the problem? Thanks in advance.
For that use case, the only supported combination of annotation and parameter type is #SoapHeader and SoapHeaderElement. Spring-WS currently doesn't support unmarshalling headers.
A hacky way of getting the value from interceptor to the handleMethodRequest is using a static ThreadLocal instance. Since the same thread that invokes the interceptor also invokes the handleMethodRequest you can use
ThreadLocal.set(AuthToken); // in interceptor.
ThreadLocal.get();// in handler and then clear it after use.
Also, I noticed that #SoapHeader(value = "{authToken") in your example does not have } is that a typo here or in your code?

Spring 3.2 REST API add cookie to the response outside controller

I'm using Spring 3.2.4 and Spring Security 3.2.3 to handle RESTful API call to "get security token" request that returns the token (which would be used to secure subsequent requests to the service). This is a POST request which has a body with username and password and is processed in the controller:
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public SessionTokenResponse getSessionToken(#RequestBody Credentials credentials, ModelAndView interceptorModel) throws AccessException {
final String token = webGate.getSessionTokenForUser(credentials.getUsername(), credentials.getPassword());
LOGGER.debug("Logged in user : " + credentials.getUsername());
interceptorModel.addObject(SessionConstants.INTERCEPTOR_MODEL_TOKEN_KEY, token); // Used by post-processing in interceptors, e.g. add Cookie
return new SessionTokenResponse(ResponseMessages.SUCCESS, token);
}
After the controller has successfully finished processing the request I would like to add a cookie with the token to the response.
I tried HandlerInterceptorAdapter implementation, but I cannot find the way to the the 'token' from the response or ModelAndView:
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView interceptorModel) throws Exception {
final String token = (String) interceptorModel.getModel().get(SessionConstants.INTERCEPTOR_MODEL_TOKEN_KEY);
if (token != null) {
final Cookie obsso = new Cookie(cookieName, token);
obsso.setPath(cookiePathUri);
obsso.setDomain(cookieDomain);
obsso.setMaxAge(cookieMaxAge);
response.addCookie(obsso);
}
}
The interceptorModel is null .
It seems that Spring MVC doesn't provide it to the postHandle since the #ResponseBody has been already resolved and there is no need for the ModelAndView anymore (this is just my assumption based on the debugging).
What is the correct way of achieving that (add cookie to the response) outside the controller in the interceptor or maybe listener?
To retrieve the token you can use the request object
request.setAttribute(SessionConstants.INTERCEPTOR_MODEL_TOKEN_KEY, token);
and then in the postHandle
String token = ( String ) request.getAttribute(SessionConstants.INTERCEPTOR_MODEL_TOKEN_KEY);
However I don't think you can add a cookie to the response object in postHandle as the response is already committed.
Perhaps you could store the token information on the servlet context instead.
In your controller, add the token information to the servlet context.
Then implement preHandle, so that every api call can check if token for that user exists on servlet context, if so you can add cookie to the response.

Resources