Java Servlet can be used to apply different variants of URL directs as given by HTTP specifications. In this tutorial we will understand the usage of different related status codes and also how server and client browser participate in URL redirection.
Prerequisite
Basic understanding of redirection and relevant status codes.
Temporary URL Redirects
Status codes 302 (Found), 303 (See other) and 307 (Temporary redirect) can be used for temporary redirects. In the following example we will use Servlet API to set these status codes and the 'Location' header as required by the W3C Specifications.
302 redirect with GET
Redirect Servlet
@WebServlet(urlPatterns = "/test")
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("----- Get Request for /test ---------");
resp.setStatus(HttpServletResponse.SC_FOUND);//302
resp.setHeader("Location", "http://localhost:8080/example/test2");
// resp.sendRedirect("http://localhost:8080/example/test2");
}
.............
}
Servlet provides a supported method HttpServletResponse#setRedirect() which internally does the same thing done by resp.setStatus(302) and resp.setHeader(location) .
Servlet handling the redirected request
@WebServlet(urlPatterns = "/test2")
public class HandlerServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("----- Request for /test2 ---------");
resp.setContentType("text/html");
PrintWriter writer = resp.getWriter();
writer.write("<h4>Test 2 page</h4>");
}
.............
}
Running embedded Jetty
mvn jetty:run
Output
Entering uri /test will redirect to /test2
Let's look at the requests/responses in chrome developer tool under the Network tab:
In above example we used GET method with the original request. Let's see the behavior if we use POST method
302 redirect with POST
We are going to use a simple HTML form for POST submission:
src/main/webapp/myForm.html<form action="/example/test" method="post">
Enter name: <input type="text" name="name" value="Joe"><br>
<input type="submit" value="Submit">
</form>
Above post request will be handled by our HandlerServlet at '/example/test'. Let's add doPost() method:
@WebServlet(urlPatterns = "/test")
public class RedirectServlet extends HttpServlet {
.............
@Override
protected void doPost(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("-----Post request for /test ---------");
System.out.println("Post param name: " + req.getParameter("name"));
//resp.setStatus(302);
// resp.setHeader("Location", "http://localhost:8080/example/test2");
resp.sendRedirect("http://localhost:8080/example/test2");
}
}
This time we are using resp.sendRedirect(location) which also sends 302 like above example.
In our HandlerServlet, we have included doPost() as well (in case if browser sends POST request in the second round):
@WebServlet(urlPatterns = "/test2")
public class HandlerServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("----- Request for /test2 ---------");
resp.setContentType("text/html");
PrintWriter writer = resp.getWriter();
writer.write("<h4>Test 2 page</h4>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
}
.............
}
Output
On submitting the form:
Chrome browser changed POST to GET in the second round. The is the same behavior per 303 specification. Also, there's no direct way to pass post data to the redirected location as GET method doesn't support that. In RedirectServlet#doPost, we can append the post params as query string like this:
resp.sendRedirect("http://localhost:8080/example/test?name="+req.getParameter("name"));
Or we can internally use HttpSession to save post data information in RedirectServlet#doPost and can retrieve them back in HandlerServlet#doGet. Most likely, we would like to handle form submission data in RedirectServlet#doPost (e.g. persisting in database) before redirecting. The redirected page could show a very general information like 'Succeeded' or 'Failed'. This technique is used to avoid double form submission as well.
Regarding browser's changing POST to GET during 302 redirect, it is not an ideal outcome per W3C specifications but fixing that would break a lot of existing server code, that's why browsers are still sticking to the old behavior. Also Servlet future specification will probably send 303 status with resp.sendRedirect(..) instead of 302.
303 will behave exactly like our above example, so we are not going to show an example on that here. Let's try 307 now.
307 Redirect with POST
The difference between 307 and 302/303 is: 307 doesn't change HTTP method in the second round, let's see that with an example
HTML Form
src/main/webapp/myForm2.html<form action="/example/test3" method="post">
Enter name: <input type="text" name="name" value="Joe"><br>
<input type="submit" value="Submit">
</form>
Creating servlets
Let's create new Servlets for redirecting and handing redirected request:
@WebServlet(urlPatterns = "/test3")
public class RedirectServlet2 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("----- Request for /test3 ---------");
System.out.println(req.getParameter("name"));
resp.setStatus(307);
resp.setHeader("Location", "http://localhost:8080/example/test4");
}
}
@WebServlet(urlPatterns = "/test4")
public class HandlerServlet2 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("----- Request for /test4 ---------");
resp.setContentType("text/html");
PrintWriter writer = resp.getWriter();
writer.write("<h4>Test 4 page</h4>");
writer.write("<p>Result of post</p>");
writer.write(req.getParameter("name"));
}
.............
}
Output
On submitting the form:
Note that browser did not change POST to GET this time, also the post data is accessible to the final redirected servlet, without we have to populate it in the session or attaching as query parameters. Per specifications, the browser is required to re-post data in the second request.
Permanent URL redirect
301 (Moved permanently) or 308 (Permanent redirect) can be used to redirect a URL permanently. The difference between the two is with 301 client may change the HTTP method but with 308 it cannot be changed. We are going to demonstrate only 308. In the following example first we will see 308 with GET and then with POST.
308 Redirect with GET
@WebServlet(urlPatterns = "/test5")
public class RedirectServlet3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("----- Request for /test5 ---------");
resp.setStatus(308);
resp.setHeader("Location", "http://localhost:8080/example/test6");
}
.............
}
@WebServlet(urlPatterns = "/test6")
public class HandlerServlet3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("----- Request for /test6 ---------");
resp.setContentType("text/html");
PrintWriter writer = resp.getWriter();
writer.write("<h4>Test 6 page</h4>");
}
.............
}
Output
On entering /test5 in the address bar will redirect to /test6:
For subsequent request of /test5, the browser will retrieve the redirected URL (/test6) from the cache and will straight request the final URL instead of two round trips:
This behavior will persist until we clear the browser cache.
308 Redirect with POST
In majority of browsers, including chrome, 301 redirect request is changed from POST to GET (POST data is lost in GET step). 308 was introduced to only allow same HTTP methods in the both requests. Let's see an example on 308 redirect with Post:
src/main/webapp/myForm3.html<form action="/example/test5" method="post">
Enter name: <input type="text" name="name" value="Joe"><br>
<input type="submit" value="Submit">
</form>
@WebServlet(urlPatterns = "/test5")
public class RedirectServlet3 extends HttpServlet {
.............
@Override
protected void doPost(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("----- Post request for /test5 ---------");
System.out.println(req.getParameter("name"));
resp.setStatus(308);
resp.setHeader("Location", "http://localhost:8080/example/test6");
}
}
@WebServlet(urlPatterns = "/test6")
public class HandlerServlet3 extends HttpServlet {
.............
@Override
protected void doPost(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("----- Post request for /test6 ---------");
resp.setContentType("text/html");
PrintWriter writer = resp.getWriter();
writer.write("<h4>Test 6 page</h4>");
writer.write("<p>Result of post</p>");
writer.write(req.getParameter("name")+"");
}
}
Output
For the subsequent /myForm3.html submission:
It seems chrome (Version 56.0.2924.87) is not caching the redirected url (/test6) for POST. Every time it is going through the same redirection flow. This behavior is same in Firefox (51.0.1) as well.
According to the specs
A 308 response is cacheable by default; i.e., unless otherwise indicated by the method definition or explicit cache controls.
According to POST definition, it is not cacheable by default:
Responses to this method are not cacheable, unless the response includes appropriate Cache-Control or Expires header fields. However, the 303 (See Other) response can be used to direct the user agent to retrieve a cacheable resource.
Example ProjectDependencies and Technologies Used: - javax.servlet-api 3.1.0 Java Servlet API
- jetty-maven-plugin 9.4.1.v20170120: Jetty maven plugins.
- JDK 1.8
- Maven 3.3.9
|