I'm trying to pass a url in to a Spring MVC controller method in a #PathVariable but am getting a 400 http response code, and the request is rejected before it reaches the controller method.
My request is being issued as:
curl 'https://127.0.0.1:8443//myapi/page/http%3A%2F%2Fwww.example.co.uk%2Ffolder%2Fpage_n0/info' -k -w "\nResponse code: %{http_code}\n"
The controller method, and UTF-8 filter, is:
#Bean
public FilterRegistrationBean filterRegistrationBean()
{
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceEncoding(true);
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(filter);
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
#RequestMapping(value = {"/myapi/page/{url}/info"}, method = RequestMethod.GET)
public ResponseEntity<?> test(HttpServletRequest request, HttpServletResponse response, #PathVariable("url") String webPage)
{
ResponseEntity<?> results = new ResponseEntity<Map<String, Object>>(HttpStatus.OK);
// Do something here
return results;
}
I am using Tomcat 8.5.5, and nothing is present in the log files (logging has been set at DEBUG level) except for the following entry in localhost_access_log.2016-10-04.txt:
127.0.0.1 - - [04/Oct/2016:09:04:24 +0100] "GET //myapi/page/http%/info HTTP/1.1" 400 -
In my test code, the url being passed into is being encoded using URLEncoder.encode(), so should be being encoded correctly.
In the remote debugger, I can see that the CharacterEncodingFilter registration code above is being entered so the filter should be being registered.
I have also addedURIEncoding="UTF-8" to each of the Connectors in the server.xml file in $CATALINA_HOME/conf.
I am going round in circles with this and keep on thinking there's something obvious I'm missing. I've never had any issues using a #PathVariable before so I presume I'm encountering some sort of encoding issue, probably relating to the % sign.
I'd be grateful for any help with this!
Update:
I think the issue is that Spring, inside AbstractHandlerMethodMapping and UriUtils, is decoding the entire url, including the #PathVariable portion. It then cannot find a request mapping for the decoded url which is unsurprising since the decoded url includes the decoded url in the #PathVariable. I need to find some way of telling Spring not to decode the application/x-www.form-urlencoded portion of the url.
Any ideas?
Related
The RestTemplate javadoc recommends migrating to WebClient, even for traditional (non-reactive) applications. So it's logical that I would also like to use WebTestClient in my controller tests, to use a consistent API.
This works fine for GET requests for example. But when I POST with a body, the body data does not arrive at the controller. Why not?
I can test my controllers like this:
WebTestClient webTestClient = WebTestClient.bindToController(TokenController()).build
webTestClient.post().uri("/post2")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.bodyValue("grant_type=authorization_code")
.exchange()
.expectStatus().isOk()
The controller code:
#PostMapping(
path = ["/post2"],
consumes = [APPLICATION_FORM_URLENCODED_VALUE],
produces = [APPLICATION_JSON_VALUE])
ResponseEntity<Any> post2(#RequestParam(GRANT_TYPE) String grantType) {
System.out.println(grantType)
return ResponseEntity.ok("{\"a\": 33}")
}
In this example, grantType is null. If I start the application and send a standard HTTP request, then grantType contains the value "authorization_code" as expected.
(I manually translated the code from Kotlin to Java for this question, there may be syntax errors)
You are not actually parsing the body at all in your controller, you are only looking for a URL parameter.
#RequestParam("grant_type") will map a parameter like /someUrl?grant_type=something
But you are sending the data in the body of the request, so you should use
#RequestBody String grantType in your controller instead. Note that the input will be the whole String "grant_type=authorization_code".
When I'm trying to consume a Spring MVC method using FF RESTClient, I get the following error:
The server refused this request because the request entity is in a
format not supported by the requested resource for the requested
method.
This is the method declaration:
#RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseWrapper login(#RequestParam("email") #NotNull #Size(min=3, max=50) final String email,
#RequestParam("password") #NotNull #Size(min=8, max=50) final String password,
final HttpSession session) {
I entered the correct URL, method POST, and in the "Body" section I wrote:
email=admin&password=87654321
I also added the "Content-Type" header with value of "application/x-www-form-urlencoded" as suggested here: Firefox Add-on RESTclient - How to input POST parameters?
The Java code is correct and I can't change it. I have to fix the problem in Firefox RESTClient.
Any help will be profoundly appreciated!
Edit:
I changed a few things and then changed it back and now in works well, don't know why. Anyway I'd be glad if the moderators could delete this question.
I feel like this should be easy. I have an app where all I am trying to do is have a form page (index.jsp) that calls a servlet (CheckInfo.java) which sets a new header (myHeader) and redirects the user to another page (redirect.jsp). All of these files are on the same server. The index.jsp is sending the request just fine and CheckInfo is processing and redirecting, but myHeader is not showing up on redirect.jsp. I've read several posts talking about response.sendRedirect sends a 302 which doesn't pass headers and that I should use RequestDispatcher, but nothing seems to work. Is there no way to send headers from a servlet to a jsp?
Here is the servlet code:
response.setHeader("myHeader", "hey there");
response.sendRedirect("redirect.jsp");
I have also tried this:
response.setHeader("myHeader", "hey there");
RequestDispatcher view = request.getRequestDispatcher("redirect.jsp");
view.forward(request, response);
And I have this in redirect.jsp:
System.out.println(request.getHeader("myHeader"));
This does not print anything.
If the answer to my question is no... then I would settle for a way to set the header once I got back to the jsp. My reverse proxy is looking for a specific header to determine whether or not to perform an action. Obviously I tried response.addHeader() on redirect.jsp, but the page has already loaded at that point so that just made me feel dumb.
response.setHeader("myHeader", "hey there");
response.sendRedirect("redirect.jsp");
You are adding it as response header and it is 302 response. Browser on seeing a 302 response will just look for Location header and fire a new request to this location. Custom headers in the response are untouched whereas you are expecting these custom response headers to be included in the request (to new redirect location) which is not being sent.
Solution:-
1. you can use request dispatcher and forward the request instead of external redirect. And you need to use request attributes here.
2. you can call submit form using an ajax request may be jquery like and handle the response manually(for 302 response) but would not suggest you to use this approach as it is not a cleaner and intuitive approach. Just mentioning so that you know there are other ways to achieve this.
The problem is that the redirect() method of the response initiates a new request altogether, thereby loosing the attributes that were set before redirecting. Luckily there is a fluent way of solving the problem still. See below
response.setHeader("myHeader", "hey there");
request.getRequestDispatcher("redirect.jsp").forward(request, response);
Then in your destination you can do response.getHeaders("myHeader")
I have tested the code.
I hope it's clear that in case of asking the client to redirect to another URL - the browser shall not honor the cookies.
However, the 2nd method - where server forwards the request is feasible. The main mistake appears to be in mutating the response while we are supposed to change the request.
Then again, one cannot directly mutate a HttpServletRequest object. Here is one way to do so:
HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(request){
public String getHeader(String name) {
String value = super.getHeader(name);
if(Strings.isNullOrEmpty(value)) {
...
value = myNewHeader;
}
return value;
}
public Enumeration<String> getHeaders(String name) {
List<String> values = Collections.list(super.getHeaders(name));
if(values.size()==0) {
...
values.add(myNewHeader);
}
return Collections.enumeration(values);
}
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
names.add(myNewHeaderName);
...
return Collections.enumeration(names);
}
}
Followed by:
RequestDispatcher view = request.getRequestDispatcher("redirect.jsp");
// OR (If you can get servletContext)
RequestDispatcher view = servletContext.getRequestDispatcher("redirect.jsp");
view.forward(requestWrapper, response);
Reference:
https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequestWrapper.html
For the headers case - getHeader(), getHeaders() and getHeaderNames() fn in the reqWrapper obj need Overriding.
Similarly you can override cookies and params.
See also: Modify request parameter with servlet filter
NOTE: It might not be possible to forward a req to an endpoint which expects a different MIME type.
A client side redirect creates a new HTTP request/response pair.
This link may help you more on debugging perspective -
Sending Custom headers
I send following http request:
http://localhost:8081/member/createCompany/getSmallThumbnail/
On server side I hit into controller method:
#RequestMapping("/error")
public String error(Model model, HttpServletRequest request){
if(request.getRequestURI().contains("thumbnail")){
System.out.println("thumbnail accepted");
}
request.toString();
model.addAttribute("message", "page not found");
return "errorPage";
}
At this method I want to know url with which the request arrived.
If in debug I stop inside this method I see information needed for me:
But I cannot find method in request which will return this.
Please help to return url which I want.
P.S.
Actually I have not mapped controller in my spring mvc application(url is broken) for http://localhost:8081/member/createCompany/getSmallThumbnail/. This url("/error") configured in web.xml as error page.
Your request got redispatched to /error (presumably for error processing).
If this framework follows the normal Servlet error dispatching behavior, then your original request can be found in the HttpServletRequest.getAttributes() under the various javax.servlet.RequestDispatcher.ERROR_* keys.
ERROR_EXCEPTION - The exception object
ERROR_EXCEPTION_TYPE - The type of exception object
ERROR_MESSAGE - the exception message
ERROR_REQUEST_URI - the original request uri that caused the error dispatch
ERROR_SERVLET_NAME - the name of the servlet that caused the error
ERROR_STATUS_CODE - the response status code determined for this error dispatch
What you want is
String originalUri = (String) request.getAttribute(
RequestDispatcher.ERROR_REQUEST_URI)
I wrote a spring-mvc controller method to get an array of values in the request parameter.The method looks like below
/**
Trying to get the value for request param foo which passes multiple values
**/
#RequestMapping(method=RequestMethod.GET)
public void performActionXX(HttpServletRequest request,
HttpServletResponse response,
#RequestParam("foo") String[] foo) {
......
......
}
The above method works fine when the request url is in below format
...?foo=1234&foo=0987&foo=5674.
However when the request url is in below format the server returns 400 error
...?foo[0]=1234&foo[1]=0987&foo[2]=5674
Any idea how to fix the method to cater to the second format request url?
This is not possible with #RequestParam. What you can do is implement and register your own HandlerMethodArgumentResolver to perform to resolve request parameters like
...?foo[0]=1234&foo[1]=0987&foo[2]=5674
into an array. You can always checkout the code of RequestParamMethodArgumentResolver to see how Spring does it.
Note that I recommend you change how the client creates the URL.
The server is supposed to define an API and the client is meant to follow it, that's why we have the 400 Bad Request status code.
I resolved this issue using the request.getParameterMap().Below is code.
Map<String,String> parameterMap= request.getParameterMap();
for(String key :parameterMap.keySet()){
if(key.startsWith("nameEntry")){
nameEntryLst.add(request.getParameter(key));
}
}