Skip to the content.

Веб-сервер на Java

Главная / Лекция 6. Веб-сервер на Java

Лекция 6. Веб-сервер на Java

Содержание

  1. Аннотации в Java
  2. Аспектно-ориентированное программирование (АОП)
  3. Библиотека Lombok в Java
  4. Record
  5. Работа с JSON
  6. TCP/IP протоколы
  7. HTTP
  8. REST API принципы
  9. Java Servlets
  10. Jetty Server

Аннотации в Java

Аннотации в Java — это специальные конструкции, которые предоставляют дополнительную информацию для компилятора, инструментов разработки и во время выполнения программы. Они не влияют на выполнение кода напрямую, но могут изменять процесс компиляции, генерации кода или поведение приложения в runtime.

Что такое аннотации?

Аннотации — это своего рода «метки» или «теги», которые можно применять к классам, методам, полям и другим элементам программы. Они начинаются с символа @ и могут содержать атрибуты, которые определяют их поведение.

Пример:

@Override
public void someMethod() {
    // тело метода
}

Зачем нужны аннотации?

  1. Упрощение разработки: аннотации помогают автоматизировать некоторые задачи, такие как генерация кода, проверка типов и т. д.
  2. Улучшение читаемости кода: они делают код более понятным, указывая на его назначение или особенности.
  3. Интеграция с инструментами разработки: многие IDE и инструменты сборки используют аннотации для предоставления дополнительных функций, таких как рефакторинг, анализ кода и т. п.
  4. Поддержка фреймворков и библиотек: аннотации часто используются в фреймворках и библиотеках для конфигурации и управления поведением компонентов.

Как написать свою аннотацию?

Для создания собственной аннотации необходимо использовать ключевое слово interface вместе с @interface. Внутри аннотации можно определить атрибуты, которые будут хранить дополнительную информацию.

Пример определения аннотации:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value() default "default value";
    int id();
}

Здесь:

С какими концепциями программирования связаны аннотации?

Аннотации в Java — это мощный инструмент для улучшения процесса разработки, повышения читаемости кода и интеграции с различными инструментами и фреймворками. Понимание того, как работают аннотации и как их использовать, позволяет разработчикам более эффективно создавать и поддерживать свои приложения.

Аспектно-ориентированное программирование (АОП)

Аспектно-ориентированное программирование (АОП) — это подход к написанию программ, который позволяет выделить и отдельно описать так называемые «поперечные concerns» или «аспекты». Это могут быть функции, которые пересекаются с основной логикой программы и применяются в разных её частях, например, логирование, обработка ошибок, управление транзакциями или безопасность.

Как работает АОП?

В АОП используются специальные конструкции — аннотации, которые указывают, где и как должен быть применён аспект. Например, вы можете определить аспект для логирования и указать, что он должен применяться ко всем методам, помеченным определённой аннотацией.

Это позволяет:

Пример

Рассмотрим простой пример, который иллюстрирует основные идеи аспектно-ориентированного программирования (АОП). Предположим, у нас есть программа, которая выполняет простые арифметические операции: сложение и умножение. Мы хотим добавить логирование времени выполнения каждой операции без изменения исходного кода этих операций.

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    public int multiply(int a, int b) {
        return a * b;
    }
}
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@interface LogExecutionTime {
}

public class Calculator {

    @LogExecutionTime
    public int add(int a, int b) {
        return a + b;
    }

    @LogExecutionTime
    public int multiply(int a, int b) {
        return a * b;
    }
}

Сам аспект мы не будем реализовывать - его можно реализовать с помощью AspectJ. AspectJ — это расширение языка Java, которое позволяет реализовывать аспектно-ориентированное программирование (АОП).

Библиотека Lombok в Java

Библиотека Lombok — это инструмент, который упрощает разработку на Java, автоматизируя создание шаблонного кода. Она позволяет сократить количество строк кода и сделать его более читаемым, устраняя необходимость вручную реализовывать стандартные методы, такие как геттеры, сеттеры, конструкторы и т. д.

Что такое Lombok?

Lombok — это библиотека с открытым исходным кодом, которая использует аннотации для генерации дополнительного кода во время компиляции. Это означает, что вместо того, чтобы писать много строк шаблонного кода, разработчики могут использовать аннотации Lombok, и библиотека автоматически сгенерирует необходимый код.

Зачем нужна Lombok?

Примеры использования Lombok

Генерация геттеров и сеттеров без Lombok:

