Reputation: 131
I am using embedded Jetty 9 where Jetty is a small corner of my application. I have a mix of static content, servlets and jsp's. My servlets and jsp's are part of my source tree and go into the executable jar and follow a normal package structure. Perhaps some of my static content will be in the jar, but other static content, including index.html should go in the project root folder; it could be in a sub-folder as web-content. My servlets and jsp's are compiled somewhere into the bin class tree.
I create a HandlerList having servletContext(servlet), resource_handler(static content), webAppContext(jsp) & DefaultHandler. My resource_handler sets WelcomeFiles to "index.html" and ResourceBase to "./web-content".
I have played with different orderings of the HandlerList. If I put resource_handler first, then the home page works fine, but then resource_handler can eat other files. If I put resource_handler last, then home page gets 404's.
Which ever is first in the list works fine but I get problems when it is second or third.
I know that each handler tries to handle the context path and if it can't, passes it to the next. I am less clear about the context path the next handler starts with. Does it go to the base context / or does it continue from where the previous handler left off. Why doesn't it see my index.html?
I would like to be able to put content, static, servlet or jsp (almost) wherever I want and have context paths chosen to seem logical at the browser and a home page with a context of /. And then have the server find it.
This is embedded so I do not need to conform to war rules.
What are the rules to organize/order handlers - context - welcomeFiles?
Upvotes: 2
Views: 1986
Reputation: 131
I am evolving some rules to answer my question, largely by experiment.
1. When using ResourceHandler for static content, do NOT use setContextPath("/") as it will match too much.
2. For each context path, use a distinct path for example for ResourceHandler(1) use (say) /static1, for ResourceHandler(2) use /static2, for a servlet use /servlet. Because there is no "/" path each easily maps to what it should serve.
3. Do not use . or * on ResourceHandler context paths, just /foo not /foo/* or ./foo
4. On servlet paths do use * to represent pathInfo, use (say) /servlet/*. Then in the servlet use request.getPathInfo() to sort out those in *.
5. For setWelcomeFiles just use "index.html". Do the rest of the path as in 6.
6. Put index.html in say /web (beside src) then for the corresponding ResourceHandler, setResourceBase="web" (no slash=relative path). Then it is (easily) not in the runnable jar
6a. If you want it in the jar put it in a /resources package beside Main.java then Eclipse export will package the jar without resorting to Ant. (may have to fiddle with loadResource() here?
7. Group ResourceHandlers in a ContextHandlerCollection since "it creates a PathMap to it's contained handlers based on the context path " then include them with 8 below.
8. Use HandlerCollection to collect Servlets since "it calls all handlers in list order, regardless of the response status or exceptions". Do include the collection of 7.
9. Note that within index.html, paths can be relative to the context path set for the ResourceHandler that serves index.html, /static1 in this example. So use absolute links to the servlet will be like href="/servlet/help" and in the browser localhost:80/servlet/help
. ( or build a scheme on relative paths)
That is as far as I have got. But things are starting to work. I am using embedded jetty with no xml configuration. I didn't merge my jsp's in with this yet, but I hope I have enough 'rules' to do that now.
It would be good for an expert to comment or expand/simplify these empirical rules!
(Edit)
When I use the second ResourceHandler, it serves the content ok (as seen on a browser) but then gives IllegalStateException.
To see what is going on, I override ResourceHandler.handle method and just use super.handle()
logging the request and isComitted()
. isComitted() shows true. However, on return from handle I see log messages:
INFO WebServer.configureResourceHandler(2) target=/foo/bar.bar request url=http:\\192.168.2.106:8080/static2/foo/bar.bar, isCommitted=true
WARNING ###### org.eclipse.jetty.server.Response.Committed before 404 null
WARNING ###### org.eclipse.jetty.servlet.ServletHandler./static2/foo/bar.bar
WARNING ###### org.eclipse.jetty.server.HttpChannel./static2/foo/bar.bar
WARNING ###### org.eclipse.jetty.server.HttpChannel.Could not send response error 500: java.lang.IllegalStateException: Committed
It seems that the ResourceHandler.handle() method has served the content ok (according to the browser) and set isComitted true, but code after handle() is not aware that it has been handled, wants to do a 404 but can't so logs a 500 (which it can't send because the response is committed).
What am I doing wrong or is this a bug?
Not a bug. I am mixing ContextHandlerCollection items with HandlerCollection items. The former are filtered by context path, the latter are not. So my servlet was trying to handle items already handled. Solve this by dealing with the servlet in the same way as the static content. Use code like this:
private ContextHandler configureRootServlet() {
ServletContextHandler context = new ServletContextHandler(); /* Can hold several servlets */
context.setContextPath( "/servlet" );
ServletHandler handler = new ServletHandler();
context.setHandler( handler );
handler.addServletWithMapping( RootServlet.class, "/*");
rootServlet = (RootServlet)handler.getServlets()[0].getServlet();/*We
only added one. Grab the servlet instance he created for us so we can
configure it */
// Configure the servlet
rootServlet.setMain( mainClass );
// etc
return context;
}
Then just add the ServletContextHandler to the ContextHandlerCollection. Note the servlet mapping is relative to the context. Wrap it all together ...
ContextHandlerCollection contexts = new ContextHandlerCollection();
contexts.setHandlers(new Handler[]{ configureResourceHandler1()
, configureResourceHandler2(), configureRootServletHandler() });
HandlerCollection handlers = new HandlerCollection();
handlers.setHandlers( new Handler[]{ contexts, new DefaultHandler()
,configureRequestLogHandler() });
Now there are no server errors!
Upvotes: 4