I am trying to implement a servlet that gets raw requests, and decide either to process them, or forward them to another backend server. It is similar to a load-balancer, where a received request is forwarded to one of the (in my case 2) destinations. One of the destination is remote (on another host). Furthermore, the requests could come to the root (http://mycompany.com/).
Since I want to get raw requests, I implemented my own servlet (subclassing HttpServlet), and that works great. My servlet looks like:
public class MyProxyServlet extends HttpServlet {
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
processOrForward(req, resp);
}
// also doGet(), doHead(), ...
}
Since the service I want to process may send requests to the root, I would like to map my servlet to be the default servlet, thereby receiving any request that does not have an explicit servlet mapping. Assume my servlet's name is "myservlet", and is running along side of another servlet "foo", I expect all requests in the form of http://mycompany.com/foo/... to be delivered to foo, and everything else (e.g., /, /bar/..., /myservlet/...) to "myservlet". Looking at earlier posts (eg., root mapping here and here, or url rewriting here), I thought I figured it out, but it does not work.
Here is my web.xml:
<web-app>
<servlet>
<servlet-name>ProxyServlet</servlet-name>
<servlet-class>com.mycompany.MyProxyServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ProxyServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
In the above web.xml, for url-pattern I tried
"/" and "/*" and empty (i.e., <url-pattern></url-pattern>), all behave the same -->
Requests to root (/)goes to tomcat's default servlet
Requests to /myservlet/... are handled by "myservlet"
Requests to /fubar/... are always 404
Is there a way of turning my servlet to be the default. I.e., any request that does not map specifically to a servlet comes to mine (it is even acceptable to receive all requests, since I can deploy this servlet in its own container). In case it matters, I am using Tomcat 7.0.30 on Ubuntu 12.10.
This should be useful to you.
From the Java™ Servlet Specification Version 3.1 (JSR 340)
Chapter 12. Mapping Requests to Servlets
12.2 Specification of Mappings
In the Web application deployment descriptor, the following syntax is used to define mappings:
A string beginning with a / character and ending with a /* suffix is used for
path mapping.
A string beginning with a *. prefix is used as an extension mapping.
The empty string ("") is a special URL pattern that exactly maps to the
application's context root, i.e., requests of the form http://host:port/<contextroot>/.
In this case the path info is / and the servlet path and context path is
empty string ("").
A string containing only the / character indicates the "default" servlet of the
application. In this case the servlet path is the request URI minus the context path
and the path info is null.
All other strings are used for exact matches only.
As an addition, read this nice explanation with short examples from the book Head First Servlets & JSP: Passing the Sun Certified Web Component Developer Exam (2nd edition) (quote):
The THREE types of <url-pattern> elements
1) EXACT match
Example:
<url-pattern>/Beer/SelectBeer.do</url-pattern>
MUST begin with a slash (/).
Can have an extension (like .do), but it’s not required.
2) DIRECTORY match
Example:
<url-pattern>/Beer/*</url-pattern>
MUST begin with a slash (/).
Always ends with a slash/asterisk (/*).
3) EXTENSION match
Example:
<url-pattern>*.do</url-pattern>
MUST begin with an asterisk (*) (NEVER with a slash).
After the asterisk, it MUST have a dot extension (.do, .jsp, etc.).
IMPORTANT NOTE:
The URL patterns represent logical / virtual structure, i.e. the patterns (paths) specified does not need to exist physically.
UPDATE
If you want, as you say in your comment,
I want host:port to hit my servlet, not the default tomcat servlet
then see the solution here:
How do I make my web application be the Tomcat default application
In other words, what you want is a path without application context, which implies the application context of the Tomcat default application.
Quote from the above link:
In a standard Tomcat installation, you will notice that under the same
directory (CATALINA_BASE)/webapps/, there is a directory called ROOT
(the capitals are important, even under Windows). That is the
residence of the current Tomcat default application, the one that is
called right now when a user calls up
http://myhost.company.com[:port]. The trick is to put your
application in its place.
I am not sure did I understood what you want but probably intercept 404 is what you want to do, then redirect where you want.
I've came here to forum because I have strange problem with tomcat 7, mine is doing just what you want ;)
This is only one way how I can have root, EMPTY
<servlet-mapping>
<servlet-name>Default</servlet-name>
<url-pattern></url-pattern>
</servlet-mapping>
That way : anything is redirected to this servlet, including images, etc, for example, I open another page, this show this one, root, then I can see in log 4 more request to same page, 3 for css and one for image.
<servlet-mapping>
<servlet-name>Default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Related
The familiar code:
<servlet-mapping>
<servlet-name>main</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>main</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
My understanding is that /* maps to http://host:port/context/*.
How about /? It sure doesn't map to http://host:port/context root only. In fact, it will accept http://host:port/context/hello, but reject http://host:port/context/hello.jsp.
Can anyone explain how is http://host:port/context/hello mapped?
<url-pattern>/*</url-pattern>
The /* on a servlet overrides all other servlets, including all servlets provided by the servletcontainer such as the default servlet and the JSP servlet. Whatever request you fire, it will end up in that servlet. This is thus a bad URL pattern for servlets. Usually, you'd like to use /* on a Filter only. It is able to let the request continue to any of the servlets listening on a more specific URL pattern by calling FilterChain#doFilter().
<url-pattern>/</url-pattern>
The / doesn't override any other servlet. It only replaces the servletcontainer's built in default servlet for all requests which doesn't match any other registered servlet. This is normally only invoked on static resources (CSS/JS/image/etc) and directory listings. The servletcontainer's built in default servlet is also capable of dealing with HTTP cache requests, media (audio/video) streaming and file download resumes. Usually, you don't want to override the default servlet as you would otherwise have to take care of all its tasks, which is not exactly trivial (JSF utility library OmniFaces has an open source example). This is thus also a bad URL pattern for servlets. As to why JSP pages doesn't hit this servlet, it's because the servletcontainer's built in JSP servlet will be invoked, which is already by default mapped on the more specific URL pattern *.jsp.
<url-pattern></url-pattern>
Then there's also the empty string URL pattern . This will be invoked when the context root is requested. This is different from the <welcome-file> approach that it isn't invoked when any subfolder is requested. This is most likely the URL pattern you're actually looking for in case you want a "home page servlet". I only have to admit that I'd intuitively expect the empty string URL pattern and the slash URL pattern / be defined exactly the other way round, so I can understand that a lot of starters got confused on this. But it is what it is.
Front Controller
In case you actually intend to have a front controller servlet, then you'd best map it on a more specific URL pattern like *.html, *.do, /pages/*, /app/*, etc. You can hide away the front controller URL pattern and cover static resources on a common URL pattern like /resources/*, /static/*, etc with help of a servlet filter. See also How to prevent static resources from being handled by front controller servlet which is mapped on /*. Noted should be that Spring MVC has a built in static resource servlet, so that's why you could map its front controller on / if you configure a common URL pattern for static resources in Spring. See also How to handle static content in Spring MVC?
I'd like to supplement BalusC's answer with the mapping rules and an example.
Mapping rules from Servlet 2.5 specification:
Map exact URL
Map wildcard paths
Map extensions
Map to the default servlet
In our example, there're three servlets. / is the default servlet installed by us. Tomcat installs two servlets to serve jsp and jspx. So to map http://host:port/context/hello
No exact URL servlets installed, next.
No wildcard paths servlets installed, next.
Doesn't match any extensions, next.
Map to the default servlet, return.
To map http://host:port/context/hello.jsp
No exact URL servlets installed, next.
No wildcard paths servlets installed, next.
Found extension servlet, return.
Perhaps you need to know how urls are mapped too, since I suffered 404 for hours. There are two kinds of handlers handling requests. BeanNameUrlHandlerMapping and SimpleUrlHandlerMapping. When we defined a servlet-mapping, we are using SimpleUrlHandlerMapping. One thing we need to know is these two handlers share a common property called alwaysUseFullPath which defaults to false.
false here means Spring will not use the full path to mapp a url to a controller. What does it mean? It means when you define a servlet-mapping:
<servlet-mapping>
<servlet-name>viewServlet</servlet-name>
<url-pattern>/perfix/*</url-pattern>
</servlet-mapping>
the handler will actually use the * part to find the controller. For example, the following controller will face a 404 error when you request it using /perfix/api/feature/doSomething
#Controller()
#RequestMapping("/perfix/api/feature")
public class MyController {
#RequestMapping(value = "/doSomething", method = RequestMethod.GET)
#ResponseBody
public String doSomething(HttpServletRequest request) {
....
}
}
It is a perfect match, right? But why 404. As mentioned before, default value of alwaysUseFullPath is false, which means in your request, only /api/feature/doSomething is used to find a corresponding Controller, but there is no Controller cares about that path. You need to either change your url to /perfix/perfix/api/feature/doSomething or remove perfix from MyController base #RequestingMapping.
I think Candy's answer is mostly correct. There is one small part I think otherwise.
To map host:port/context/hello.jsp
No exact URL servlets installed, next.
Found wildcard paths servlets, return.
I believe that why "/*" does not match host:port/context/hello because it treats "/hello" as a path instead of a file (since it does not have an extension).
The essential difference between /* and / is that a servlet with mapping /* will be selected before any servlet with an extension mapping (like *.html), while a servlet with mapping / will be selected only after extension mappings are considered (and will be used for any request which doesn't match anything else---it is the "default servlet").
In particular, a /* mapping will always be selected before a / mapping. Having either prevents any requests from reaching the container's own default servlet.
Either will be selected only after servlet mappings which are exact matches (like /foo/bar) and those which are path mappings longer than /* (like /foo/*). Note that the empty string mapping is an exact match for the context root (http://host:port/context/).
See Chapter 12 of the Java Servlet Specification, available in version 3.1 at http://download.oracle.com/otndocs/jcp/servlet-3_1-fr-eval-spec/index.html.
This question already has answers here:
Difference between / and /* in servlet mapping url pattern
(5 answers)
Closed 5 years ago.
When I have "/" as my url-pattern, I can type into whatever I want after the slash in the address bar and land successfully onto the servlet.
That is, both
http://localhost:8080/firstServlet/
as well as
http://localhost:8080/firstServlet/any_random_string
gives me the same result.
But when I have url-pattern blank then only http://localhost:8080/firstServlet/ works and everything. Please explain why.
There is something here and here similar but I don't understand it exactly.
The pattern "/*" is also behaving just like "/".
<url-pattern>/*</url-pattern>
The /* on a servlet overrides all other servlets, including all servlets provided by the servletcontainer such as the default servlet and the JSP servlet. Whatever request you fire, it will end up in that servlet. This is thus a bad URL pattern for servlets. Usually, you'd like to use /* on a Filter only
<url-pattern>/</url-pattern>
The / doesn't override any other servlet. It only replaces the servletcontainer's builtin default servlet for all requests which doesn't match any other registered servlet. This is normally only invoked on static resources (CSS/JS/image/etc) and directory listings
And for empty url pattern
<url-pattern></url-pattern>
The empty string ("") is a special URL pattern that exactly maps to
the application's context root
The familiar code:
<servlet-mapping>
<servlet-name>main</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>main</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
My understanding is that /* maps to http://host:port/context/*.
How about /? It sure doesn't map to http://host:port/context root only. In fact, it will accept http://host:port/context/hello, but reject http://host:port/context/hello.jsp.
Can anyone explain how is http://host:port/context/hello mapped?
<url-pattern>/*</url-pattern>
The /* on a servlet overrides all other servlets, including all servlets provided by the servletcontainer such as the default servlet and the JSP servlet. Whatever request you fire, it will end up in that servlet. This is thus a bad URL pattern for servlets. Usually, you'd like to use /* on a Filter only. It is able to let the request continue to any of the servlets listening on a more specific URL pattern by calling FilterChain#doFilter().
<url-pattern>/</url-pattern>
The / doesn't override any other servlet. It only replaces the servletcontainer's built in default servlet for all requests which doesn't match any other registered servlet. This is normally only invoked on static resources (CSS/JS/image/etc) and directory listings. The servletcontainer's built in default servlet is also capable of dealing with HTTP cache requests, media (audio/video) streaming and file download resumes. Usually, you don't want to override the default servlet as you would otherwise have to take care of all its tasks, which is not exactly trivial (JSF utility library OmniFaces has an open source example). This is thus also a bad URL pattern for servlets. As to why JSP pages doesn't hit this servlet, it's because the servletcontainer's built in JSP servlet will be invoked, which is already by default mapped on the more specific URL pattern *.jsp.
<url-pattern></url-pattern>
Then there's also the empty string URL pattern . This will be invoked when the context root is requested. This is different from the <welcome-file> approach that it isn't invoked when any subfolder is requested. This is most likely the URL pattern you're actually looking for in case you want a "home page servlet". I only have to admit that I'd intuitively expect the empty string URL pattern and the slash URL pattern / be defined exactly the other way round, so I can understand that a lot of starters got confused on this. But it is what it is.
Front Controller
In case you actually intend to have a front controller servlet, then you'd best map it on a more specific URL pattern like *.html, *.do, /pages/*, /app/*, etc. You can hide away the front controller URL pattern and cover static resources on a common URL pattern like /resources/*, /static/*, etc with help of a servlet filter. See also How to prevent static resources from being handled by front controller servlet which is mapped on /*. Noted should be that Spring MVC has a built in static resource servlet, so that's why you could map its front controller on / if you configure a common URL pattern for static resources in Spring. See also How to handle static content in Spring MVC?
I'd like to supplement BalusC's answer with the mapping rules and an example.
Mapping rules from Servlet 2.5 specification:
Map exact URL
Map wildcard paths
Map extensions
Map to the default servlet
In our example, there're three servlets. / is the default servlet installed by us. Tomcat installs two servlets to serve jsp and jspx. So to map http://host:port/context/hello
No exact URL servlets installed, next.
No wildcard paths servlets installed, next.
Doesn't match any extensions, next.
Map to the default servlet, return.
To map http://host:port/context/hello.jsp
No exact URL servlets installed, next.
No wildcard paths servlets installed, next.
Found extension servlet, return.
Perhaps you need to know how urls are mapped too, since I suffered 404 for hours. There are two kinds of handlers handling requests. BeanNameUrlHandlerMapping and SimpleUrlHandlerMapping. When we defined a servlet-mapping, we are using SimpleUrlHandlerMapping. One thing we need to know is these two handlers share a common property called alwaysUseFullPath which defaults to false.
false here means Spring will not use the full path to mapp a url to a controller. What does it mean? It means when you define a servlet-mapping:
<servlet-mapping>
<servlet-name>viewServlet</servlet-name>
<url-pattern>/perfix/*</url-pattern>
</servlet-mapping>
the handler will actually use the * part to find the controller. For example, the following controller will face a 404 error when you request it using /perfix/api/feature/doSomething
#Controller()
#RequestMapping("/perfix/api/feature")
public class MyController {
#RequestMapping(value = "/doSomething", method = RequestMethod.GET)
#ResponseBody
public String doSomething(HttpServletRequest request) {
....
}
}
It is a perfect match, right? But why 404. As mentioned before, default value of alwaysUseFullPath is false, which means in your request, only /api/feature/doSomething is used to find a corresponding Controller, but there is no Controller cares about that path. You need to either change your url to /perfix/perfix/api/feature/doSomething or remove perfix from MyController base #RequestingMapping.
I think Candy's answer is mostly correct. There is one small part I think otherwise.
To map host:port/context/hello.jsp
No exact URL servlets installed, next.
Found wildcard paths servlets, return.
I believe that why "/*" does not match host:port/context/hello because it treats "/hello" as a path instead of a file (since it does not have an extension).
The essential difference between /* and / is that a servlet with mapping /* will be selected before any servlet with an extension mapping (like *.html), while a servlet with mapping / will be selected only after extension mappings are considered (and will be used for any request which doesn't match anything else---it is the "default servlet").
In particular, a /* mapping will always be selected before a / mapping. Having either prevents any requests from reaching the container's own default servlet.
Either will be selected only after servlet mappings which are exact matches (like /foo/bar) and those which are path mappings longer than /* (like /foo/*). Note that the empty string mapping is an exact match for the context root (http://host:port/context/).
See Chapter 12 of the Java Servlet Specification, available in version 3.1 at http://download.oracle.com/otndocs/jcp/servlet-3_1-fr-eval-spec/index.html.
We usually end up with writing <url-pattern>/*</url-pattern> in web.xml for any Filter in servlets.
<filter-mapping>
<filter-name>requestRedirectorFilter</filter-name>
<url-pattern>/action</url-pattern>
</filter-mapping>`.
Now my doubt is how java identifies which is next servlet/jsp is? Because any request we make through
request.getRequestDispatcher("/ABCXYZ").forward(request, (HttpServletResponse)servletResponse);
to navigate on next servlet/jsp, container by default is going to search in web.xml. And in web.xml <url-pattern>/*</url-pattern> is already there for the filter we use. Exactly here actual problem begins.
If <url-pattern>/*</url-pattern> [which is acting like a universal receiver for any request] is already there in web.xml then How the heck container knows to follow <url-pattern>/ABCXYZ</url-pattern> instead <url-pattern>/*</url-pattern> ? Please share your views and knowledge on this front.
Servlet Matching Procedure
A request may match more than one servlet-mapping in a given context. The servlet container uses a straightforward matching procedure to determine the best match.
The matching procedure has four simple rules.
First, the container prefers an exact path match over a wildcard path match.
Second, the container prefers to match the longest pattern.
Third, the container prefers path matches over filetype matches.
Finally, the pattern <url-pattern>/</url-pattern> always matches any request that no other pattern matches.
For example, a context web.xml file can map the home page for an online catalog to one pattern and the search page for the catalog to a different pattern, as shown below:
<servlet-mapping>
<servlet-name>catalogBrowse</servlet-name>
<url-pattern>/Catalog/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>catalogSearch</servlet-name>
<url-pattern>/Catalog/search/*</url-pattern>
</servlet-mapping>
Below figure illustrates the matching process for a context. Since the container prefers to match the longest pattern, a URL that includes /Catalog/search/ always matches the mapping for catalogSearch rather than the mapping for catalogBrowse.
URL pattern matching
It is copied form the below link if your are not interested to go to the link.
Please have a look at URL Patterns where it is described in detail with examples.
I want to be able to return a specific jsp page from restful service given the page name as a PathParam. These pages will be added and removed by a different group of people after the application is deployed.
I believe using the Viewable object is a decent way to go (I'm open to a better/correct way.)
#GET
#Produces(MediaType.TEXT_HTML)
#Path("{page}")
public Response showJSP(#PathParam("page") String page) {
Viewable loadPage = null;
try {
loadPage = new Viewable("/" + page, null);
} catch (Exception e) {
return Response.status(404).build();
}
Response ret = Response.ok(loadPage).build();
// if (ret.getStatus() == 500){
// ret = Response.status(404).build();
// }
return ret;
}
The above is my experimental code and contains my attempts to handle the error.
This works fine as long as the page name is a valid jsp page. If I pass in an invalid page I get
500 Internal Server Error
java.io.IOException: The template name, /someWrongFileName, could not be resolved to a fully qualified template name.....
What I've figured out is this error is generated internal to the Viewable object so it's not throwing an exception and the Response of course is a valid page so checking for the 500 status doesn't work.
I can hack in several things that I'm pretty sure are wrong such as, I really do not want to toString the generated page and regex for the error text.
I would like to be able to detect that the page is invalid and simply return a 404.
Am I on the right path or is there a better way?
If I'm on the right path how do I detect the bad page name?
I've tried catching the error like you did but it seems pretty hard. I suppose the class was never intended to be used this way.
The Viewable interface lets you use JSPs as a means to visualize the representation of your application's resources. In general, the resource is represented by a POJO that you serialize to JSON, XML or pass to the Viewable constructor.
As far as I understand, what you're trying to do here is a little different. Your resources are the JSPs themselves and it seems you only want to make the pages available to clients, without passing any objects to the Viewable constructor.
The HTTP 404 error means that a resource can not be found. In your case (while treating the JSPs as resources), when the path to a JSP is incorrect, this is exactly what happens so I understand why you want to use the status code.
However, I think that the creators of the interface you're trying to use had a different opinion on the matter. They didn't see the JSPs as resources but as a tool to represent them. The construction of a view is seen here as a completely different thing. A matter internal to the server and something that should be hidden from the client. The client has to receive a response with an HTML string in it. How it happens should not matter at all. An HTTP 500 is totally understandable in such context.
If you only want to use GET requests to fetch the contents of your JSPs, you can just ignore the Viewable interface or even Jersey itself. If your web.xml is appropriately set, the pages should be accessible anyway. You don't have to pass a JSP name to an annotated method. Just use paths to the documents themselves. Custom 404s can be dealt with in web.xml as well.
Assume that you have a project called MyApp and deployed to the path <host>:<port>/MyApp
With the following structure of its Web pages directory.
-Web pages
|-META-INF
|-WEB-INF
|-error
|\-error404.jsp
|-package1
||-ResourceClass
||\-page1.jsp
|-pages
||-plainpage1.jsp
|\-plainpage2.jsp
\-index.jsp
Now, let's assume the web.xml looks like this:
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<servlet>
<servlet-name>ServletAdaptor</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ServletAdaptor</servlet-name>
<url-pattern>/resources/ *</url-pattern>
</servlet-mapping>
<error-page>
<error-code>404</error-code>
<location>/err/err404.jsp</location>
</error-page>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
You can use the Viewable interface to show a page for your actual resource. This is how the interface is supposed to be used. As a means to show a resource's html representation, not the resource itself.
package package1;
#Path("/")
public class ResourceClass {
#Path("/POJO/{id}")
#GET
public Response tryToGetBadView(#PathParam("id") String id) {
MyPOJOClass entity = null;
//fetch your POJO from database/file/whatever
return Response.ok().entity(new Viewable("page1",entity)).build();
}
}
You can get the appropriate view using a path like:
<host>:<port>/MyApp/resources/POJO/1
while also being able to get the plain JSPs without the help of Jersey Servlet Container. The files are there and they don't need any other representation than themselves. The path to your Jersey Servlet Container is specified as
<host>:<port>/resources/*
So you can omit the container and reach normal files from your Web Apps folder. Images, css and also JSPs.
Like this:
<host>:<port>/MyApp/pages/plainpage1.jsp
the same way you get
<host>:<port>/MyApp/index.jsp
and even a page that you'd normally use to construct a Viewable object (without the guarantee that it would work properly without passing a POJO)
<host>:<port>/MyApp/package1/ResourceClass/page1.jsp
For these "static" files, you'll get a 404 every time you pick a name of a non-existent page. The error page displayed will be the one specified in web.xml
This is how I'd do it. Using Jersey to serve plain JSPs seems like an overkill and brings unnecessary complication. If it's not what you meant, I suggest rethinking the design.
Notice that while Jersey is not used, the pages can still be accessed in a way you can consider RESTful.
If you really want to stick to your experimantal code, you can also try accessing the directories in your Web Pages folder using the IO API and checking manually whether the requested files exist. It'd be ugly but it might just work.
I found what I'm looking for in another post and modified my method below.
#GET
#Produces("text/html")
#Path("{page}")
public void showJSP(#Context HttpServletResponse response,
#Context HttpServletRequest request,
#PathParam("orderId") String orderId) throws ServletException, IOException {
request.getRequestDispatcher(page + ".jsp").forward(request, response);
}
This gives me my 404 that I'm looking for and all of my services still work.
If this is the 'wrong' way to do this I'm still open for ideas but for now it works.