public class Person {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

С lombok

import lombok.Getter;
import lombok.Setter;

public class Person {
    @Getter @Setter
    private String name;
    @Getter @Setter
    private int age;
}

Как добавить в проект:

plugins {
    id 'io.franzbecker.gradle-lombok' version '5.0.0'
    id 'java'
}

lombok {
    version = '1.18.26'
    sha256 = ""
}

Возможности

@Getter @Setter

@Getter
@Setter
public class Author {
    private int id;
    private String name;
    @Setter(AccessLevel.PROTECTED)
    private String surname;
}

public class Author {
    private int id;
    private String name;
    private String surname;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    protected void setSurname(String surname) {
        this.surname = surname;
    }
}

@NoArgsConstructor - создает дефолтный конструктор @AllArgsConstructor - создает конструктор, где все поля класса - аргументы

@NoArgsConstructor
@AllArgsConstructor
public class Author {
    private int id;
    private String name;
    private String surname;
    private final String birthPlace;
}

public class Author {
    private int id;
    private String name;
    private String surname;
    private final String birthPlace;

    // @NoArgsConstructor
    public Author() {}

    // @AllArgsConstructor
    public Author(int id, String name, String surname, String birthPlace) {
        this.id = id
        this.name = name
        this.surname = surname
        this.birthPlace = birthPlace
    }

    // @RequiredArgsConstructor
    public Author(String birthPlace) {
        this.birthPlace = birthPlace
    }
}

@ToString - создает метод ToString

@ToString(includeFieldNames=true)
public class Author {
    private int id;
    private String name;
    private String surname;
}

public class Author {
    private int id;
    private String name;
    private String surname;

    @Override
    public String toString() {
        return "Author(id=" + this.id + ", name=" + this.name + ", surnname=" + this.surname + ")";
    }
}
@Getter
@Setter
@EqualsAndHashCode
public class Author {
    private int id;
    private String name;
    private String surname;
}

public class Author {

    // геттеры и сеттеры ...

    @Override
    public int hashCode() {
        final int PRIME = 31;
        int result = 1;
        result = prime * result + id;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + ((surname == null) ? 0 : surname.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof Author)) return false;
        Author other = (Author) o;
        if (!other.canEqual((Object)this)) return false;
        if (this.getId() == null ? other.getId() != null : !this.getId().equals(other.getId())) return false;
        if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
        if (this.getSurname() == null ? other.getSurname() != null : !this.getSurname().equals(other.getSurname())) return false;
        return true;
    }
}

@Data - сокращенная аннотация сочетающая @ToString, @EqualsAndHashCode, @Getter @Setter и @RequiredArgsConstructor.

@Getter
@Builder
public class Student {
    private String firstName;
    private String lastName;
    private Long studentId;
    private String email;
    private String phoneNumber;
    private String address;
    private String country;
    private int age;
}

class Temp {
    Student john = Student.builder()
            .firstName("John")
            .lastName("Doe")
            .email("john@doe.com")
            .country("England")
            .age(20)
            .build();
}

Плюсы Lombok

Недостатки Lombok

Полезные ссылки

Статья Lombok. Полное руководство

Статья Lombok возвращает величие Java

Статья Lombok: хорошее и плохое применение

Record

Record — это относительно новая конструкция в языке Java, которая упрощает создание неизменяемых данных (immutable data). Она появилась в Java 16 как часть усилий по упрощению и улучшению языка.

Зачем нужны Record?

Когда появились Record?

Record были введены в Java 16 в марте 2021 года как часть проекта Amber, направленного на улучшение и упрощение языка Java.

Как раскрываются Record после компиляции?

После компиляции Record раскрывается в обычный класс с некоторыми автоматически сгенерированными методами:

Пример записи:

public record Person(String name, int age) {
    // автоматически генерируются геттеры, equals, hashCode и toString
}

Компилятор преобразует этот код в полноценный класс с соответствующими методами, что позволяет разработчикам сосредоточиться на логике приложения, а не на реализации базовых функциональностей.

Пример того, как будет выглядеть класс Person, созданный с использованием record, после компиляции в обычный класс Java:

import java.util.Objects;

public final class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String name() {
        return name;
    }

    public int age() {
        return age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{" + "name=" + name + ", age=" + age + '}';
    }
}

Наследоваться от Record в Java нельзя, поскольку Record по умолчанию является final классом. Это сделано для обеспечения неизменяемости и простоты использования, что является одной из ключевых особенностей Record. Запрет на наследование помогает предотвратить потенциальные проблемы, связанные с нарушением инвариантов и изменением состояния объекта, что противоречит концепции неизменяемых данных.

