Files
obsidian/Веб/Клиент-сервер/Java Servlets.md
T
roma-dxunvrs c513ce326f
deploy / Pull and Restart (push) Successful in 17s
web fix 2
2026-11-02 22:45:19 +03:00

15 KiB
Raw Blame History

Платформа Java EE

  • Набор стандартов и спецификаций для создания корпоративных приложений на Java
  • Спецификации Java EE реализуются серверами приложений
    • Apache Tomcat
    • Sun/Oracle GlassFish
    • BEA/Oracle WebLogic
    • IBM WebSphere
    • RedHat JBoss

Сервлеты

  • Серверные сценарии написанные на Java
  • Жизненным циклов сервлетов управляет веб-контейнер (он же контейнер сервлетов)
  • В отличие от CGI запросы обрабатываются в отдельных потоках (а не процессах) на веб-контейнере

Преимущества

  • Выполняются быстрее, чем CGI-сценарии
  • Хорошая масштабируемость
  • Надежность и безопасность
  • Платформенно-независимы
  • Множество инструментов мониторинга и отладки

Недостатки

  • Слабое разделение уровня представления и бизнес-логики
  • Возможны конфликты при параллельной обработке запросов

Обработка HTTP-запроса

  1. Браузер формирует HTTP-запрос и отправляет его на сервер
  2. Веб-контейнер создает объекты HttpServletRequest и HttpServletResponse
  3. Веб-контейнер вызывает метод service у сервлета
  4. Сервлет формирует ответ и записывает его в поток вывода HttpServletResponse

Пример сервлета

package sample.servlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class SampleServlet extends HttpServlet {
	@Override
	public void doGet(HttpServletRequest request, 
					HttpServletResponse response) 
					throws IOException {
		String pageTitle = "Пример сервлета";
		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		out.println("<html>");
		...
		out.println("</html>");
	}
}

Конфигурация сервлета

Конфигурирование происходит в WEB-INF/web.xml (есть конечно аннотации, но это слишком современно). В WEB-INF/classes должны лежать скомпилированные джава-классы

<!-- Регистрируем сервлет -->
<servlet>
	<servlet-name>SampleServlet</servlet-name>
	<servlet-class>org.itmo.sample.SampleServlet></servlet-class>
</servlet>

<!-- Маппинг -->
<servlet-mapping>
	<servlet-name>SampleServlet</servlet-name>
	<url-pattern>/sample</url-pattern>
</servlet-mapping>

Полный файл web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app>

	<!-- Регистрируем сервлет -->
	<servlet>
		<servlet-name>SampleServlet</servlet-name>
		<servlet-class>org.itmo.sample.SampleServlet></servlet-class>
	</servlet>
	
	<!-- Маппинг -->
	<servlet-mapping>
		<servlet-name>SampleServlet</servlet-name>
		<url-pattern>/sample</url-pattern>
	</servlet-mapping>

</web-app>

Жизненный цикл сервлета

  • Жизненным циклом управляет веб-контейнер
  • Методы, управляющие жизненным циклом, должен вызывать только веб-контейнер

1. Загрузка класса сервлета

