Перейти к основному содержимому

Встроенный сервер Jetty на Java

· 4 мин. чтения

1. Обзор

В этой статье мы рассмотрим библиотеку Jetty . Jetty предоставляет веб-сервер, который может работать как встроенный контейнер и легко интегрируется с библиотекой javax.servlet .

2. Зависимости Maven

Для начала мы добавим зависимости Maven в библиотеки jetty -servlet и jetty-servlet :

<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.4.3.v20170317</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>9.4.3.v20170317</version>
</dependency>

3. Запуск Jetty Server с сервлетом

Запустить встроенный контейнер Jetty очень просто. Нам нужно создать экземпляр нового объекта Server и настроить его на запуск на заданном порту:

public class JettyServer {
private Server server;

public void start() throws Exception {
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(8090);
server.setConnectors(new Connector[] {connector});
}

Допустим, мы хотим создать конечную точку, которая будет отвечать кодом состояния HTTP 200, если все пойдет хорошо, и простой полезной нагрузкой JSON.

Мы создадим класс, расширяющий класс HttpServlet для обработки таких запросов; этот класс будет однопоточным и блокируется до завершения:

public class BlockingServlet extends HttpServlet {

protected void doGet(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("{ \"status\": \"ok\"}");
}
}

Далее нам нужно зарегистрировать класс BlockingServlet в объекте ServletHandler с помощью метода addServletWithMapping() и запустить сервер:

servletHandler.addServletWithMapping(BlockingServlet.class, "/status");
server.start();

Если мы хотим протестировать логику нашего сервлета, нам нужно запустить наш сервер, используя ранее созданный класс JettyServer , который является оболочкой фактического экземпляра сервера Jetty в тестовой настройке:

@Before
public void setup() throws Exception {
jettyServer = new JettyServer();
jettyServer.start();
}

После запуска мы отправим тестовый HTTP-запрос на конечную точку /status :

String url = "http://localhost:8090/status";
HttpClient client = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(url);

HttpResponse response = client.execute(request);

assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);

4. Неблокирующие сервлеты

Jetty имеет хорошую поддержку асинхронной обработки запросов.

Допустим, у нас есть огромный ресурс, который интенсивно использует ввод-вывод и требует много времени для загрузки, блокируя исполняемый поток в течение значительного промежутка времени. Лучше, если этот поток может быть освобожден для обработки других запросов в то же время, вместо ожидания некоторого ресурса ввода-вывода.

Чтобы обеспечить такую логику с помощью Jetty, мы можем создать сервлет, который будет использовать класс AsyncContext , вызвав метод startAsync() в HttpServletRequest. Этот код не будет блокировать исполняемый поток, но будет выполнять операцию ввода-вывода в отдельном потоке, возвращая результат, когда он будет готов, используя метод AsyncContext.complete() :

public class AsyncServlet extends HttpServlet {
private static String HEAVY_RESOURCE
= "This is some heavy resource that will be served in an async way";

protected void doGet(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

ByteBuffer content = ByteBuffer.wrap(
HEAVY_RESOURCE.getBytes(StandardCharsets.UTF_8));

AsyncContext async = request.startAsync();
ServletOutputStream out = response.getOutputStream();
out.setWriteListener(new WriteListener() {
@Override
public void onWritePossible() throws IOException {
while (out.isReady()) {
if (!content.hasRemaining()) {
response.setStatus(200);
async.complete();
return;
}
out.write(content.get());
}
}

@Override
public void onError(Throwable t) {
getServletContext().log("Async Error", t);
async.complete();
}
});
}
}

Мы записываем ByteBuffer в OutputStream , и как только весь буфер записан, мы сигнализируем, что результат готов вернуться к клиенту, вызывая метод complete() .

Далее нам нужно добавить AsyncServlet в качестве сопоставления сервлета Jetty:

servletHandler.addServletWithMapping(
AsyncServlet.class, "/heavy/async");

Теперь мы можем отправить запрос на конечную точку /heavy/async — этот запрос будет обработан Jetty асинхронно:

String url = "http://localhost:8090/heavy/async";
HttpClient client = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(url);
HttpResponse response = client.execute(request);

assertThat(response.getStatusLine().getStatusCode())
.isEqualTo(200);
String responseContent = IOUtils.toString(r
esponse.getEntity().getContent(), StandardCharsets.UTF_8);
assertThat(responseContent).isEqualTo(
"This is some heavy resource that will be served in an async way");

Когда наше приложение обрабатывает запросы асинхронно, мы должны явно настроить пул потоков. В следующем разделе мы настроим Jetty для использования пользовательского пула потоков.

5. Конфигурация причала

Когда мы запускаем наше веб-приложение в рабочей среде, мы можем захотеть настроить, как сервер Jetty обрабатывает запросы. Это делается путем определения пула потоков и применения его к нашему серверу Jetty.

Для этого у нас есть три параметра конфигурации, которые мы можем установить:

  • maxThreads — чтобы указать максимальное количество потоков, которые Jetty может создавать и использовать в пуле.
  • minThreads — для установки начального количества потоков в пуле, который будет использовать Jetty.
  • idleTimeout — это значение в миллисекундах определяет, как долго поток может бездействовать, прежде чем он будет остановлен и удален из пула потоков. Количество оставшихся потоков в пуле никогда не опустится ниже параметра minThreads .

С их помощью мы можем настроить встроенный сервер Jetty программно, передав настроенный пул потоков конструктору сервера :

int maxThreads = 100;
int minThreads = 10;
int idleTimeout = 120;

QueuedThreadPool threadPool = new QueuedThreadPool(maxThreads, minThreads, idleTimeout);

server = new Server(threadPool);

Затем, когда мы запускаем наш сервер, он будет использовать потоки из определенного пула потоков.

6. Заключение

В этом кратком руководстве мы увидели, как интегрировать встроенные серверы с Jetty, и протестировали наше веб-приложение.

Как всегда, код доступен на GitHub .