Работа с JSON

JSON (JavaScript Object Notation) — это формат обмена данными, который используется для хранения и передачи структурированной информации. Он основан на синтаксисе объектов JavaScript и представляет данные в виде текстовых объектов, что делает его лёгким для чтения как для человека, так и для машины. JSON широко применяется в веб-разработке для обмена данными между сервером и клиентом, а также между различными приложениями.

С помощью JSON можно отправлять данные с сервера на веб-страницу.

Пример:

{
  "name": "yandex",
  "id": 0
}

JSON обладает несколькими ключевыми особенностями:

В Java существует несколько подходов к работе с JSON:

Парсинг JSON:

Сериализация / десериализация объектов:

Json Simple

Json Simple — это библиотека для работы с JSON в Java, которая предоставляет простой и удобный способ парсить JSON-данные, а также создавать и манипулировать JSON-объектами.

Основные возможности Json Simple:

Конечно нам потребуется зависимость в gradle-файле (maven)

implementation 'com.googlecode.json-simple:json-simple:1.1'

Мы будем работать с 2-мя основным классами:

class Temp {
    public static String buildWeatherJson() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name", "Yandex");
        jsonObject.put("id", "0");

        return jsonObject.toJSONString();
    }

    public static void parseCurrentWeatherJson(String resultJson) {
        try {
            // конвертируем строку с Json в JSONObject для дальнейшего его парсинга
            JSONObject companyJsonObject = (JSONObject) JSONValue.parseWithException(resultJson);

            // получаем название города, для которого смотрим погоду
            System.out.println("Название компании: " + companyJsonObject.get("name"));

            // получаем массив элементов для поля weather
            /* ... "weather": [
            {
                "id": 500,
                    "main": "Rain",
                    "description": "light rain",
                    "icon": "10d"
            }
            ], ... */
            JSONArray weatherArray = (JSONArray) weatherJsonObject.get("weather");
            // достаем из массива первый элемент
            JSONObject weatherData = (JSONObject) weatherArray.get(0);

            // печатаем текущую погоду в консоль
            System.out.println("Погода на данный момент: " + weatherData.get("main"));
            // и описание к ней
            System.out.println("Более детальное описание погоды: " + weatherData.get("description"));

        } catch (org.json.simple.parser.ParseException e) {
            e.printStackTrace();
        }
    }
}

Jackson

Jackson — это популярная библиотека для работы с JSON в Java, которая предоставляет мощные возможности для сериализации и десериализации объектов. Она позволяет преобразовывать Java-объекты в JSON-формат и обратно, что упрощает обмен данными между приложениями и хранение информации.

Основные возможности Jackson:

Подключение в gradle

implementation 'com.fasterxml.jackson.core:jackson-core:2.10.1'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.10.1'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.1'
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
    private String firstName;
    private String lastName;
    private int age;
}

public class JacksonExample {
    static ObjectMapper objectMapper = new ObjectMapper();

    public static void main(String[] args) {
        Employee employee = new Employee("Mark", "James", 20);

        try {
            String json = objectMapper.writeValueAsString(employee);
            System.out.println(json);
        } catch (JsonProcessingException e) {
            throw new RuntimeException();
        }

        try {
            Employee employee1 = objectMapper.readValue(employeeJson, Employee.class);
            System.out.println(employee.getFirstName());
            System.out.println(employee.getLastName());
            System.out.println(employee.getAge());
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }
}

Иногда документ JSON представляет собой не объект, а список объектов. Давайте посмотрим, как можно его прочитать.

class Temp {
    public static void main(String[] args) {
        File file = new File("src/test/resources/employeeList.json");
        List<Employee> employeeList = objectMapper.readValue(file, new TypeReference<>(){});

        assertThat(employeeList).hasSize(2);
        assertThat(employeeList.get(0).getAge()).isEqualTo(33);
        assertThat(employeeList.get(0).getLastName()).isEqualTo("Simpson");
        assertThat(employeeList.get(0).getFirstName()).isEqualTo("Marge");
    }
}

У Jackson есть свои аннотации: @JsonSetter @JsonGetter @JsonIgnore

@JsonAnySetter

public class Car {  
    @JsonSetter("car_brand")  
    private String carBrand;  
    private Map<String, String> unrecognizedFields = new HashMap<>();  
  
