Aiming to start Jetty locally with a webapp servlet and a proxy servlet both running, and other tools like deploy and console logging. All Jetty configuration is in XML files.
The proxy servlet will reverse-proxy GET
requests prefixed /media/*
to an external site https://example-server/
. So http://localhost:8080/media/image.jpg will pass through to https://media-server/image.jpg.
Here's an extract from my jetty.xml
:
<Set name="handler">
<New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
<Set name="handlers">
<Array type="org.eclipse.jetty.server.Handler">
<Item>
<New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
</Item>
<Item>
<New id="context" class="org.eclipse.jetty.servlet.ServletContextHandler">
<Arg><Get class="org.eclipse.jetty.servlet.ServletContextHandler" name="SESSIONS"/></Arg>
<Call name="setContextPath" arg="/"/>
<Set name="servletHandler">
<New id="handler" class="org.eclipse.jetty.servlet.ServletHandler">
<Call id="holder" name="addServletWithMapping" arg="org.eclipse.jetty.proxy.ProxyServlet$Transparent,/media/*">
<Call name="setInitParameter" arg="proxyTo,https://media-server"/>
<Call name="setInitParameter" arg="prefix,/media"/>
</Call>
</New>
</Set>
</New>
</Item>
<Item>
<New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
</Item>
</Array>
</Set>
</New>
</Set>
The above XML should be equivalent to this Java code.
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
ServletHandler handler = new ServletHandler();
ServletHolder holder = handler.addServletWithMapping(ProxyServlet$Transparent.class, "/media/*");
holder.setInitParameter("proxyTo", "https://media-server");
holder.setInitParameter("prefix", "/media");
context.setServletHandler(handler);
That is adapted from the default jetty.xml
and https://dzone.com/articles/configuring-jetty-servlet-proxy
From the DZone guide I've updated the class names for Jetty 9.x. So org.eclipse.jetty.servlets.ProxyServlet
is now org.eclipse.jetty.proxy.ProxyServlet
and the proxyTo
and prefix
parameters must start with a lowercase p
.
Checked that jetty-proxy-9.4.12.v20180830.jar
is included as a library in Jetty's startup configuration.
For logging, the Jetty command line includes -Dorg.eclipse.jetty.proxy.LEVEL=DEBUG
(I recommend this for anyone else troubleshooting ProxyServlet.)
The problem: nothing's happening. The
ProxyServlet
is not activating on GET
requests to `http://localhost:8080/media/image.jpg'.
Here's the log line showing the ServletContextHandler
is starting.
2018-09-28 15:26:46.045:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@1e028a9{"",null,AVAILABLE}
I guess there's simple solution to this, like setting the ServletContext correctly, but I can't figure out how to do that in XML, and would really appreciate some help. Jetty documentation on this is thin.
Now if I change the
jetty.xml
to this below then the proxy does activate on GET
requests to `http://localhost:8080/media/image.jpg'.
<Set name="handler">
<New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
<Set name="handlers">
<Array type="org.eclipse.jetty.server.Handler">
<Item>
<New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
</Item>
<Item>
<New id="handler" class="org.eclipse.jetty.servlet.ServletHandler">
<Call id="holder" name="addServletWithMapping" arg="org.eclipse.jetty.proxy.ProxyServlet$Transparent,/media/*">
<Call name="setInitParameter" arg="proxyTo,https://media-server"/>
<Call name="setInitParameter" arg="prefix,/media"/>
</Call>
</New>
</Item>
<Item>
<New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
</Item>
</Array>
</Set>
</New>
</Set>
The log shows the ProxyServlet activating like this:
2018-09-28 14:22:14.904:DBUG:oejpP.194a1b5:qtp22374712-13: org.eclipse.jetty.proxy.ProxyServlet$Transparent-194a1b5 @ null/media to https://media-server
2018-09-28 14:22:14.904:DBUG:oejpP.194a1b5:qtp22374712-13: 21964987 rewriting: http://localhost:8080/media/image.jpg -> null
But here the proxy fails because it has a null
context. So the prefix has been set (source code) to null/media
is because the ServletContext.getContextPath()
is null. And that causes a redirect to null
because of this source code. With _prefix
set to null/media
, nothing's going to match that. Possibly that's a bug, I've opened an issue.
Here's an example of using a ProxyServlet
from a ${jetty.base}
directory.
$ mkdir proxy-example-base
$ cd proxy-example-base/
$ java -jar ../../jetty-home-9.4.12.v20180830/start.jar --add-to-start=http,deploy,proxy
INFO : webapp transitively enabled, ini template available with --add-to-start=webapp
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : proxy initialized in ${jetty.base}/start.ini
INFO : security transitively enabled
INFO : servlet transitively enabled
INFO : http initialized in ${jetty.base}/start.ini
INFO : client transitively enabled
INFO : threadpool transitively enabled, ini template available with --add-to-start=threadpool
INFO : deploy initialized in ${jetty.base}/start.ini
MKDIR : ${jetty.base}/webapps
INFO : Base directory was modified
$ ls -la
total 8
drwxr-xr-x 4 joakim staff 136 Oct 1 14:10 ./
drwxr-xr-x 17 joakim staff 578 Oct 1 14:10 ../
-rw-r--r-- 1 joakim staff 2146 Oct 1 14:10 start.ini
drwxr-xr-x 3 joakim staff 102 Oct 1 14:11 webapps/
$ cp ~/Downloads/media-proxy.xml webapps/
$ cat webapps/media-proxy.xml
<?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.servlet.ServletContextHandler">
<Set name="contextPath">/</Set>
<Call name="addServlet">
<Arg>org.eclipse.jetty.proxy.ProxyServlet$Transparent.class</Arg>
<Arg>/media/*</Arg>
<Set name="InitOrder">1</Set>
<Call name="setInitParameter">
<Arg>proxyTo</Arg>
<Arg>https://media-server/</Arg>
</Call>
<Call name="setInitParameter">
<Arg>prefix</Arg>
<Arg>/media</Arg>
</Call>
</Call>
</Configure>
The way this works is that ${jetty.base}
is configured to have the proxy
jetty module enabled, which puts the proxy classes on the server classpath.
Then the deploy
jetty module is enabled to find configurations of webapps in the ${jetty.base}/webapps/
directory and deploy them.
Finally, an XML deployable is setup to have a javax.servlet.ServletContext
, at the /
contextPath, and one defined servlet, the ProxyServlet$Transparent
with some init-parameters.