Spring MVC Controller response as a static content to cache in browser - java

I would like to my response of the Spring MVC Controller be seen as a static content in the browser. Just to fetch once and then get it from browser cache to get 304 Not Modified status code.
I have ordinary Spring MVC controller with simple method. simpleService.getVariables() is getting huge content from the database. It returns String.
#RequestMapping(value = "/jsContent.htm")
public ModelAndView jsContent(#RequestParam("ver") String version,
HttpServletRequest request, HttpServletResponse response) {
ModelAndView mav = new ModelAndView("jsContent");
mav.addObject("variables", simpleService.getVariables());
return mav;
}
Response is handling by Apache Tiles. The firstPart is ordinary JavaScript file, and the secondPart is the String added to the ModelAndView object.
<definition name="javascript" template="/WEB-INF/tiles/javascript.jsp" />
<definition name="jsContent" extends="javascript">
<put-attribute name="firstPart" expression="/js/content/static.js" />
<put-attribute name="secondPart" expression="${variables}" />
</definition>
To enable expressions in tiles I have added this tilesProperties:
This is my tiles/javascript.jsp:
<%# taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<tiles:insertAttribute name="firstPart" />
<tiles:insertAttribute name="secondPart" />
I have only achieved 304 Not Modified status code by added this Spring filter to the web.xml. However content is still fetching every time, and by Etag header comparison the status code is 304 when there is any change.
<filter>
<filter-name>etagFilter</filter-name>
<filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>etagFilter</filter-name>
<servlet-name>dispatcher</servlet-name>
</filter-mapping>
How can I get cached response of my Spring Controller? I have tried to achieve this by HTTP Headers but it wasn't working.

The ETag method will not suit your need here. The way the filter works is that it calls the controller each time a request is received, it then computes the Hash of the response, compares the hash (ETag) with the Etag sent in the request, and if they match, the filter assumes data has not changed, and sends the client 304 with no response body.
As you can see, this method does not reduce the amount of work your actual controller needs to do for a request, it only improves bandwidth utilization and provides faster response to the client (Since instead of sending the response body, we are only sending a status code).
So, for your case, you should use the Last-Modified response header and If-Modified-Since request header. To do this with Spring MVC, you can try the following method:
1. In your controller, maintain a time stamp in millis, which should be the same as the last modified time for the resource. I set this to the deployment time, with the assumption that my content is not going to be modified while the application is live. You may chose to update this time as you wish, if you want to force browsers to load a fresh copy, even if they have a older version.
Accept org.springframework.web.context.request.WebRequest as a parameter to your controller method.
In your method, do the actual processing, only if webRequest.checkNotModified(lastModifiedTime) is false.
Sample code:
public class MyController {
//Injected
private long lastModifiedTime;
public void getFullSdk(WebRequest webRequest, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (webRequest.checkNotModified(lastModifiedTime)) {
return;
} else {
// real expensive code goes here
// write to response
}
}
}
Hope this helps.

Related

How to handle a POST request with a Servlet API

