HTTP-сервер — программа, которая принимает TCP-соединения, разбирает по ним HTTP-запросы и отправляет ответы. В Java это доступно на трех уровнях: сырой Socket, встроенный com.sun.net.httpserver.HttpServer и сервлет-контейнер под фреймворком. Дальше — рабочий сервер на первых двух уровнях и учебный чат поверх HttpServer, с проговоренным результатом каждого запуска.
Содержание
Три уровня HTTP-сервера в Java: не путать термины
Эти понятия часто смешивают, хотя они решают разные задачи:
- Сокет (
java.net.ServerSocket) — только транспорт, поток байт по TCP. HTTP разбираете сами: строку запроса, заголовки, тело. - HttpServer (
com.sun.net.httpserver.HttpServer) — минимальная HTTP-обвязка из состава JDK. Разбирает запрос сам, роутинг — по префиксу пути (createContext). - Сервлет (
HttpServlet) — контракт для контейнера вроде Tomcat: жизненный цикл, сессии, фильтры. - Фреймворк (Spring Boot и аналоги) — надстройка над контейнером или Netty: аннотации, DI, готовые интеграции.
| Уровень | Что дает | Что реализуете сами | Пример |
|---|---|---|---|
| Socket | TCP-поток | Разбор HTTP, keep-alive | new ServerSocket(8002) |
| HttpServer | Разбор HTTP, роутинг | Логику в обработчике | HttpServer.create(...) |
| Servlet + контейнер | Сессии, фильтры | Класс-обработчик | HttpServlet.doGet() |
| Spring Boot | Аннотации, DI | Контроллеры | @RestController |
Дальше — первые два уровня на коде: они в составе JDK и лучше всего показывают, что делает «сервер» под капотом.
Минимальный сервер на HttpServer
HttpServer — часть модуля jdk.httpserver, доступен в любом JDK без Maven и Gradle. Полный рабочий пример:
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executors;
public class SimpleHttpServer {
public static void main(String[] args) throws IOException {
HttpServer server = HttpServer.create(new InetSocketAddress("localhost", 8001), 0);
server.createContext("/hello", new HelloHandler());
server.setExecutor(Executors.newFixedThreadPool(10));
server.start();
System.out.println("Сервер запущен: http://localhost:8001/hello");
}
static class HelloHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
byte[] body = "Привет от HttpServer!".getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200, body.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(body);
}
}
}
}
Запуск без сборщика: javac SimpleHttpServer.java, затем java SimpleHttpServer. Запрос curl http://localhost:8001/hello вернет тело Привет от HttpServer! с кодом 200.
Второй аргумент HttpServer.create(addr, 0) — размер очереди ожидающих соединений (backlog); 0 значит «системное значение по умолчанию», а не «без очереди». createContext направляет запросы по префиксу пути к обработчику, а setExecutor задает пул потоков — без него сервер обрабатывает запросы по очереди в один поток. В handle() тело пишется в OutputStream, который обязательно закрывается — иначе клиент не получит ответ целиком.
Тот же сервер на сыром Socket
Чтобы увидеть, что именно берет на себя HttpServer, вот эквивалент на голом ServerSocket, вручную собирающий HTTP-ответ:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class SimpleSocketServer {
public static void main(String[] args) throws IOException {
try (ServerSocket serverSocket = new ServerSocket(8002)) {
System.out.println("Сокет-сервер слушает порт 8002");
while (true) {
Socket client = serverSocket.accept();
handle(client);
}
}
}
private static void handle(Socket client) throws IOException {
try (client;
BufferedReader in = new BufferedReader(
new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
OutputStream out = client.getOutputStream()) {
String requestLine = in.readLine();
System.out.println("Запрос: " + requestLine);
String body = "Привет от сырого сокета!";
byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
String response = "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain; charset=utf-8\r\n"
+ "Content-Length: " + bodyBytes.length + "\r\n"
+ "\r\n" + body;
out.write(response.getBytes(StandardCharsets.UTF_8));
}
}
}
Запуск: javac SimpleSocketServer.java && java SimpleSocketServer. Запрос curl http://localhost:8002/ выведет Привет от сырого сокета!, в консоли появится Запрос: GET / HTTP/1.1.
Граница: код читает только первую строку запроса и не разбирает заголовки и тело — для POST пришлось бы вручную вычитывать Content-Length. Именно эту рутину и берет на себя HttpServer.
Учебный чат поверх HttpServer
Чат — тот же HTTP-сервер с двумя обработчиками: прием сообщения и отдача истории, без WebSocket:
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
public class ChatServer {
private static final List<String> messages = new CopyOnWriteArrayList<>();
public static void main(String[] args) throws IOException {
HttpServer server = HttpServer.create(new InetSocketAddress("localhost", 8003), 0);
server.createContext("/chat/send", ChatServer::handleSend);
server.createContext("/chat/history", ChatServer::handleHistory);
server.setExecutor(Executors.newFixedThreadPool(10));
server.start();
System.out.println("Чат-сервер: http://localhost:8003");
}
private static void handleSend(HttpExchange exchange) throws IOException {
if (!"POST".equals(exchange.getRequestMethod())) {
exchange.sendResponseHeaders(405, -1);
return;
}
String text = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
messages.add(text);
respond(exchange, "ok");
}
private static void handleHistory(HttpExchange exchange) throws IOException {
respond(exchange, String.join("\n", messages));
}
private static void respond(HttpExchange exchange, String text) throws IOException {
byte[] body = text.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200, body.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(body);
}
}
}
Проверка: curl -X POST --data "Привет всем" http://localhost:8003/chat/send вернет ok, а curl http://localhost:8003/chat/history после этого вернет Привет всем. Это модель с опросом (polling): клиент сам периодически запрашивает /chat/history, сервер не «толкает» сообщения сам. Мгновенную доставку без опроса дает WebSocket — отдельный протокол, здесь не разбираю.
Где проходит граница: почему это не прод-сервер
HttpServer и голый Socket подходят для инструментов, тестов и учебных задач, но не для прод-нагрузки: без setExecutor обработка идет в один поток, нет TLS из коробки (нужен HttpsServer с SSLContext), нет таймаутов и лимитов на тело запроса, а история чата живет только в памяти процесса без аутентификации.
Решается это контейнером (Tomcat, Jetty) или фреймворком со встроенным сервером (Spring Boot), обычно за reverse-proxy вроде nginx, который берет TLS и часть лимитов на себя.
Выводы
- HTTP-сервер в Java существует на трех уровнях: сырой Socket, встроенный HttpServer из JDK, сервлет-контейнер/фреймворк — у каждого своя зона ответственности.
HttpServerразбирает HTTP за вас без внешних зависимостей, но без явногоsetExecutorработает в один поток.- Учебный чат — два HTTP-обработчика (прием и выдача истории) поверх HttpServer, без WebSocket, через опрос, а не мгновенную доставку.
- Ни Socket-, ни HttpServer-вариант не готовы к продакшену: нет TLS, лимитов и персистентности из коробки.
Где применяется / связь с практикой
Освойте тему на практике
Понимание уровней HTTP-сервера — стандартный вход в серверную разработку на Java, полезный и перед Spring Boot, и перед Netty. Пройти путь от сокетов и HttpServer до полноценного веб-приложения системно, с разбором сервлетов, Spring и многопоточности, помогает курс Java Backend Developer. Сориентироваться, что важно на старте, помогают бесплатные открытые уроки Otus.
FAQ
Можно ли ставить com.sun.net.httpserver.HttpServer в продакшн?
Не рекомендуется: нет TLS и обработки нагрузки из коробки, как у Tomcat, Jetty или Netty. Годится для локальных инструментов, тестов и обучения.
Нужны ли Maven или Gradle для примеров из статьи?
Нет, HttpServer входит в стандартный JDK (модуль jdk.httpserver), достаточно javac и java.
Как добавить HTTPS к HttpServer?
Через класс HttpsServer из того же пакета с настроенным SSLContext под ваш сертификат. Отдельная тема, здесь не разбирается.