Проверяются пути: WEB-INF/classes/, WEB-INF/lib/*.jar, стандартные классы Java SE и классы веб-контейнера

<servlet>
	<servlet-name>SampleServlet</servlet-name>
	<servlet-class>org.itmo.sample.SampleServlet></servlet-class>
	
	<!-- Этот параметр можно получить через унаследованный метод getInitParametr(paramName); -->
	<init-param>
		<param-name>say-hello</param-name>
		<param-value>true</param-value>
	</init-param>
</servlet>

2. Создание экземпляра сервлета

Создание через конструктор new SampleServlet()

3. Вызов метода init()

init(config) - принимает config

4. Обработка HTTP-запросов

На каждый запрос в новом потоке (на самом деле там пул потоков) вызывается метод service()

5. Уничтожение сервлета

Вызов метода destroy()

Контекст сервлетов

  • API, с помощью которого сервлет может взаимодействовать со своим контейнером
  • Доступ к методам осуществляется через интерфейс javax.servlet.ServletContext
  • У всех сервлетов внутри приложения общий контекст
  • В контекст можно помещать общую для всех сервлетов информацию (методы getAttribute и setAttribute)
  • Если приложение - распределенное, то на каждом экземпляре JVM контейнером создается свой контекст
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class DemoServlet extends HttpServlet {
	@Override
	public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		res.setContentType("text/html");
		PrintWriter pw = res.getWriter();
		
		ServletContext context = getServletContext();
		String driverName = context.getInitParameter("dname");
		pw.println("driver name: " + driverName);
		pw.close();
	}
}

В web.xml до сервлетов

<context-param>
	<param-name>dname</param-name>
	<param-value>org.postgresql.Driver</param-value>
</context-param>

HTTP-сессии

  • Напомним, что HTTP - stateless
  • javax.servlet.HttpSession - интерфейс, позволяющий идентифицировать конкретного клиента (браузер) при обработке множества HTTP-запросов от него
  • Экземпляр HttpSession создается при первом обращении клиента к предложению и сохраняется некоторое (настраиваемое) время после последнего обращения
  • Идентификатор сессии либо помещается в cookie, либо добавляется к URL. Если удалить этот идентификатор, то сервер создаст новую сессию
  • В экземпляр HttpSession можно помещать общую для этой сессии информацию (getAttribute и setAttribute)
  • Сессия привязана к конкретному приложению, у разных приложений - разные сессии
  • В распределенном окружении обеспечивается сохранение целостности данных в HTTP-сессии
public class SimpleSession extends HttpServlet {
	@Override
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();
		
		out.println("<html>");
		...
		out.println("session id: " + session.getId());
		out.println("session timeout: " + session.getMaxInactiveInterval());
		session.setMaxInactiveInterval(20*60);
		out.println("new session timeout: " + session.getMaxInactiveInterval());
		...
		out.println("</html>");
	}
}

Диспетчиризация запросов сервлетами

  • Сервлеты могут делегировать обработку запросов другим ресурсам (сервлетам, JSP, HTML-страницам)
  • Диспетчеризация осуществляется с помощью реализаций интерфейса javax.servlet.RequestDispatcher
  • Два способа получения RequestDispatcher - через ServletRequest (абсолютный или относительный URL) и ServletContext (только абсолютный URL)
  • Два способа делегирования - forward и include (в обоих случаях url не меняется)
    • forward - полностью отдает запрос другому сервлету или JSP-странице
    • include - включает в себя обработку запроса от другого сервлета или JSP-страницы
  • Полный редирект (меняется url в браузере, статус-код 302) - response.sendRedirect(url)
public class MyServlet extends HttpServlet {
	@Override
	public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		RequestDispatcher dispatcher = request.getRequestDispatcher("index.jsp");
		dispatcher.forward(request, response);
	}
}

Фильтры запросов

  • Фильтры позволяют осуществлять пред- и постобработку запросов до и после передачи их ресурсу (сервлету, JSP или HTML-странице)
  • Пример предобработки - допуск только авторизованных пользователей
  • Пример постобработки - запись в лог времени обработки запроса
  • Реализуют интерфейс javax.servlet.Filter
  • Ключевой метод doFilter
  • Метод doFilter класса FilterChain передает управление следующему фильтру или целевому ресурсу, таким образом возможна реализация последовательностей фильтров
public class MyFilter implements Filter {
	@Override
	public void init(FilterConfig arg0) throws ServletException {}
	
	@Override
	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
		PrintWrite out = resp.getWriter();
		out.print("filter is invoked before");
		
		chain.doFilter(req, resp); // sends to next resource
		
		out.print("filter is invoked after);
	}
	
	@Override
	public void destroy() {}
}

Конфигурация похожа на конфигурацию сервлетов

<web-app>
	<servlet>
		<servlet-name>s1</servlet-name>
		<servlet-class>HelloServlet</servlet-class>
	</servlet>
	
	<servlet-mapping>
		<servlet-name>s1</servlet-name>
		<url-pattern>/servlet1</url-pattern>
	</servlet-mapping>
	
	<filter>
		<filter-name>f1</filter-name>
		<filter-class>MyFilter</filter-class>
	</filter>
	
	<filter-mapping>
		<filter-name>f1</filter-name>
		<url-pattern>/servlet1</url-pattern>
	</filter-mapping>

</web-app>

[!important] Порядок сверху вниз, но если использовать аннотации, то порядок недетерминирован

Конфигурация аннотациями

Конфигурация можно писать прямо в Java коде. Tomcat сам сканирует классы и регистрирует компоненты

Сервлеты

@WebServlet - замена <servlet> и <servlet-mapping>

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;

@WebServlet("/api/check")
public class CheckServlet extends HttpServlet {}

Продвинутый вариант

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.annotation.WebInitParam;

@WebServlet(
    name = "MyCheckLogic",               // Аналог <servlet-name>
    urlPatterns = {"/api/check", "/run"},// Можно привязать сразу несколько URL!
    loadOnStartup = 1,                   // Запустить при старте сервера
    initParams = {                       // Замена <init-param>
        @WebInitParam(name = "adminEmail", value = "admin@itmo.ru"),
        @WebInitParam(name = "maxSize", value = "5000")
    }
)
public class CheckServlet extends HttpServlet { ... }

Фильтры

@WebFilter - замена <filter> и <filter-mapping>

import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.Filter;

@WebFilter(urlPatterns = "/*")
public class EncodingFilter implements Filter {}

Справка по параметрам/аттрибутам

Внешние данные от пользователя

  • getParameter()
  • Приходит от браузера/клиента по сети
  • Возвращает String или null
  • Находится в URL или в теле запроса
  • Приоритет у первого query параметра
  • String user = request.getParameter("red");
  • Если несколько String[] colors = request.getParameterValues("color");
  • Никакого set не существует

Внутренние данные приложения

  • getAttribute() и setAttribute()
  • Записывается Java-кодом на сервере. Браузер ничего не знает
  • Возвращает Object или null, поэтому требует явного каста
  • Внутри запроса HttpServletRequest - живет 1 запрос
    • Используется для передачи из Model в View
    • request.setAttribute("myUser", new User()); User u = (User) request.getAttribute("myUser");
  • Внутри сессии HttpSession - живет пока юзер на сайте
    • HttpSession session = request.getSession();
    • session.setAttribute("role", "ADMIN");
    • String role = (String) session.getAttribute("role");
  • Внутри контекста ServletContext - живет пока работает сервер
    • ServletContext context = getServletContext();
    • context.setAttribute() и context.getAttribute()

Метаданные HTTP

  • getHeader()
  • Приходит от браузера в служебных строках
  • Возвращает String или null
  • request.getHeader("Authorization");

Статические настройки из файла конфигурации

  • getInitParameter()
  • Читается сервером из файла web.xml или аннотаций
  • Возвращает String или null
  • String email = getServletConfig().getInitParameter("email"); - для сервлета
  • String dbUrl = getServletContext().getInitParameter("dbUrl"); - для всех сервлетов