    @JsonAnySetter  
    public void allSetter(String fieldName, String fieldValue) {  
        unrecognizedFields.put(fieldName, fieldValue);  
    }  
}

TCP/IP протоколы

Предисловие 1. Немного про TCP-IP

Как же нам из приложения отправить запрос на сервер, использую эти ваши интернеты?

На примере доставки подарочной плитки шоколадки:

Какие же есть протоколы? Об этом вам подробнее расскажут на Вычислительных системах и компьютерных сетях. Если вкратце - есть различные протоколы, которые определяют различные действия на различных этапов передачи данных между программами.

Так сложилось, что есть две модели, описывающие уровни протоколов. Одна из них теоретическая – модель OSI, а другая практическая – TCP/IP.

Таким образом работа протоколов на пути нашего сообщения идет следующим образом:

HTTP

Что такое HTTP?

HTTP (HyperText Transfer Protocol) — это протокол прикладного уровня, который используется для передачи данных в веб-приложениях. Он работает по принципу клиент-серверной архитектуры, где клиент отправляет запросы, а сервер возвращает ответы.

Основные характеристики HTTP

Структура HTTP запроса

HTTP запрос состоит из следующих элементов:

  1. Стартовая строка (Request Line): Метод + URI + версия HTTP
  2. Заголовки (Headers): Метаданные запроса
  3. Пустая строка: Разделитель между заголовками и телом
  4. Тело запроса (Body): Данные (опционально)

Структура HTTP ответа

HTTP ответ состоит из следующих элементов:

  1. Строка статуса (Status Line): Версия HTTP + код статуса + сообщение
  2. Заголовки (Headers): Метаданные ответа
  3. Пустая строка: Разделитель между заголовками и телом
  4. Тело ответа (Body): Данные (опционально)

HTTP методы

Основные HTTP методы, используемые для работы с ресурсами:

Коды ответов HTTP

HTTP использует стандартные коды ответов для индикации результата операции:

Пример HTTP запроса и ответа

Запрос:

GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html

Ответ:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234

<!DOCTYPE html>
<html>
<head><title>Example</title></head>
<body>Hello, World!</body>
</html>

REST API

REST (Representational State Transfer) — это архитектурный стиль для построения распределённых систем, особенно веб-сервисов. REST API — это API, который следует принципам REST.

Основные принципы REST

  1. Клиент-серверная архитектура: Клиент и сервер разделены, что позволяет им развиваться независимо друг от друга.

  2. Отсутствие состояния (Stateless): Каждый запрос от клиента должен содержать всю информацию, необходимую для понимания и обработки запроса. Сервер не хранит информацию о состоянии клиента между запросами.

  3. Кэшируемость: Ответы от сервера должны быть явно помечены как кэшируемые или нет, что позволяет улучшить производительность.

  4. Единый интерфейс: Унифицированный интерфейс между компонентами упрощает архитектуру и позволяет каждой части развиваться независимо.

  5. Слоистая система: Архитектура может состоять из нескольких слоёв, каждый из которых выполняет свою функцию.

  6. Код по требованию (Code on Demand): Сервер может расширять функциональность клиента, передавая ему исполняемый код (необязательный принцип).

Ресурсы и URI

В REST всё является ресурсом. Каждый ресурс имеет уникальный идентификатор — URI (Uniform Resource Identifier).

Примеры URI для ресурсов:

HTTP методы в REST

REST использует стандартные HTTP методы для выполнения операций над ресурсами:

GET

Примеры:

GET /users              — получить всех пользователей
GET /users/123          — получить пользователя с ID 123
GET /users/123/orders   — получить заказы пользователя 123

POST

Примеры:

POST /users
{
  "name": "Ivan",
  "email": "ivan@example.com"
}
— создать нового пользователя

PUT

Примеры:

PUT /users/123
{
  "id": 123,
  "name": "Ivan Updated",
  "email": "ivan.updated@example.com"
}
— полностью обновить пользователя 123

PATCH

Примеры:

PATCH /users/123
{
  "email": "new.email@example.com"
}
— обновить только email пользователя 123

DELETE

Примеры:

DELETE /users/123  — удалить пользователя 123

Идемпотентность

Идемпотентность — это свойство операции, при котором многократное выполнение одной и той же операции даёт тот же результат, что и однократное выполнение.

Почему идемпотентность важна?

  1. Надёжность в сети: В распределённых системах запросы могут повторяться из-за сетевых проблем, таймаутов или ошибок. Идемпотентные операции гарантируют, что повторные запросы не приведут к нежелательным побочным эффектам.

