How to pass new header to sendRedirect - servlets

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

Related

WebServlet url-pattern ending by /jsfinspector

I'd like to map a servlet for every url ending with "/jsfinspector". For example:
http://localhost/myapp/pages/somewhere/jsfinspector
http://localhost/myapp/jsfinspector
Is it possible to do that? In a very simple way, without declaring all possible url patterns in web.xml?
The Servlet API doesn't support that.
Your best bet is creating a #WebFilter("/*") which forwards to #WebServlet("/jsfinspector") when the URL matches, as shown below:
if (request.getRequestURI().endsWith("/jsfinspector")) {
request.getRequestDispatcher("/jsfinspector").forward(request, response);
} else {
chain.doFilter(request, response);
}
You can if necessary extract the original request URI in servlet as below:
String originalRequestURI = (String) request.getAttribute(RequestDispachter.FORWARD_REQUEST_URI);
You could think about creating a filter to intercept every request and eventually redirect the flow. https://docs.oracle.com/javaee/6/tutorial/doc/bnagb.html

jetty server log request body

RequestLogHandler requestLogHandler = new RequestLogHandler();
Slf4jRequestLog requestLog = new CustomSlf4jRequestLog();
requestLogHandler.setRequestLog(requestLog);
Slf4jRequestLog is only logging request method, url and date, and response status code and bytes written.
I definitely want to log body for my PUT/POST requests.
I derived CustomSlf4jRequestLog from Slf4jRequestLog and I tried:
public void log(Request request, Response response) {
StringBuilder sb = new StringBuilder("RequestBody: ");
try {
LOG.info("BODY SIZE: " + request.getContentLength());
BufferedReader in = new BufferedReader(new InputStreamReader(request.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
sb.append(line);
}
LOG.info(sb.toString());
Unfortunately no body is printed out as it is already processed by handler?
Is it possible to get body of request here?
(I really care about body because I have JsonProvider and I want to see a whole body of request when Json fails to parse data) Or when my app fails I want to see what caused that without adding logging for each input request.
Servlet spec getting in your way here.
The actual servlet that is processing the request has already read the request body, rending further reads from request.getInputStream() invalid (you are at EOF)
If you want to capture the request body, you'll need to create a custom Servlet Filter, and then wrap the HttpServletRequest, overriding the getInputStream() AND getReader() methods with your own implementations that make a copy of the data that was read.
Then its up to you to determine what you want to do with that copy of the request body content.
or
You can just use a network capture utility like Wireshark to see what the request body was. Even if you use HTTPS, you can configure Wireshark with your server certificate to inspect encrypted conversation.
The logger is calling getInputStream() on the same request again. You are not allowed to read the same data twice. Yo should create a ServletRequestWrapper to make a copy of the body of the request.

Check logged user with normal and ajax request

I use interceptor to check if a user is logged in every controller call like this :
public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) {
if(request.getSession().getAttribute("user") == null) {
response.sendRedirect("redirect:/login?next="+
URLEncoder.encode(
request.getRequestURL().toString() + "" +
(request.getQueryString() != null ? "?" + request.getQueryString() : "")
,"utf-8");
return false;
}
return true;
}
It work fine for normal request but for ajax request i can't make a response.sendRedirect(..).
How to know if it's a ajax or normal request ?
How can i do it like if i got a ajax error ?
$.ajax({
.....
success : function(data) { ...... },
error : function(){
alert("login error"); // or
document.location = '/path/login' // or something else
}
});
There a other way to handle it rather than using interceptor ?
1. How to know if it's a ajax or normal request ?
You can check inside your interceptor for the existence of the X-Requested-With header. This header is always added to the ajax request by the jQuery library (to my knowing almost all major js libraries add it as well) with the purpose of preventing the Cross-Site request forgery. To figure out if the request is ajax, you can write your preHandle method like
public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) {
String requestedWith = request.getHeader("X-Requested-With");
Boolean isAjax = requestedWith != null ? "XMLHttpRequest".equals(requestedWith) : false;
...
}
2. How can i do it like if i got a ajax error ?
As you've already noticed, ajax request don't recognize server side redirects, as the intention of the server side redirects is to be transparent to the client. In the case of an ajax request, don't do redirect rather set some status code to the response e.g. response.setStatus(respCode) or add a custom header e.g. response.setHeader("Location", "/path/login"), and read it through in the jQuery's complete method which is a callback that follows after either success or error, e.g.
$.ajax({
//...
complete: function(xhr, textStatus) {
console.log(xhr.status);
console.log(xhr.getResponseHeader('Location'));
// do something e.g. redirect
}
});
3. There a other way to handle it rather than using interceptor ?
Definitely. Checkout Spring Security. Its a framework, and adds a bit to the learning curve, but its well worth it. It will add much more than a custom solution, e.g. you'll get authorization mechanism on top of the authentication. When your application matures, you'll notice that the straigthforward implementation that you're on to now, has quite a few security flaws that are not hard to exploit e.g. session fixation, where spring security can easily protect you. There's plenty of examples online, and you'll get better support here on the SO in comparison to any custom solution. You can unit test it, an asset I personally value very much
You could simply:
Refuse ajax requests before the user is properly logged in
once the user logs in, set a security token in the session or somewhere
pass that token in the ajax request and use that token to validate on the server side prehandle
in your case you would check the existence of the token before running into the code
Also, the preHandle does not have to apply to every routes, you could also have different routes each with different authorisation, prehandle, code.

Spring MVC binding request parameters

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));
}
}

