Интеграция RWT/RAP + Jetty + JAX-WS

в 4:51, , рубрики: Без рубрики

При разработке 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

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js