RWT / RAP + Jetty + JAX-WS Integration
- Tutorial
When developing a RAP / RCP application, I had a problem: how to make custom error-pages and integrate them into embedded jetty. There are many tutorials on the Internet on how to do this if you embed Jetty in your application. But with RWT, things get more complicated. RWT itself launches Jetty and starts it as a managed service. At the same time, the Jetty interface itself is hidden from other bundles and it will not work directly with it.
I went a little further and decided to expand the functionality to execute any servlets with Jetty, which is built into the RWT application. And, most importantly, integrate JAX-WS web services there too. Sympathetic, welcome to cat.
It is implied that the application is launched from OSGi platform, I have it Equinox.
To begin with, it is known from the documentation that you can make some Jetty customizer and, passing the name of the system property, get some customization. So
let's do it: when starting the whole application, we ’ll pass -Dorg.eclipse.equinox.http.jetty.customizer.class = ru.futurelink.jetty.customizer.MyJettyCustomizer
customizer code:
Here we do the following:
1) Maple some servlets, according to certain URLs, now they will be available there, everything is simple here.
2) Install a custom handler for erroneous pages.
3) We add the JAX-WS web services service - more on that in the end, this is interesting;)
If everything is clear and simple with the first item - we write a servlet, map it to the URL and voila, it is available, then the second requires an example of the handler code, there he is:
Now we can process any HTTP response code in the way we need.
Now for the fun part, we are integrating JAX-WS into our RWT application. The difficulty here arises when implementing the web service itself. We need to make an XML description, which is placed in WEB-INF / sun-jaxws.xml .
But our application does not want to see this description file and create these very endpoints. And the standard solution that is proposed for JAX-WS for creating endpoints in runtime is not suitable, because it spawns another web server com.sun.net.httpserver . It is necessary to hang it on a separate port, and indeed, it is somehow clumsy. We need another solution that will use the existing Jetty to process requests for web services.
We have already added the servlet itself, which will process requests to the web service, now we need endpoints to work. However, since our project works in the OSGi framework, the file description that we just created JAX-WS does not see. The thing is that JAX-WS is not an OSGi-compatible bundle, although we repackaged it (I hope so?) Into a bundle from a simple JAR and added a manifest to it. You need to make him eat this file, while doing everything correctly in the style of OSGi.
We pack our entire customizer into a fragment to the org.eclipse.equinox.http.jetty host. This is necessary, as you know, the fragment extends the classpath, thereby allowing the host to find and load some custom parts from the classpath of the fragment, as if it were its own. We will force JAX-WS to use jetty as an HTTP transport, instead of the usual default com.sun.net.httpserver.
Next, we take several classes from JAX-WS (unfortunately, the architecture of this package does not allow us to inherit correctly and we will have to redefine everything), from com / sun / xml / ws / transport / http / servlet . Here is an example:
I don’t know why they did this ... now we have to rewrite a piece of JAX-WS to our fragment. Copied files can be found at github: github.com/futurelink/habrahabr
Total we need Class 4:
WSServletContextListener - MyServletContextListener,
ServletResourceLoader - MyServletResourceLoader,
ServletContainer - MyServletContainer,
DeploymentDescriptorParser - MyDeploymentDescriptorParser
All we need is for the sake of change was a single line of code in WSServletContextListener:
replaced by:
The problem is that now, with the deployment of the new version of JAX-WS, you need to follow this fragment so that it works normally. But I did not find another way to integrate into Jetty. If someone wants to go this way from the beginning, it will be interesting to read a different solution.
I went a little further and decided to expand the functionality to execute any servlets with Jetty, which is built into the RWT application. And, most importantly, integrate JAX-WS web services there too. Sympathetic, welcome to cat.
It is implied that the application is launched from OSGi platform, I have it Equinox.
To begin with, it is known from the documentation that you can make some Jetty customizer and, passing the name of the system property, get some customization. So
let's do it: when starting the whole application, we ’ll pass -Dorg.eclipse.equinox.http.jetty.customizer.class = ru.futurelink.jetty.customizer.MyJettyCustomizer
customizer code:
public class MyJettyCustomizer extends JettyCustomizer {
@Override
public Object customizeContext(Object context,
Dictionary settings) {
ServletContextHandler c = (ServletContextHandler) context;
// Сервлет, раздающий файлы
c.getServletHandler().addServletWithMapping(
MyFileServlet.class, "/files/*");
// Сервлет, обслуживающий веб-сервисы JAX-WS
// import com.sun.xml.ws.transport.http.servlet.WSServlet;
c.getServletHandler().addServletWithMapping(
WSServlet.class, "/service/mobile");
// Сервлет-обработчик страниц ошибок
ErrorHandler errorHandler = new MyJettyErrorHandler();
errorHandler.setShowStacks(true);
c.setErrorHandler(errorHandler);
// Добавляем веб-сервисы JAX-WS (самое интересное внизу)
c.getServletContext().getContextHandler().
addEventListener(new MyServletContextListener());
return c;
}
}
Here we do the following:
1) Maple some servlets, according to certain URLs, now they will be available there, everything is simple here.
2) Install a custom handler for erroneous pages.
3) We add the JAX-WS web services service - more on that in the end, this is interesting;)
If everything is clear and simple with the first item - we write a servlet, map it to the URL and voila, it is available, then the second requires an example of the handler code, there he is:
public class MyJettyErrorHandler extends ErrorHandler {
@Override
protected void handleErrorPage(HttpServletRequest request,
Writer writer, int code, String message) throws IOException {
if (code == 404) {
writer.write("No,no,no!!! This page does not exist!");
return;
}
super.handleErrorPage(request, writer, code, message);
}
}
Now we can process any HTTP response code in the way we need.
Now for the fun part, we are integrating JAX-WS into our RWT application. The difficulty here arises when implementing the web service itself. We need to make an XML description, which is placed in WEB-INF / sun-jaxws.xml .
But our application does not want to see this description file and create these very endpoints. And the standard solution that is proposed for JAX-WS for creating endpoints in runtime is not suitable, because it spawns another web server com.sun.net.httpserver . It is necessary to hang it on a separate port, and indeed, it is somehow clumsy. We need another solution that will use the existing Jetty to process requests for web services.
We have already added the servlet itself, which will process requests to the web service, now we need endpoints to work. However, since our project works in the OSGi framework, the file description that we just created JAX-WS does not see. The thing is that JAX-WS is not an OSGi-compatible bundle, although we repackaged it (I hope so?) Into a bundle from a simple JAR and added a manifest to it. You need to make him eat this file, while doing everything correctly in the style of OSGi.
We pack our entire customizer into a fragment to the org.eclipse.equinox.http.jetty host. This is necessary, as you know, the fragment extends the classpath, thereby allowing the host to find and load some custom parts from the classpath of the fragment, as if it were its own. We will force JAX-WS to use jetty as an HTTP transport, instead of the usual default com.sun.net.httpserver.
Next, we take several classes from JAX-WS (unfortunately, the architecture of this package does not allow us to inherit correctly and we will have to redefine everything), from com / sun / xml / ws / transport / http / servlet . Here is an example:
public final class WSServletContextListener {…}
I don’t know why they did this ... now we have to rewrite a piece of JAX-WS to our fragment. Copied files can be found at github: github.com/futurelink/habrahabr
Total we need Class 4:
WSServletContextListener - MyServletContextListener,
ServletResourceLoader - MyServletResourceLoader,
ServletContainer - MyServletContainer,
DeploymentDescriptorParser - MyDeploymentDescriptorParser
All we need is for the sake of change was a single line of code in WSServletContextListener:
static final String JAXWS_RI_RUNTIME = "/WEB-INF/sun-jaxws.xml";
replaced by:
static final String JAXWS_RI_RUNTIME = "/META-INF/sun-jaxws.xml";
The problem is that now, with the deployment of the new version of JAX-WS, you need to follow this fragment so that it works normally. But I did not find another way to integrate into Jetty. If someone wants to go this way from the beginning, it will be interesting to read a different solution.