- Java Guides
- What is REST?
- 1. REST Basics
- 2. Design REST API Guide
- 3. Rest API Design Best Practices
- 4. Build REST API with Jersey Rest Framework
- 5. Build REST API using JAX-RS RESTEasy Framework
- 6. Build REST API using Spring Boot
- Subscriber to my top YouTube Channel (120K+ Subscribers)
- My Udemy Course: Building Microservices with Spring Boot and Spring Cloud
- Spring Boot Thymeleaf Real-Time Web Application Course
- My Udemy Course — Testing Spring Boot Application with JUnit and Mockito
- My Udemy Course — Building Real-Time REST APIs with Spring Boot
- My Udemy Course — Master Spring Data JPA with Hibernate
- My Udemy Course — Spring Boot RabbitMQ Course — Event-Driven Microservices
- My Udemy Course — Spring Boot + Apache Kafka Course
- About Me
- REST API на Java без фреймворков
- Начинаем
- Первый эндпоинт
- Поддержка разных HTTP-методов
- Парсинг параметров запроса
- Безопасность
- JSON, обработка исключений и прочее
Java Guides
In this tutorial, you will learn everything about REST and how to implement REST APIs (Restful web services) using Java.
What is REST?
- State means data
- REpresentational means formats (such as XML, JSON, YAML, HTML, etc)
- Transfer means carrying data between consumer and provider using the HTTP protocol
A REST API is an intermediary Application Programming Interface that enables two applications to communicate with each other over HTTP, much like how servers communicate to browsers.
The REST architectural style has quickly become very popular over the world for designing and architecting applications that can communicate.
In Java, we use JAX-RS APIs to build RESTful APIs. The JAX-RS is an API specification so we can use either Jersey or RestEasy framework as JAX-RS implementation to build RESTful web services.
1. REST Basics
2. Design REST API Guide
3. Rest API Design Best Practices
4. Build REST API with Jersey Rest Framework
5. Build REST API using JAX-RS RESTEasy Framework
6. Build REST API using Spring Boot
- Get link
- Other Apps
Subscriber to my top YouTube Channel (120K+ Subscribers)
My Udemy Course: Building Microservices with Spring Boot and Spring Cloud
Spring Boot Thymeleaf Real-Time Web Application Course
My Udemy Course — Testing Spring Boot Application with JUnit and Mockito
My Udemy Course — Building Real-Time REST APIs with Spring Boot
My Udemy Course — Master Spring Data JPA with Hibernate
My Udemy Course — Spring Boot RabbitMQ Course — Event-Driven Microservices
My Udemy Course — Spring Boot + Apache Kafka Course
About Me
Hi, I am Ramesh Fadatare. I am VMWare Certified Professional for Spring and Spring Boot 2022.
I am founder and author of this blog website JavaGuides, a technical blog dedicated to the Java/Java EE technologies and Full-Stack Java development.
All the articles, guides, tutorials(2000 +) written by me so connect with me if you have any questions/queries. Read more about me at About Me.
Top YouTube Channel (75K+ Subscribers): Check out my YouTube channel for free videos and courses — Java Guides YouTube Channel
REST API на Java без фреймворков
В экосистеме Java есть много фреймворков и библиотек. Хотя и не так много, как в JavaScript, но они и не устаревают так быстро. Тем не менее, это заставило меня задуматься о том, что мы уже забыли, как писать приложения без фреймворков.
Вы можете сказать, что Spring — это стандарт и зачем изобретать велосипед? А Spark — это хороший удобный REST-фреймворк. Или Light-rest-4jis. И я скажу, что вы, конечно, правы.
Но вместе с фреймворком, помимо готовой функциональности, вы получаете много магии, сложности с изучением, дополнительные функции, которые вы, скорее всего, не будете использовать, а также баги. И чем больше стороннего кода в вашем сервисе, тем больше вероятность того, что у вас будут ошибки.
Сообщество open source очень активное, и есть большая вероятность, что ошибки в фреймворке будут быстро исправлены. Но все же, я хотел бы призвать вас подумать, действительно ли вам нужен фреймворк. Если у вас небольшой сервис или консольное приложение, возможно, вы сможете обойтись без него.
Что вы можете получить (или потерять), используя чистый Java-код? Подумайте об этом:
- ваш код может быть намного чище и понятнее (а может и в полном беспорядке, если вы плохой программист)
- у вас будет больше контроля над вашим кодом, вы не будете ограничены рамками фреймворка (хотя вам придется писать больше своего кода для функциональности, которую фреймворк предоставляет из коробки)
- ваше приложение будет развертываться и запускаться гораздо быстрее, потому что фреймворку не нужно инициализировать десятки классов (или не будет запускаться вообще, если вы перепутаете что-то, например, в многопоточности)
- если вы развертываете приложение в Docker, то ваши образы будут намного меньше, потому что ваши jar также будут меньше
Когда я начал писать этот код, то часто сталкивался с ситуациями, когда отсутствовал функционал, который есть в Spring из коробки. В эти моменты, вместо того, чтобы взять Spring, надо было переосмыслить и разработать все самостоятельно.
Я понял, что для решения реальных бизнес-задач я, все же, предпочел бы использовать Spring, а не изобретать велосипед. Тем не менее, я считаю, что это упражнение было довольно интересным опытом.
Начинаем
Я буду описывать каждый шаг, но не всегда буду приводить полный исходный код. Полный код вы можете посмотреть в отдельных ветках git-репозитория.
Сначала создайте новый Maven-проект со следующим pom.xml .
4.0.0 com.consulner.httpserver pure-java-rest-api 1.0-SNAPSHOT 11 $ $
Добавьте в зависимости java.xml.bind , потому что он был удален в JDK 11 (JEP-320).
org.glassfish.jaxb jaxb-runtime 2.4.0-b180608.0325
com.fasterxml.jackson.core jackson-databind 2.9.7
Для упрощения POJO-классов будем использовать Lombok:
org.projectlombok lombok 1.18.0 provided
и vavr для средств функционального программирования
Также создадим основной пустой класс Application .
Первый эндпоинт
В основе нашего веб-приложения будет класс com.sun.net.httpserver.HttpServer . И простейший эндпоинт (endpoint) /api/hello может выглядеть следующим образом:
package com.consulner.api; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpServer; class Application < public static void main(String[] args) throws IOException < int serverPort = 8000; HttpServer server = HttpServer.create(new InetSocketAddress(serverPort), 0); server.createContext("/api/hello", (exchange ->< String respText = "Hello!"; exchange.sendResponseHeaders(200, respText.getBytes().length); OutputStream output = exchange.getResponseBody(); output.write(respText.getBytes()); output.flush(); exchange.close(); >)); server.setExecutor(null); // creates a default executor server.start(); > >
Веб-сервер запускается на порту 8000 и предоставляет эндпоинт, который просто возвращает Hello. Это можно проверить, например, используя curl:
Поддержка разных HTTP-методов
Наш первый эндпоинт работает отлично, но вы можете заметить, что независимо от того, какой HTTP-метод использовать, он всегда отвечает одинаково.
curl -X POST localhost:8000/api/hello curl -X PUT localhost:8000/api/hello
Первое, что нужно сделать, это добавить код для различения методов, например:
server.createContext("/api/hello", (exchange -> < if ("GET".equals(exchange.getRequestMethod())) < String respText = "Hello!"; exchange.sendResponseHeaders(200, respText.getBytes().length); OutputStream output = exchange.getResponseBody(); output.write(respText.getBytes()); output.flush(); >else < exchange.sendResponseHeaders(405, -1);// 405 Method Not Allowed >exchange.close(); >));
Попробуйте еще раз такой запрос:
curl -v -X POST localhost:8000/api/hello
ответ будет примерно таким:
> POST /api/hello HTTP/1.1 > Host: localhost:8000 > User-Agent: curl/7.61.0 > Accept: */* > < HTTP/1.1 405 Method Not Allowed
Есть также несколько моментов, которые нужно помнить. Например, не забыть сделать flush() для OutputStream и close() для exchange . При использовании Spring мне об этом даже не приходилось думать.
Парсинг параметров запроса
Парсинг параметров запроса — это еще одна «функция», которую нам нужно реализовать самостоятельно.
Допустим, мы хотим, чтобы наш hello api получал имя в параметре name , например:
curl localhost:8000/api/hello?name=Marcin Hello Marcin!
Мы могли бы распарсить параметры следующим образом:
public static Map> splitQuery(String query) < if (query == null || "".equals(query)) < return Collections.emptyMap(); >return Pattern.compile("&").splitAsStream(query) .map(s -> Arrays.copyOf(s.split(" java">Map> params = splitQuery(exchange.getRequestURI().getRawQuery()); String noNameText = "Anonymous"; String name = params.getOrDefault("name", List.of(noNameText)).stream().findFirst().orElse(noNameText); String respText = String.format("Hello %s!", name);
Аналогично, если мы хотим использовать параметры в path. Например:
curl localhost:8000/api/items/1
Чтобы получить элемент по нам нужно распарсить url самостоятельно. Это становится громоздким.
Безопасность
Часто нам нужно защитить доступ к некоторым эндпоинтам. Например, это можно сделать, используя базовую аутентификацию (basic authentication).
Для каждого HttpContext мы можем установить аутентификатор, как показано ниже:
HttpContext context = server.createContext("/api/hello", (exchange -> < // здесь ничего не изменяем >)); context.setAuthenticator(new BasicAuthenticator("myrealm") < @Override public boolean checkCredentials(String user, String pwd) < return user.equals("admin") && pwd.equals("admin"); >>);
Значение “myrealm” в конструкторе BasicAuthenticator — это имя realm. Realm — это виртуальное имя, которое может быть использовано для разделения областей аутентификации.
Подробнее об этом можно прочитать в RFC 1945.
Теперь вы можете вызвать этот защищенный эндпоинт, добавив заголовок Authorization :
curl -v localhost:8000/api/hello?name=Marcin -H 'Authorization: Basic YWRtaW46YWRtaW4='
Текст после «Basic» — это кодированный в Base64 текст admin:admin , который представляет собой учетные данные, жестко закодированные в нашем примере.
Для аутентификации в реальном приложении вы, вероятно, получите учетные данные из заголовка и сравните их с именем пользователя и паролем, хранящимися в базе данных.
Если вы не укажете заголовок, то API ответит статусом
JSON, обработка исключений и прочее
Теперь пришло время для более сложного примера.
Из моего опыта в разработке программного обеспечения наиболее распространенным API, который я разрабатывал, был обмен JSON.
Мы собираемся разработать API для регистрации новых пользователей. Для их хранения будем использовать базу данных в памяти.
У нас будет простой доменный объект User :
@Value @Builder public class User
Я использую Lombok, чтобы избавится от бойлерплейта (конструкторы, геттеры).
В REST API я хочу передать только логин и пароль, поэтому я создал отдельный объект:
@Value @Builder public class NewUser
Объекты User создаются в сервисе, который будем использовать в обработчике API. Сервисный метод просто сохраняет пользователя.
public String create(NewUser user)
В реальном приложении можно сделать больше. Например, отправлять события после успешной регистрации пользователя.
Реализация репозитория выглядит следующим образом:
import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import com.consulner.domain.user.NewUser; import com.consulner.domain.user.User; import com.consulner.domain.user.UserRepository; public class InMemoryUserRepository implements UserRepository < private static final Map USERS_STORE = new ConcurrentHashMap(); @Override public String create(NewUser newUser) < String User user = User.builder() .id(id) .login(newUser.getLogin()) .password(newUser.getPassword()) .build(); USERS_STORE.put(newUser.getLogin(), user); return id; >>
Наконец, склеим все вместе в handle() :
protected void handle(HttpExchange exchange) throws IOException < if (!exchange.getRequestMethod().equals("POST")) < throw new UnsupportedOperationException(); >RegistrationRequest registerRequest = readRequest(exchange.getRequestBody(), RegistrationRequest.class); NewUser user = NewUser.builder() .login(registerRequest.getLogin()) .password(PasswordEncoder.encode(registerRequest.getPassword())) .build(); String userId = userService.create(user); exchange.getResponseHeaders().set(Constants.CONTENT_TYPE, Constants.APPLICATION_JSON); exchange.sendResponseHeaders(StatusCode.CREATED.getCode(), 0); byte[] response = writeResponse(new RegistrationResponse(userId)); OutputStream responseBody = exchange.getResponseBody(); responseBody.write(response); responseBody.close(); >
Здесь JSON-запрос преобразуется в объект RegistrationRequest :
@Value class RegistrationRequest
который позже я сопоставляю с объектом NewUser , чтобы сохранить его в базе данных и отправить ответ в виде JSON.
Также мне нужно преобразовать объект RegistrationResponse обратно в JSON-строку. Для этого используем Jackson
( com.fasterxml.jackson.databind.ObjectMapper ).
Вот как я создаю новый обработчик ( handler ) в main() :
public static void main(String[] args) throws IOException < int serverPort = 8000; HttpServer server = HttpServer.create(new InetSocketAddress(serverPort), 0); RegistrationHandler registrationHandler = new RegistrationHandler(getUserService(), getObjectMapper(), getErrorHandler()); server.createContext("/api/users/register", registrationHandler::handle); // here follows the rest.. >
Рабочий пример можно найти в ветке step-6. Там я также добавил глобальный обработчик исключений для отправки стандартных JSON-сообщений об ошибках. Например, если HTTP-метод не поддерживается или запрос к API сформирован неправильно.
Вы можете запустить приложение и попробовать один из следующих запросов:
curl -X POST localhost:8000/api/users/register -d ''
curl -v -X POST localhost:8000/api/users/register -d ''
Кроме того, я случайно столкнулся с проектом java-express, который является Java-аналогом фреймворка Express для Node.js. В нем также используется jdk.httpserver , поэтому все концепции, описанные в этой статье, вы можете изучить на реальном фреймворке, который, к тому же, достаточно мал для изучения его кода.