  2. Безопасность: Предотвращает случайное дублирование операций (например, двойное списание денег с счёта).

  3. Предсказуемость: Поведение системы становится более предсказуемым и отлаживаемым.

Идемпотентность HTTP методов:

Метод Идемпотентный Объяснение
GET ✅ Да Множественные запросы возвращают одни и те же данные
POST ❌ Нет Каждый запрос создаёт новый ресурс
PUT ✅ Да Замена ресурса одним и тем же содержимым даёт тот же результат
PATCH ❌ Нет (обычно) Зависит от операции (например, инкремент не идемпотентен)
DELETE ✅ Да После первого удаления последующие не изменят состояние

Примеры идемпотентности:

Идемпотентная операция (PUT):

# Первый запрос
PUT /accounts/123
{
  "balance": 1000
}
# Результат: баланс = 1000

# Второй запрос (повтор)
PUT /accounts/123
{
  "balance": 1000
}
# Результат: баланс = 1000 (тот же результат)

Не идемпотентная операция (POST):

# Первый запрос
POST /orders
{
  "userId": 123,
  "amount": 100
}
# Результат: создан заказ с ID 1

# Второй запрос (повтор)
POST /orders
{
  "userId": 123,
  "amount": 100
}
# Результат: создан заказ с ID 2 (другой результат!)

Не идемпотентная операция (PATCH с инкрементом):

# Первый запрос
PATCH /accounts/123
{
  "operation": "increment",
  "value": 100
}
# Результат: баланс = 1100 (было 1000)

# Второй запрос (повтор)
PATCH /accounts/123
{
  "operation": "increment",
  "value": 100
}
# Результат: баланс = 1200 (другой результат!)

Коды ответов HTTP

REST API использует стандартные коды ответов HTTP для указания результата операции:

2xx — Успешные операции

4xx — Ошибки клиента

5xx — Ошибки сервера

Форматы данных

REST API обычно использует JSON для обмена данными:

Пример ответа (GET /users/123):

{
  "id": 123,
  "name": "Ivan",
  "email": "ivan@example.com",
  "createdAt": "2024-01-15T10:30:00Z",
  "_links": {
    "self": "/users/123",
    "orders": "/users/123/orders"
  }
}

Пример запроса (POST /users):

{
  "name": "Petr",
  "email": "petr@example.com"
}

Версионирование API

Существует несколько подходов к версионированию REST API:

  1. Версия в URL:
    /api/v1/users
    /api/v2/users
    
  2. Версия в заголовке:
    Accept: application/vnd.myapi.v1+json
    
  3. Версия в параметрах запроса:
    /users?version=1
    

Пример полного REST API

Рассмотрим пример REST API для управления пользователями:

GET    /users              — получить всех пользователей
GET    /users/{id}         — получить пользователя по ID
POST   /users              — создать нового пользователя
PUT    /users/{id}         — полностью обновить пользователя
PATCH  /users/{id}         — частично обновить пользователя
DELETE /users/{id}         — удалить пользователя

GET    /users/{id}/orders  — получить заказы пользователя
POST   /users/{id}/orders  — создать заказ для пользователя

Примеры запросов и ответов:

