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

307 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Платформа Java EE
- Набор стандартов и спецификаций для создания корпоративных приложений на Java
- Спецификации Java EE реализуются серверами приложений
- Apache Tomcat
- Sun/Oracle GlassFish
- BEA/Oracle WebLogic
- IBM WebSphere
- RedHat JBoss
## Сервлеты
- [Серверные сценарии](Серверные%20сценарии.md) написанные на Java
- Жизненным циклов сервлетов управляет веб-контейнер (он же контейнер сервлетов)
- В отличие от [CGI](CGI.md) запросы обрабатываются в отдельных потоках (а не процессах) на веб-контейнере
## Преимущества
- Выполняются быстрее, чем CGI-сценарии
- Хорошая масштабируемость
- Надежность и безопасность
- Платформенно-независимы
- Множество инструментов мониторинга и отладки
## Недостатки
- Слабое разделение уровня представления и бизнес-логики
- Возможны конфликты при параллельной обработке запросов
## Обработка HTTP-запроса
1. Браузер формирует [HTTP](../Сеть%20и%20API/HTTP.md)-запрос и отправляет его на сервер
2. Веб-контейнер создает объекты `HttpServletRequest` и `HttpServletResponse`
3. Веб-контейнер вызывает метод `service` у сервлета
4. Сервлет формирует ответ и записывает его в поток вывода `HttpServletResponse`
## Пример сервлета
```Java
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` должны лежать скомпилированные джава-классы
```xml
<!-- Регистрируем сервлет -->
<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
<?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 и классы веб-контейнера
```xml
<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 контейнером создается свой контекст
```Java
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` до сервлетов
```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-сессии
```Java
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)`
```Java
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` передает управление следующему фильтру или целевому ресурсу, таким образом возможна реализация последовательностей фильтров
```Java
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() {}
}
```
Конфигурация похожа на конфигурацию сервлетов
```xml
<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>`
```Java
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
@WebServlet("/api/check")
public class CheckServlet extends HttpServlet {}
```
Продвинутый вариант
```Java
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>`
```Java
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");` - для всех сервлетов