I am trying to make an API with Jetty Server, and I have this simple GET request:
#GET
public String helloWorld(){
return "Hello world";
}
In order to make a POST request, I assume that one must save the input to the Jetty server. I have tried to research for quite a while, but found nothing.
I imagine something like this:
#POST
public void Save(String stringToSave) {
// Save to DB?
}
You could likely google this but let me give you a quick overview. A Servlet is a chunk of code that is normally run during an HTTP action - GET, POST, etc. It is the original technology of the JavaEE world, having been released in the late 1990's.
A simple Java servlet, using modern annotations, would look something like:
#WebServlet(name = "SampleServlet", urlPatterns = "/sampleServlet")
public class SampleServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// called when an HTTP POST is sent
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// called when an HTTP GET is sent
}
}
The important parts to note are that the class extends HttpServlet and that you have to write code to pull data out of the request and push it into the response. This isn't bad to do but it does have to be done.
JAX-RS is a newer standard, aimed simplifying the creation of REST services. It too is a chunk of code that runs during an HTTP interaction.
A simple example of this would be:
#Path("/sampleService")
public class SampleService{
#Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#POST
#Path("/v1/hello")
public Response sayHello( SomeObject someobject ) {
The code here is both simpler and a bit more complex. The use of annotations helps determine the path that the service exists on a URL (in this case /sampleService/v1/hello), the HTTP method, and the Content-Type for both the request and response. Additionally, if the SomeObject object is defined correctly, the JAX-RS framework will automatically deserialize the incoming JSON or XML payload into an object for you.
The Response object contains the HTTP response code (perhaps a teapot) and a response body. In this example, the body will be automatically serialized back to the requestor in a way that matches the Accept header of the HTTP request (i.e., JSON for an application/json Accept header and XML for application/xml).
Note that while not directly related the JAX-RS framework takes advantage of the Servlet framework. Indeed in JAX-RS you can access the HttpServletRequest and HttpServletResponse object in your methods.
Which way is "better"? In general I would recommend using JAX-RS where possible as it is the newer standard and is a bit easier to implement. However, if you do any work in the JavaEE world you're very likely to run into Servlet code so it's important to understand it too.
Note that both Servlets and JAX-RS require an application server of some sort. Jetty is one of those. Another very common one is Tomcat. The application server sets up the environment for your code and listens for incoming HTTP messages. When it gets one it looks to see if it knows how to handle the URL and routes to the appropriate place. In the servlet world the server routes solely on the URL. In the JAX-RS world the server routes on the URL and, if specified by the #Consumes annotation, the HTTP Content-Type header too.
There is much more but let's start there and see if it answers what you're after.

HttpServletRequest stored in model gets replaced

I'm working on a legacy code (Spring 2.5.x, Java 1.5), where controller passes Http Servlet Request to view like this:
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map attributes = new HashMap();
attributes.put("httpRequest", request);
return new ModelAndView("/path/to/page.jsp", attributes);
}
However what happens is that httpRequest attribute does indeed exist in the JSP Page context, but it contains current request (JSP page) and not the original (controller). The passed request is being used to extract some information from the URL. It appears that I can get around that problem by using a bunch of "javax.servlet.forward.*" attributes. This however puzzles me as it is pretty simple thing and I'd be happy to get it to work anyways or at least know exactly why it doesn't work now. Anybody has any ideas?
To begin with, it is a bad idea to store the request as attribute of request. If you need specific data for the current request, you can retrieve it directly using ${requestContext}, if you need to access to the URL, use ${requestContext.requestURL}. If you need to access to parts of this URL to display it in client side, it would be better to parse the url in server side (controller) by first obtaining it using StringBuffer url = request.getRequestURL(); and then setting the required data as attributes.

Getting "405 - Request method 'GET' not supported when calling" method=DELETE

I have a Spring MVC web app. In it a form with a button that's supposed to delete a resource from another resource:
<td>
<form action="/product-bases/${productBase.id}/positions/${position.id}" method="DELETE">
<input type="submit" value="delete" />
</form>
</td>
My controller:
#Controller
#RequestMapping(value = "/product-bases/{id}/positions")
public class ProductBasePositionController {
#RequestMapping(value = "/{positionId}", method = RequestMethod.DELETE)
public ModelAndView delete(#PathVariable Integer productBaseId, #PathVariable Integer positionId) {
So in theory the server should route to the controller. But alas it does not, hence the post ;)
I'm getting
HTTP Status 405 - Request method 'GET' not supported
type Status report
message Request method 'GET' not supported
description The specified HTTP method is not allowed for the requested resource (Request method 'GET' not supported).
Apache Tomcat/7.0.19
Obviously I don't have a get for /positions/id defined yet, but why should I, I want to do a delete for now..
(I'm also trying to run this from my spring-test-mvc framework on a mock servlet without any tomcat implementation in between and it gives me a 400 - bad request..
)
So what am I missing here?
Oh, just to cut some corners: post and get will work for other resources, so the rest of my setup is fine.
The booting server even tells me:
RequestMappingHandlerMapping [INFO] Mapped "{[/product-bases/{id}/positions/{positionId}],methods=[DELETE],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.web.servlet.ModelAndView our.view.controller.ProductBasePositionController.delete(java.lang.Integer,java.lang.Integer)
Anyone as confused as I am? If less so, please enlighten me!
Forms can be submitted via GET or POST only (maybe also PUT, but I doubt that is widely implemented), as form submission requires a method where data is transmitted to the server.
The DELETE method does not have a request body, so specifying it in a form action is unsupported.
Do you have the HiddenHttpMethodFilter filter in your web.xml?
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
http://static.springsource.org/spring/docs/current/spring-framework-reference/html/view.html#rest-method-conversion
The error messages indicate that the browser is actually sending a GET request rather than a DELETE request.
What you need to do is:
examine the source of the web page that the browser is on at the time, and
using the browser's web debugger, see what the request URL and method actually are.
In Microsoft Azure portal i got this error for a Java App when i turned on Authentication/Authorization. If it's the same problem in your case, revert your action.

How do I specify a query string in Tomcat's <servlet-mapping> <url-pattern>?

I am running Tomcat 5.5.4 and have a servlet running with no problems. However, I'd like to set up a mapping to only launch the servlet when a URL containing a particular query string is submitted.
Right now in web.xml I have:
<servlet-mapping>
<servlet-name>MyServer</servlet-name>
<url-pattern>/go/*</url-pattern>
</servlet-mapping>
If a browser submits http://localhost/MyServer/go?P=123 the servlet is launched and all is well. However, I'd like to only launch that servlet if the URL is exactly as just shown. Unfortunately, right now if the URL is http://localhost/MyServer/go?P=AnyDarnThing the servlet still launches. I have tried setting up the following:
<url-pattern>/go?P=123</url-pattern>
but this results in The requested resource (/MyServer/go) is not available.
I've tried numerous variations (quoting the string, ...) on the above URL pattern but I always get the above error. I notice that if I (for debugging purposes) drop the "?" as in
<url-pattern>/goP=123</url-pattern>
I no longer get the error message and the server launches (but, of course, it doesn't respond to the "query string" because it's not properly formed.) This suggest to me that the "?" is causing a problem in the mapping. I've tried replacing it with its URL special character equivalent as follows:
<url-pattern>/go%3FP=123</url-pattern>
but this gives the same result just described above when I tried dropping the "?" altogether.
I realize I can let the servlet get launched when any query string is submitted and then "ignore" the request for all but the one I care about but there is a reason I'd prefer to not have the servlet launched to begin with. So, my question is, how can I configure the servlet so that it is only launched when a specific query string is included?
Thank you.
You can't do that. The url-pattern is pretty limited.
If you want to have distinct actions taken based on a GET parameter, you can do that manually. In the doGet() method of the servlet have a simple if-clause and invoke different methods depending on the query string / get param.
You can't do that using URL patterns.
You can achive this using filters. Implement a filter which will forward to the Servlet only if the query params exists.
Here is the how the filter will look like:
public class ServletAcessFilter implements Filter
{
public void init(FilterConfig filterConfig) throws ServletException
{
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws IOException, ServletException
{
//validate the request, check if the request can be forwarded to servlet.
if(request.getParameter("P").equalsIgnoreCase("123")){
filterChain.doFilter(request, response);
} else {
//write what you want to do if the request has no access
//below code will write 404 not found, you can do based on your requirement
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(404);
}
}
public void destroy()
{
}
}
Define the filter in the web.xml like this:
<filter>
<filter-name>ServletAccessFilter</filter-name>
<filter-class>com.ServletAcessFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ServletAccessFilter</filter-name>
<url-pattern>/go/*</url-pattern>
</filter-mapping>
To add to Bozho response, you may also try to move to Clean URLs
This will greatly increase your options in terms of URL pattern matching, and, in particular, may significantly ease configuration of a fronting reverse proxy if you ever need one.

Servlet's sendRedirect() kills my session attributes

I am working on a webapp in WinXP, Eclipse Indigo and Google web plugin.
I have a simple form that takes a value from user (e.g email) , passes it to a servlet named SignIn.java that processes it and saves the email value to the session.
The SignIn code is very simple , here is what its doGet mostly does:
String email = req.getParameter("email"); //getting the parameter from html form
...
...
HttpSession session = req.getSession(); //create a new session
session.setAttribute("email", email);
So far so good, I've verified that the values aren't null at this point. Now comes the problem, I want to redirect to another servlet (ShowOnline.java) that needs to do some more processing. When I write
resp.sendRedirect(resp.encodeRedirectURL("/ShowOnlineServlet"));
ShowOnline gets null session values (the same email attribute I saved a second before is now null)
When I write
getServletConfig().getServletContext().getRequestDispatcher("/ShowOnlineServlet");
everything is OK, the email attribute from before isn't null!
What is going on? sendRedirect() just makes your browser send a new request, it shouldn't affect the session scope. I have checked the cookies and they are fine (it is the same session from before for sure since it is the first and only session my webapp creates and furthermore I even bothered and checked the sesison ID's and they're the same on both requests).
Why would there be a difference between sendRedirect() and forward()? The easy solution would be to use forward() but I want to get to the bottom of this before I just let go , I think it is important for me to understand what happened. I'm not sure I like the idea of not knowing what's going on on such basic concepts (my whole webapp is very simple and basic at this point since I'm a beginner).
Any thoughts ideas or suggestions would be warmly welcome !
If your SignIn servlet is only saving a request parameter (email), then you could also replace the servlet with a filter, e.g. SignInFilter.
SignInFilter would contain the same logic as your SignIn servlet (copying the email from the request parameters to the session), but would call the next item in the chain (which will be your ShowOnline servlet) instead of doing any redirect/forward.
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession();
String email = req.getParameter("email");
session.setAttribute("email", email);
chain.doFilter(req, res); // continue to 'ShowOnline'
}
Set up your form to POST to the ShowOnline servlet instead, and configure your new SignInFilter to execute before ShowOnline (servlet mapping omitted below for brevity).
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<filter>
<filter-name>SignInFilter</filter-name>
<filter-class>com.example.SignInFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SignInFilter</filter-name>
<url-pattern>/ShowOnline</url-pattern>
</filter-mapping>
</web-app>
As far as my knowledge, sendRedirect() just redirects the control to another page without transfering the associated request & response object of parent page, but RequestDispatcher(object) will dispatch the ServletRequest and ServletResponse to the page mentioned in path argument { getServletContext().getRequestDispatcher("path")} after that you can either forward the objects to that page or include the objects. So by this container becomes assured that he has to use the previous request and response object from of the parent page instead of creating new one.
Specially if you are using session management the best option is RequestDispatcher.
Hope that answers the question.
To All :- Please correct me if i am wrong.
#rs

Categories

Resources