  1. Создание пользователя: ```bash POST /users Content-Type: application/json

{ “name”: “Anna”, “email”: “anna@example.com” }

Ответ: 201 Created Location: /users/456

{ “id”: 456, “name”: “Anna”, “email”: “anna@example.com”, “createdAt”: “2024-01-15T10:30:00Z” }


2. Получение пользователя:
```bash
GET /users/456

Ответ:
200 OK

{
  "id": 456,
  "name": "Anna",
  "email": "anna@example.com",
  "createdAt": "2024-01-15T10:30:00Z"
}
  1. Обновление пользователя: ```bash PUT /users/456 Content-Type: application/json

{ “id”: 456, “name”: “Anna Updated”, “email”: “anna.updated@example.com” }

Ответ: 200 OK

{ “id”: 456, “name”: “Anna Updated”, “email”: “anna.updated@example.com”, “createdAt”: “2024-01-15T10:30:00Z”, “updatedAt”: “2024-01-15T11:00:00Z” }


4. Удаление пользователя:
```bash
DELETE /users/456

Ответ:
204 No Content
  1. Ошибка при попытке получить несуществующего пользователя: ```bash GET /users/999

Ответ: 404 Not Found

{ “error”: “User not found”, “code”: “USER_NOT_FOUND”, “details”: “User with ID 999 does not exist” }


### Лучшие практики REST API

1. **Используйте существительные во множественном числе для ресурсов:**
   - ✅ `/users`, `/products`, `/orders`
   - ❌ `/user`, `/getUsers`, `/getAllUsers`

2. **Используйте правильные HTTP методы:**
   - GET для получения данных
   - POST для создания
   - PUT для полного обновления
   - PATCH для частичного обновления
   - DELETE для удаления

3. **Возвращайте правильные коды ответов:**
   - 201 для создания ресурса
   - 200/204 для успешных операций
   - 400 для ошибок клиента
   - 404 для несуществующих ресурсов
   - 500 для ошибок сервера

4. **Используйте пагинацию для больших коллекций:**

GET /users?page=1&limit=20


5. **Поддерживайте фильтрацию, сортировку и поиск:**

GET /users?name=Ivan&sort=createdAt:desc


6. **Включайте метаданные в ответы:**
   ```json
   {
     "data": [...],
     "meta": {
       "total": 100,
       "page": 1,
       "limit": 20
     }
   }
  1. Обеспечивайте идемпотентность там, где это возможно:
    • Используйте PUT вместо POST для обновлений
    • Добавляйте уникальные идентификаторы для операций
    • Реализуйте механизмы обработки повторных запросов
  2. Документируйте ваш API:
    • Используйте OpenAPI/Swagger
    • Описывайте все эндпоинты, параметры и ответы
    • Приводите примеры запросов и ответов

Java Servlets

Java Servlets — это Java-классы, которые расширяют возможности серверов, обрабатывая запросы и генерируя ответы. Они являются основой для создания веб-приложений на Java и работают на стороне сервера.

Что такое сервлеты?

Сервлеты — это Java-компоненты, которые обрабатывают HTTP-запросы и генерируют HTTP-ответы. Они работают в контейнере сервлетов (например, Tomcat, Jetty), который управляет их жизненным циклом.

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

  1. Загрузка и инициализация: Контейнер загружает класс сервлета и вызывает метод init()
  2. Обработка запросов: Для каждого запроса вызывается метод service(), который делегирует обработку соответствующим методам (doGet(), doPost() и т.д.)
  3. Уничтожение: При остановке контейнера вызывается метод destroy()

Пример простого сервлета

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloServlet extends HttpServlet {
    
    @Override
    public void init() throws ServletException {
        // Инициализация сервлета
    }
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        
        try {
            out.println("<html>");
            out.println("<head><title>Hello Servlet</title></head>");
            out.println("<body>");
            out.println("<h1>Hello, World!</h1>");
            out.println("<p>Current time: " + new java.util.Date() + "</p>");
            out.println("</body>");
            out.println("</html>");
        } finally {
            out.close();
        }
    }
}

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

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    
    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>HelloServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
    
</web-app>

Jetty Server

Jetty — это легковесный, высокопроизводительный веб-сервер и сервлет-контейнер, написанный на Java. Он часто используется для встраивания веб-серверов в приложения и для разработки.

Основные особенности Jetty

Пример встраиваемого Jetty сервера

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

public class EmbeddedJettyServer {
    
    public static void main(String[] args) throws Exception {
        // Создаем сервер на порту 8080
        Server server = new Server(8080);
        
        // Создаем контекст сервлетов
        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.setContextPath("/");
        
        // Добавляем сервлет
        context.addServlet(new ServletHolder(new HelloServlet()), "/hello");
        
        // Устанавливаем обработчик для сервера
        server.setHandler(context);
        
        // Запускаем сервер
        server.start();
        server.join();
    }
}

Конфигурация Maven для Jetty

<dependencies>
    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-server</artifactId>
        <version>11.0.15</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-servlet</artifactId>
        <version>11.0.15</version>
    </dependency>
</dependencies>

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

  1. Простота использования: Легко настраивается и запускается
  2. Модульность: Можно использовать только необходимые компоненты
  3. Асинхронная обработка: Поддержка асинхронных запросов
  4. WebSocket поддержка: Встроенная поддержка WebSocket
  5. Активное сообщество: Постоянное развитие и поддержка

Полезные ссылки

Очень классная статья про TCP и UDP

Про сервлеты

Еще про сервлеты

Простая веб-служба со встроенным Jetty

Jetty tutorial for begginers

Парсинг JSON с помощью Jackson