Serving Static Files from Alternate Path in Embedded Jetty

Serving static files from alternate path in embedded Jetty

What you need:

  1. A DefaultServlet at "/" (recommended, it is a requirement of the servlet spec)

    • this should be at named dispatcher of "default" (another requirement of servlet spec)
  2. An alternate DefaultServlet with your custom static content, configured via init-params

    • Using a different named dispatcher than "default" (to avoid a name collision between other servlet spec features)
    • You can use the ServletHolder.setInitParameter(name,value) to accomplish this
    • Be sure you set the pathInfoOnly parameter to true (to get around special cases for "default" named dispatchers)
  3. A servlet of your own, serving dynamic content.

AltDefaultServlet.java

package jetty.demo;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

public class AltDefaultServlet
{
public static void main(String[] args)
{
System.setProperty("org.eclipse.jetty.LEVEL","INFO");

Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(8080);
server.addConnector(connector);

// The filesystem paths we will map
String homePath = System.getProperty("user.home");
String pwdPath = System.getProperty("user.dir");

// Setup the basic application "context" for this application at "/"
// This is also known as the handler tree (in jetty speak)
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setBaseResource(Resource.newResource(pwdPath));
context.setContextPath("/");
server.setHandler(context);

// add a simple Servlet at "/dynamic/*"
ServletHolder holderDynamic = new ServletHolder("dynamic", DynamicServlet.class);
context.addServlet(holderDynamic, "/dynamic/*");

// add special pathspec of "/home/" content mapped to the homePath
ServletHolder holderHome = new ServletHolder("static-home", DefaultServlet.class);
holderHome.setInitParameter("resourceBase",homePath);
holderHome.setInitParameter("dirAllowed","true");
holderHome.setInitParameter("pathInfoOnly","true");
context.addServlet(holderHome,"/home/*");

// Lastly, the default servlet for root content (always needed, to satisfy servlet spec)
// It is important that this is last.
ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class);
holderPwd.setInitParameter("dirAllowed","true");
context.addServlet(holderPwd,"/");

try
{
server.start();
server.dump(System.err);
server.join();
}
catch (Throwable t)
{
t.printStackTrace(System.err);
}
}

}

Serving static files with content negotiation using embedded Jetty

You are not serving static files if you want to use content negotiation.

Content Negotiation is a fundamental concept of HTTP, and it really for the content that is being served from the (hand waving) "resource", that you are requesting.

Serving static files is a specialized form of "resource", where the mime-type / content-type is known based on its file extension.

The DefaultServlet (which is doing the static file serving in your example) has 1 more feature on top of the this specialization, but not for content-type, but rather content-encoding (you can pre-compress your static resources by creating a <filename>.gz file that sits next to the original file, if the requesting client indicates that they can accept gzip, then this <filename>.gz is served instead of the uncompressed <filename> version.

In order to accomplish this goal, you'll need to write something that serves the static files in a way that makes sense for you.

What you'll need if you want to do this yourself.

  • A new servlet on url-pattern /*
  • The new servlet's .init() creates an in-memory data structure that houses all of the known static files you have, plus their extensions, and mime-types.
  • The new servlet's .doGet() will handle any incoming requests by seeing if there is an acceptable resource to serve based on this in-memory lookup. Serve the actual content you want from this in-memory lookup.
  • Don't forget to support ETag, Ranged requests, Server response Cache, HTTP Cache, and Gzip.

Serving static files with jetty in karaf (outside of bundle)

The problem maybe related with pax-web version. In Karaf 3.0.5 it uses version Pax-web 3.2.6 which I have read (sorry I cannot find the link) had a bug related with serving static content.

I have tested @Achim's approach in Karaf 4.0.3 (Pax-web 4.2.3) and it works like charm.

Serving static files with embedded Jetty

Use a ResourceHandler instead of ServletContextHandler.

Unable to access static resources in JAR file using embedded Jetty

The code you have ...

String res = ApiServer.class.getClassLoader().getResource("res").toExternalForm();
context.setResourceBase(res);

Does not work for me, as you cannot use a classloader to obtain a directory reference, only file references. The call ClassLoader.getResource("res") always returns null.

This needs to be fixed first.

Next, your declaration of Jersey is exceedingly greedy.

ServletHolder jerseyServlet = context.addServlet(ServletContainer.class, "/*");
jerseyServlet.setInitOrder(0);

This means that Servlet (ServletContainer.class) is handling 100% of all requests, even requests for static content.

It is impossible for that Servlet, based on your url-pattern, to "not handle" static requests and let Jetty serve those static requests.

Relax this url-pattern, to say /api/* and then you'll be one step closer.

The final thing you need is a DefaultServlet (the component in the Servlet spec, and Jetty that serves static files).

So you'll wind up with the following code ...

ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
URL url = ApiServer.class.getClassLoader().getResource("res/index.html");
if (url == null)
throw new FileNotFoundException("Whoops, can't find static resources folder");
URI webroot = url.toURI().resolve("./");
context.setBaseResource(Resource.newResource(webroot));

ServletHolder jerseyServlet = context.addServlet(ServletContainer.class, "/api/*");
jerseyServlet.setInitOrder(0);

// Tells the Jersey Servlet which REST service/class to load.
String classes = new StringJoiner(",")
.add(MyClass1.class.getCanonicalName())
.add(MyClass2.class.getCanonicalName())
.toString();

jerseyServlet.setInitParameter(ServerProperties.PROVIDER_CLASSNAMES, classes);

// always named "default", always last, always on url-pattern "/"
ServletHolder defaultServ = new ServletHolder("default", DefaultServlet.class);
defaultServ.setInitParameter("dirAllowed","true");
context.addServlet(defaultServ,"/");

jettyServer = new Server(port);
jettyServer.setHandler(context);

Deploy Static content in jetty

Note: be careful with your DOCTYPE, what you have declared is from Jetty 7.x thru Jetty 8.x, and is not correct for Jetty 9.x

Don't mix ResourceHandler and WebAppContext / ServletContextHandler.

<?xml version="1.0"  encoding="ISO-8859-1"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN"
"http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/mail</Set>
<Set name="virtualHosts">
<Array type="java.lang.String">
<Item>apps.cairunet.ad.br</Item>
</Array>
</Set>
</Configure>

The most basic support is to not reference /ccmail in your <Configure>.

The fact that it exists as ${jetty.base}/webapps/ccmail/ is enough, that will deploy /ccmail as a static resource base for you.

BUT if you want to combine static resources with virtual hosts, then you can either use a WebAppContext with an alternate base, or a new ResourceHandler.

Example of alternate bases:
Serving static files from alternate path in embedded Jetty

Example of ResourceHandler usage:
https://www.eclipse.org/jetty/documentation/current/static-content-deployment.html

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN"
"http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
<Set name="contextPath">/ccmail</Set>
<Set name="handler">
<New class="org.eclipse.jetty.server.handler.ResourceHandler">
<Set name="resourceBase">/fully/qualified/path/to/my/jetty.base/webapps/ccmail</Set>
<Set name="directoriesListed">true</Set>
</New>
</Set>
<Set name="virtualHosts">
<Array type="java.lang.String">
<Item>apps.cairunet.ad.br</Item>
</Array>
</Set>
</Configure>


Related Topics



Leave a reply



Submit