tl;dr: Dispatching to an absolute path from a servlet fails when it (the servlet) is bound with a wildcard (/a/*) but not when bound explicitly (/b/b)
I have a Guice webapp with a servlet that dispatches traffic to an HTML file stored in WEB-INF:
#Singleton
public class MyServlet extends HttpServlet {
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/hello.html").forward(request, response);
}
}
It is configured with a GuiceServletContextListener...
public class MyApp extends GuiceServletContextListener {
#Override
protected Injector getInjector() {
return Guice.createInjector(new MyModule());
}
private static final class MyModule extends ServletModule {
#Override
protected void configureServlets() {
serve("/a/*").with(MyServlet.class);
serve("/b/b").with(MyServlet.class);
}
}
}
...and a web.xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>MyApp</display-name>
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>test.MyApp</listener-class>
</listener>
</web-app>
I launch it in Tomcat and go to localhost/b/b and I see my hello.html page. However, when I go to localhost/a/a, I get a HTTP Status 404 - /WEB-INF/hello.html. This seems very strange to me as the two paths, wildcarded or not, are bound to the same servlet, as well as the HTML path being absolute and not really up for interpretation.
Is this the result of a bug, some (to me unknown) behavior I haven't accounted for, or sheer misconfiguration on my part?
Edit:
Based on some further experimentation, include seems to work as expected, which suggests to me that forward has some additional behavior I've missed. Using include will probably hold me over for now, but I'd still like to know what I'm doing wrong.
I found a GitHub issue which seems to match the problems I've encountered, meaning it's a bug in Guice itself.
The bug was originally reported in 2010 and has been treated with what seems like minor interest from contributors and none whatsoever from those in charge, so it'll probably never be fixed.
For future reference, I'm going to attempt to make do with include for now and consider switching to another framework at some point in the future.
Related
I have created a Wicket application that has recently been upgraded with the wicket REST functionality. When developing it and running through Jetty, I am able to post messages to the REST service correctly. However when it is deployed to tomcat, going to the REST URL gives a 404 error and a 'requested resource is not available' response.
Pom entries:
wicketstuff-annotation, wicketstuff-restannotations, wicketstuff-restannotations-json (all jar/compile, version identical to wicket version 6.24.0)
Code in the rest class
public class Webhook extends AbstractRestResource<JsonWebSerialDeserial> {
#MethodMapping(value="/notification", httpMethod=HttpMethod.POST, consumes= RestMimeTypes.APPLICATION_JSON, produces = RestMimeTypes.TEXT_PLAIN)
public String notification( #RequestBody Notification data ) {
// do some things
return "received successfully";
}
#Override
public String getMountPath() {
return "/emailcampaign/webhook";
}
}
The REST class is initialized in WicketApplication:
public void init() {
final Webhook hook = new Webhook();
mountResource( hook.getMountPath(), new ResourceReference( hook.getClass().getSimpleName() ) {
private static final long serialVersionUID = 1L;
public IResource getResource() {
return hook;
}
});
}
The tomcat localhost_access_logs have this:
`XX.XX.XX.XX - - [01/Feb/2018:06:46:10 +0000] "POST /emailcampaign/webhook/notification HTTP/1.1" 404 1041
The system starts up correctly, so I don't appear to be missing any jar files on deployment, so I'm at a loss. Can anyone help please?
Tomcat 7.0.67, Jetty 7.6.3, Wicket 6.24, Spring 4.1.4
Edit: This is the content of the web.xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app 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_2_5.xsd"
version="2.5">
<display-name>system-ui</display-name>
<context-param>
<param-name>configuration</param-name>
<param-value>deployment</param-value>
</context-param>
<filter>
<filter-name>wicket.system-ui</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>com.sw.system.ui.WicketApplication</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>wicket.system-ui</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
....aaand this was down to a rookie oversight.
On my development machine, the path to the application on jetty was http://localhost:8080. On the deployment machine, it's http://[server_name]/theapp. So I set the Client REST URL to point to http://[server_name]/theapp/emailcampaign/webhook/notification (ie add the theapp subpath) and it worked!
Doh but hopefully it helps someone else out.
this is my first time that I work on a Java project that use HttpServlet.
So I know that an HttpServlet is a program that run on a Web Application server and act as a middle layer between a request coming from a Web browser or other HTTP client and databases or applications on the HTTP server. So the servlet extend the competence of my application server.
I have some doubt to understand how exactly work this servlet founded into my project, into web.xml file I found this configuration:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>My Project</display-name>
<listener>
<listener-class>it.sistinf.ediweb.quartz.QuartzListener</listener-class>
</listener>
<servlet>
<servlet-name>edimon</servlet-name>
<servlet-class>it.sistinf.ediweb.monitor.servlets.Monitoraggio</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>edimon</servlet-name>
<url-pattern>/edimon.do/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>/logon.jsp</welcome-file>
</welcome-file-list>
<taglib>
<taglib-uri>displaytag</taglib-uri>
<taglib-location>/WEB-INF/displaytag-11.tld</taglib-location>
</taglib>
</web-app>
So reading some documentation it seem to understand that I have to tell the servlet container (or application server) what servlets to deploy, and what URL's to map the servlets to.
In the previous case I am configuring a servlet named edimon implemented by the Monitoraggio class.
Then it is mapped the servlet to a URL or URL pattern. In this case the edimon servlet is mapping with the /edimon.do/* URL pattern. So when it is called something that match with the previous pattern the edimon servlet is performed.
Then into my Monitoraggio class that implement the HttpServlet I found the service() method:
public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
LoggerMDC.setup(req, res);
Logger logger = (Logger) Logger.getStdLogger(Monitoraggio.class); // do not declare 'logger' as static field in order to work with MDC
String service = req.getParameter("serv");
char serviceId = Utility.getServizio(req.getParameter("serv"));
if (checkSession(req, serviceId) == false) {
gotoPage(ConfigurationFactory.getPropertiesPages().getProperty("pagina_errore_session"), req, res);
return;
}
LoggerWatch loggerWatch = new LoggerWatch(Monitoraggio.class, Long.valueOf(System.getProperty(Constants.Keys.CONFIG_STATS_WARNING_THRESHOLD, String.valueOf(LoggerWatch.DEFAULT_WARNING_THRESHOLD))).longValue());
if (logger.isTraceEnabled())
logger.trace("lanciaServizio() | logger threshold: " + loggerWatch.getWarningThreshold());
loggerWatch.start();
loggerWatch.info(new StringBuffer("service() | servizio: [").append(service).append("] | service start").toString());
String paginaDaLanciare = lanciaServizio(serviceId, req, res);
String executionTime = loggerWatch.getInfoTime();
//Modifica per export
if (req.getSession().getAttribute("export") == null) {
gotoPage(paginaDaLanciare, req, res);
}
loggerWatch.info(new StringBuffer("service() | servizio: [").append(service).append("] | [").append(executionTime).append("] after forward to ").append(paginaDaLanciare).toString(), true);
loggerWatch.stop();
req.getSession().removeAttribute("export");
req.getSession().removeAttribute("stringaXML");
req.getSession().removeAttribute("downbyte");
return;
}
Reading on the documentation it receives standard HTTP requests from the public service method and dispatches them to the doXXX methods defined in this class
So what exatly do this method? I can't understand how the servlet load the JSP
The documentation you read describes what the service() method of HttpServlet does by default. Since your servlet overrides the service() method and provides a different implementation, it doesn't do that anymore. Instead, it does... what the code in the method does.
A servlet doesn't "load a JSP". I don't see how JSPs have any relation to the servlet code you posted. Maybe gotoPage() does tell the container to forward the request to a JSP. You should look at the documentation and/or code of that method to know what it does.
This is my current Guice configuration:
public class MyServletModule extends ServletModule {
#Override
protected void configureServlets() {
bind(MyRest.class);
serveRegex(".+(?<!\\.(html|css|png|jpg))")
.with(HttpServletDispatcher.class);
}
}
However I want that my Rest resource is only access in form of http://127.0.0.1:8888/{hashcode_or_filename} and the only form accepted and processed (well, plus the /create method below).
Right now, I can deal with hashcode and filename properly in this path pattern.
However I am not sure how to deal the kind or scenario below, where the client is requesting path that is not mapped, which returns this in my case:
Could not find resource for relative : /examples/foo of full path:
http://127.0.0.1:8888/examples/foo
or
Could not find resource for relative : /examples/bar/foo of full
path: http://127.0.0.1:8888/examples/bar/foo
What I need is to be able to be able to handle unmapped paths so I can return a error HTML page or something and not show these error text in the browser.
Also if the request is: http://127.0.0.1:8888/ I need to forward to http://127.0.0.1:8888/index.html automatically. As right now I have to manually put the index.html in the tail.
My Resteasy resource is configure or wired with just:
#Singleton
#Path("/")
public class MyRest {
#GET
#Path({hashcode})
public Response getSomething(...){}
#POST
#Path("create")
public Response createSomething(...){}
}
Easiest way is to register filter to handle responses with error code other that 200 (OK). Or add to your web.xml something like this:
<error-page>
<error-code>404</error-code>
<location>/ErrorPage.jsp</location>
</error-page>
Also if the request is: http://127.0.0.1:8888/ I need to forward to
http://127.0.0.1:8888/index.html automatically. As right now I have to
manually put the index.html in the tail.
You can use this module http://tuckey.org/urlrewrite/
WEB-INF/web.xml
<filter>
<filter-name>UrlRewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
<init-param>
<param-name>confPath</param-name>
<param-value>/WEB-INF/urlrewrite.xml</param-value>
</init-param>
<!--...omitted...-->
</filter>
<filter-mapping>
<filter-name>UrlRewriteFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
WEB-INF/urlrewrite.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE urlrewrite
PUBLIC "-//tuckey.org//DTD UrlRewrite 4.0//EN"
"http://www.tuckey.org/res/dtds/urlrewrite4.0.dtd">
<urlrewrite>
<rule match-type="regex">
<from>^/$</from>
<to type="redirect">/index.html</to>
</rule>
</urlrewrite>
I have a REST api put together using jersey and guice for dependency injection. Everything works great, I have FEATURE_CANONICALIZE_URI_PATH and FEATURE_NORMALIZE_URI turned on and that handles extra slashes in almost all cases. For instance:
http://localhost:8080/my_service/param1//param2
The problem is I can't sort out the proper way to handle an extra slash at the beginning of the url (before the service path). Like this:
http://localhost:8080//my_service/param1/param2
This is how I have stuff set up currently:
web.xml
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>MyServletConfig</listener-class>
</listener>
MyServletConfig.java
public class MyServletConfig extends GuiceServletContextListener {
#Override
protected Injector getInjector() {
return Guice.createInjector(new JerseyServletModule() {
#Override
protected void configureServlets() {
bind(MyService.class);
guiceContainerConfig.put(ResourceConfig.FEATURE_NORMALIZE_URI, "true");
guiceContainerConfig.put(ResourceConfig.FEATURE_CANONICALIZE_URI_PATH, "true");
serve("/*").with(GuiceContainer.class, guiceContainerConfig);
}
});
}
}
MyService.java
#Path("/")
public class MyService {
#GET
#Path("/{param1}/{param2}")
#Produces("application/json")
public String get(
#PathParam("param1") final String param1,
#PathParam("param2") final String param2) {
return "{\"param1\":\"" + param1 + "\"}";
}
}
I can see that the issue really is that the request with the // before the service name is not actually getting handled by the servlet because it is not matched in the url-mapping of the filter-mapping but I'm just not really sure the right solution. Do I need to do an url rewrite before that url is matched to a servlet or is there a better way to map the request urls?
Quick leads:
http://tuckey.org/urlrewrite/ is a java web filter. It is a very powerful tool just like Apache's mod_rewrite.
Setup the filter with classic htaccess rewrites from http://www.mydigitallife.info/redirect-or-rewrite-to-remove-double-or-multiple-slashes-in-url/
If you're using jetty, just add the module rewrite-compactpath.
Module: rewrite-compactpath
: Add a rule to the rewrite module to compact paths so that double slashes
: in the path are treated as a single slash.
Depend: rewrite
XML: etc/rewrite-compactpath.xml
This is pretty simple and straightforward. I want to throw a 503 error from the servlet side.
response.sendError(503);
When this is thrown, I need it to hit a custom error page. Basically a 503 error page itself, but with a few modifications.
Say I have 503.html, and I added
<error-page>
<error-code>503</error-code>
<location>/503.html</location>
</error-page>
in web.xml.
I created a war file, with a servlet which throws the 503 error, and web.xml with this content. I kept the 503.html in the parent folder location. (Should I keep it elsewhere ?)
I deployed the app in WLS, but this custom 503.html is not getting hit. I am getting the generic 503 error.
Am I missing something?
My code is below:
webapp1.war
->web-inf
->web-inf->classes->prject4->Class1.class
->web-inf->jsp->error->custom.html
web.xml
<?xml version="1.0"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>Class1</servlet-name>
<servlet-class>project2.Class1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Class1</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<error-page>
<error-code>503</error-code>
<location>/WEB-INF/jsp/error/custom.html</location>
</error-page>
</web-app>
class1.java
public class Class1 extends HttpServlet
{
private ServletConfig config;
public void init(ServletConfig config)throws ServletException
{
this.config=config;
}
public void service (HttpServletRequest request, HttpServletResponse response)
throws IOException
{
response.setContentType("text/html");
ServletOutputStream l_out = response.getOutputStream();
response.sendError(503);
}
}
Ok, this was a minor error which I didn't figure out in the beginning.
In my web.xml the servlet-mapping was given as /*, which was causing an infinite loop condition as it throws the same code for which it has been mapped. So I had to adjust the servlet mapping so that Class1 doesn't map to any error pages - like say /images/*.
And then everything started working fine. :)
You can also try handling it with custom Error Handler.
public void service (HttpServletRequest request, HttpServletResponse response)
throws IOException
{
try
{
//some error generating code
throw new Exception("503_Exception");
}
catch(Exception e)
{
response.sendRedirect(HandleError.handle(e, request));
}
}
A separate class to handle errors. This can handle different types of errors.
You can add functionality to log stacktrace, send out emails if something is wrong etc.
public class HandleError{
public static String handle(Throwable t, javax.servlet.http.HttpServletRequest request)
{
String sErrorMsg = t.getMessage();
if (sErrorMsg.equals("503_Exception")) {
request.setAttribute("msg", Constants.EINVALSESSION);
return "/503.html";
}
return "/default_error.html";
}
}
If you are using Maven as your project build tool then it will look in the src/main/webapp directory, so for example our config looks like this:
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/jsp/error/error404.html</location>
</error-page>
and our error404.html sits in the folder:
${PROJECT_NAME}/src/main/webapp/WEB-INF/jsp/error/
If your not using Maven the path in the location will have a base directory of wherever you put your index.jsp
I guess there's a minimum limit on the number bytes your custom error page has. The lower limit is usually 512 Bytes. See Important note for your Custom error pages. I've seen this behavior in Google-Chrome too when using Tomcat.