При разработке RAP/RCP приложения у меня возникла проблема: как сделать кастомные error-pages и интегрировать их в embedded jetty. В интернете есть множество обучалок тому, как это сделать, если вы встраиваете Jetty в свое приложение. Но с RWT все сложнее. RWT сам занимается запуском Jetty и стартует его как managed service. При этом сам интерфейс Jetty скрыт от других бандлов и работать с ним напрямую не получится.
Я пошел немного дальше и решил расширить фнкциональность до исполнения любых сервлетов тем самым Jetty, который встроен в RWT приложение. И, самое главное интегрировать JAX-WS веб-сервисы туда же. Сочувствующим, добро пожаловать под кат.
Подраземевается, что приложение запускается из OSGi платфопмы, у меня это Equinox.
Для начала, из документации известно, что можно сделать некоторый Jetty customizer и, передав системным свойством его название, получить некоторую кастомизацию. Так и сделаем: при запуске всего приложения передадим
-Dorg.eclipse.equinox.http.jetty.customizer.class=ru.futurelink.jetty.customizer.MyJettyCustomizer
Код кастомайзера:
public class MyJettyCustomizer extends JettyCustomizer {
@Override
public Object customizeContext(Object context,
Dictionary<String, ?> 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;
}
}
Тут мы делаем следующее:
1) Мапим какие-то сервлеты, по определенным урлам, теперь они там будут доступны, тут все просто.
2) Устанавливаем кастомный обработчиик для ошибочных страниц.
3) Добавляем обслуживание веб-сервисов JAX-WS — об этом подробнее в конце, это интересно ;)
Если с первым пунктом все понятно и просто — пишем сервлет, мапим его на URL и вуаля, он доступен, то второй требует примера кода обработчика, вот он:
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);
}
}
Теперь можно обработать любой HTTP response code нужным нам способом.
Теперь самое интересное, интергируем JAX-WS в наше RWT прлиожение. Сложность тут возникает при реализации самого веб-сервиса. Нам надо сделать XML описание, которое разместить в WEB-INF/sun-jaxws.xml.
<?xml version="1.0" encoding="UTF-8"?>
<endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime" version="2.0">
<endpoint name="ClientService"
implementation="ru.futurelink.mo.mobile.server.ClientService"
url-pattern="/service/mobile"/>
</endpoints>
Но наше приложение никак не хочет видеть этот файл описание и создавать эти самые конечные точки. А стандартное решение, которое предлагается для JAX-WS для создания конечных точек в рантайме не подходит, т. к. порождает еще один веб-сервер com.sun.net.httpserver. Надо вешать его на отдельный порт, да и вообще, как-то коряво это. Надо другое решение, которое будет использовать уже существующий Jetty для обработки запросов к веб-сервисам.
Мы уже добавили сам сервлет, который будет обрабатывать запросы к веб-сервису, теперь надо чтобы заработали конечные точки (endpoints). Однако, так, как наш проект работает в OSGi фреймворке, то файл описание, который мы только что создали JAX-WS не видит. Все дело в том, что JAX-WS не является совместимым OSGi бандлом, хотя мы его перепаковали (надеюсь это так?) в бандл из простого JAR и добавили ему манифест. Надо заставить его скушать этот файл, при этом сделать все правильно в стиле OSGi.
Весь наш кастомайзер мы упаковываем во фрагмент к хосту org.eclipse.equinox.http.jetty. Это обязательно, как известно фрагмент расширяет classpath, тем самым позволяет хосту находить и загружать какие-то кастомные части из classpath фрагмента, как из своего собственного. Мы заставим JAX-WS использовать jetty как HTTP транспорт, вместо обычного дефолтового com.sun.net.httpserver.
Далее, берем несколько классов из JAX-WS (к сожалению, архитектура этого пакета не позволяет корректно наследоваться и нам придется переопределить все), из com/sun/xml/ws/transport/http/servlet. Вот пример:
public final class WSServletContextListener {…}
Не знаю зачем они так сделали… теперь нам придется переписать кусок JAX-WS к себе во фрагмент. Переписанные файлы можно найти в github: github.com/futurelink/habrahabr
Всего нам понадобится 4 класса:
WSServletContextListener — MyServletContextListener,
ServletResourceLoader — MyServletResourceLoader,
ServletContainer — MyServletContainer,
DeploymentDescriptorParser — MyDeploymentDescriptorParser
Все это нужно нам было ради изменения одной единственной строки кода в WSServletContextListener:
static final String JAXWS_RI_RUNTIME = "/WEB-INF/sun-jaxws.xml";
заменили на:
static final String JAXWS_RI_RUNTIME = "/META-INF/sun-jaxws.xml";
Проблема в том, что теперь, с равертыванием новой версии JAX-WS, надо следить и за этим фрагментом, чтобы он нормально работал. Но другого способа интегрироваться в Jetty я не обнаружил. Если кто-то захочет пройти этот путь с начала, будет интересно почитать иное решение.
Автор: futurelink