The only way to accomplish this in the Servlet world is to have a error page handling the 404 status code and issuing the redirect yourself.
This can only be done within a specific webapp, and not from a generic rewrite handling routine.
Why?
Well, the "If the requested resource doesn't exist, use index.html" is the key.
What happens.
- The webapp was entered, and no url-pattern matched, so the default url-pattern (of
"/"
) is used.
- The default url-pattern is mapped to what's known as "The Default Servlet"
- The Default Servlet is responsible for looking in the webapp's base resource for a matching resource and returning that content as a static resource request.
- If the static resource doesn't exist, and the request was for a directory (eg:
/js/
then use the list of welcome files to look up a welcome file. (this list is configured in the WEB-INF/web.xml
)
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
- If the static resource still doesn't exist, process it as a 404 response.
Meanwhile, the error page handling routines of the servlet spec kick in.
This will result in a lookup for a path declared for 404 in your WEB-INF/web.xml
.
<servlet>
<servletname>404Handler</servlet-name>
<servlet-class>com.acme.My404Servlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>404Handler</servlet-name>
<url-pattern>/404status</url-pattern>
</servlet-mapping>
<!-- ... then later ... -->
<error-page>
<error-code>404</error-code>
<location>/404status</location>
</error-page>
That path can be a servlet, a static resource, a jsp, etc. Pretty much anything that you can reference with a path.
If it's a servlet (or jsp) you can interrogate the original request via the request attributes to know why you are handling this error.
See: https://stackoverflow.com/a/32910916/775715
An example of this in embedded-jetty would be ...
package jetty.errors;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.resource.PathResource;
public class EmbeddedWelcomeErrorDemo
{
public static void main(String[] args) throws Exception
{
Server server = new Server(8080);
String baseDir = System.getProperty("user.home");
if (args.length > 0)
baseDir = args[0];
Path basePath = Paths.get(baseDir);
if (!Files.exists(basePath) || !Files.isDirectory(basePath))
{
throw new IOException("Not a valid directory: " + basePath);
}
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
context.setBaseResource(new PathResource(basePath));
context.setWelcomeFiles(new String[]{
"index.html"
});
// Add error page mapping for context
context.addServlet(ErrorHandling.class, "/errorpage");
ErrorPageErrorHandler errorMapper = new ErrorPageErrorHandler();
errorMapper.addErrorPage(404, "/errorpage");
context.setErrorHandler(errorMapper);
// to handle static resources against base resource (always last)
// always named "default" (per spec)
ServletHolder defaultHolder = new ServletHolder("default", DefaultServlet.class);
// assigned to default url-pattern of "/" (per spec)
context.addServlet(defaultHolder, "/");
HandlerList handlers = new HandlerList();
handlers.addHandler(context);
handlers.addHandler(new DefaultHandler()); // for non-context errors
server.setHandler(handlers);
server.start();
server.join();
}
public static class ErrorHandling extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
if (req.getDispatcherType() != DispatcherType.ERROR)
{
// we didn't get here from a error dispatch.
// somebody attempted to use this servlet directly.
resp.setStatus(404);
return;
}
String requestedResource = (String)req.getAttribute(RequestDispatcher.ERROR_REQUEST_URI);
log("[ErrorHandling] Requested resource was " + requestedResource);
int statusCode = (int)req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
switch (statusCode)
{
case 404:
// let handle it by a redirect
resp.sendRedirect("/");
break;
default:
// pass the other errors through
resp.setStatus(statusCode);
break;
}
}
}
}
Some example of what happens.
$ mkdir $HOME/tmp-base
$ mdkir css
$ echo "this is the index.html" > index.html
$ echo "this is my other html" > myother.html
$ echo "this is my fancy css" > css/main.css
Then run the server example with the command line to this directory
$ java ... jetty.errors.EmbeddedWelcomeErrorDemo $HOME/tmp-base
2019-09-24 14:17:55.540:INFO::main: Logging initialized @190ms to org.eclipse.jetty.util.log.StdErrLog
2019-09-24 14:17:55.621:INFO:oejs.Server:main: jetty-9.4.20.v20190813; built: 2019-08-13T21:28:18.144Z; git: 84700530e645e812b336747464d6fbbf370c9a20; jvm 1.8.0_202-b08
2019-09-24 14:17:55.661:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@7921b0a2{/,file:///home/joakim/tmp-base/,AVAILABLE}
2019-09-24 14:17:55.674:INFO:oejs.AbstractConnector:main: Started ServerConnector@7cef4e59{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2019-09-24 14:17:55.674:INFO:oejs.Server:main: Started @325ms
And then make a few requests ...
$ curl -L -vv http://localhost:8080/
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 24 Sep 2019 19:26:28 GMT
< Last-Modified: Tue, 24 Sep 2019 19:12:21 GMT
< Content-Type: text/html
< Accept-Ranges: bytes
< Content-Length: 23
< Server: Jetty(9.4.20.v20190813)
<
this is the index.html
* Connection #0 to host localhost left intact
this was welcome file handling
$ curl -L -vv http://localhost:8080/myother.html
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /myother.html HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 24 Sep 2019 19:21:10 GMT
< Last-Modified: Tue, 24 Sep 2019 19:13:46 GMT
< Content-Type: text/html
< Accept-Ranges: bytes
< Content-Length: 22
< Server: Jetty(9.4.20.v20190813)
<
This is my other html
* Connection #0 to host localhost left intact
this was normal static file serving
$ curl -L -vv http://localhost:8080/css/main.css
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /css/main.css HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 24 Sep 2019 19:22:22 GMT
< Last-Modified: Tue, 24 Sep 2019 19:22:16 GMT
< Content-Type: text/css
< Accept-Ranges: bytes
< Content-Length: 21
< Server: Jetty(9.4.20.v20190813)
<
this is my fancy css
* Connection #0 to host localhost left intact
This was normal static file serving
If I make some requests to non-existent resources or directories ....
$ curl -L -vv http://localhost:8080/css/bogus.css
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /css/bogus.css HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Date: Tue, 24 Sep 2019 19:22:46 GMT
< Location: http://localhost:8080/
< Content-Length: 0
< Server: Jetty(9.4.20.v20190813)
<
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost:8080/'
* Found bundle for host localhost: 0x5647e1581a50 [can pipeline]
* Re-using existing connection! (#0) with host localhost
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 24 Sep 2019 19:22:46 GMT
< Last-Modified: Tue, 24 Sep 2019 19:12:21 GMT
< Content-Type: text/html
< Accept-Ranges: bytes
< Content-Length: 23
< Server: Jetty(9.4.20.v20190813)
<
this is the index.html
* Connection #0 to host localhost left intact
This was handled by the ErrorHandling
servlet
$ curl -L -vv http://localhost:8080/this/directory/does/not/exist
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /this/directory/does/not/exist HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Date: Tue, 24 Sep 2019 19:23:02 GMT
< Location: http://localhost:8080/
< Content-Length: 0
< Server: Jetty(9.4.20.v20190813)
<
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost:8080/'
* Found bundle for host localhost: 0x561eefa8b020 [can pipeline]
* Re-using existing connection! (#0) with host localhost
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 24 Sep 2019 19:23:02 GMT
< Last-Modified: Tue, 24 Sep 2019 19:12:21 GMT
< Content-Type: text/html
< Accept-Ranges: bytes
< Content-Length: 23
< Server: Jetty(9.4.20.v20190813)
<
this is the index.html
* Connection #0 to host localhost left intact
This was handled by the ErrorHandling
servlet
[joakim@hyperion tmp]$ curl -L -vv http://localhost:8080/non-existant.jpeg
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /non-existant.jpeg HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Date: Tue, 24 Sep 2019 19:21:18 GMT
< Location: http://localhost:8080/
< Content-Length: 0
< Server: Jetty(9.4.20.v20190813)
<
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost:8080/'
* Found bundle for host localhost: 0x563f476b6a50 [can pipeline]
* Re-using existing connection! (#0) with host localhost
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 24 Sep 2019 19:21:18 GMT
< Last-Modified: Tue, 24 Sep 2019 19:12:21 GMT
< Content-Type: text/html
< Accept-Ranges: bytes
< Content-Length: 23
< Server: Jetty(9.4.20.v20190813)
<
this is the index.html
* Connection #0 to host localhost left intact
This was handled by the ErrorHandling
servlet