Adding Cookie after FilterChain.doFilter() - HttpServletResponseWrapper to ignore flushing?

I'd like to add a cookie to an HttpServletResponse after its content (usually HTML) has been rendered.
As mentioned here (http://osdir.com/ml/java.jasig.uportal/2005-10/msg00276.html),
and here (Adding a cookie to the response in Java after the header has been flushed?), this is possible by buffering up the response so it won't get flushed and sent to client (since after headers are sent to client, response is committed, and no more headers, namely cookie headers, can be sent to client).
Assuming this is the goal at hand: I gather that a possible way to accomplish this is by use of an HttpServletResponseWrapper, I can override its flushBuffer() method and prevent the actual flushing of headers\content to client:
public class BufferedHttpServletResponse extends HttpServletResponseWrapper {
public BufferedHttpServletResponse(HttpServletResponse response) {
super(response);
}
public void flushBuffer() {
}
}
and apply this buffered response in a Filter, for the rest of the filter chain to use:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
BufferedHttpServletResponse bufferedResponse = new BufferedHttpServletResponse(response);
chain.doFilter(request, bufferedResponse);
// ... create Cookie and add it to response ... response.addCookie(newCookie)
// response.flushBuffer() ?
}
Question: is the above okay, or is it completely off? Will the response's buffer just keep getting filled with content\headers (and possibly getting re-sized) until after my doFilter() exits, to then be flushed and sent to client by other filters or by the servlet container (Tomcat)? Should I flush the response myself? ... mentioned here (Should one call .close() on HttpServletResponse.getOutputStream()/.getWriter()?) that stream should not be closed, but should response be flushed in this case?
Not necessarily assuming this is the goal at hand: My general problem is that I need to set a cookie for a client browser, but the value of the cookie depends on stuff that happens while rendering the response's content (stuff that happens in a Struts 1.0 Action, and after that in a Velocity template) - I know what cookie value I want to set only after going through the entire (Servlet -> Rendering of HTML) flow.
Enlighten me please. Thanks.
Looks like nobody is ready to take this one, I will give an attempt to provide at least my view point.
Before I get to your question First, Lets look at the main tasks of the servlet filter.
Query the request and act accordingly.
Block the request-and-response pair from passing any further.
Modify the request headers and data. You do this by providing a
customized version of the request.
Modify the response headers and data. You do this by providing a
customized version of the response.
Source: http://docs.oracle.com/javaee/6/tutorial/doc/bnagb.html
Coming back to your question now, You could modify the response headers by customizing the response a.k.a ResponseWrapper.
Looking at your code, what you're trying to do is modifying the original response instead of modifying the custom response and which (adding cookie to original response) is not OK.
So the proper way of doing it is to add the cookie in your custom something like the below sample.
private class CustomResponseWrapper extends HttpServletResponseWrapper {
private Collection<Cookie> myCookies = new ArrayList<Cookie>();
public CustomResponseWrapper(HttpServletResponse response) {
super(response);
}
#Override
public void addCookie(Cookie cookie) {
super.addCookie(cookie);
myCookies.add(cookie);
}
}

Resources