Java http rest request

RESTful call in Java

I am going to make a RESTful call in Java. However, I don’t know how to make the call. Do I need to use the URLConnection or others?

It depends how you do rest calls in your application. Do you use a JaxRS implementation ? A particular one ? Are you doing requests manually ?

@Colin Hebert, thank you for your reply. But I have no ideas of the RESTful call. could you give me more information about different parts of the RESTful call.

14 Answers 14

Update: It’s been almost 5 years since I wrote the answer below; today I have a different perspective.

99% of the time when people use the term REST, they really mean HTTP; they could care less about “resources”, “representations”, “state transfers”, “uniform interfaces”, “hypermedia”, or any other constraints or aspects of the REST architecture style identified by Fielding. The abstractions provided by various REST frameworks are therefore confusing and unhelpful.

So: you want to send HTTP requests using Java in 2015. You want an API that is clear, expressive, intuitive, idiomatic, simple. What to use? I no longer use Java, but for the past few years the Java HTTP client library that has seemed the most promising and interesting is OkHttp. Check it out.

You can definitely interact with RESTful web services by using URLConnection or HTTPClient to code HTTP requests.

However, it’s generally more desirable to use a library or framework which provides a simpler and more semantic API specifically designed for this purpose. This makes the code easier to write, read, and debug, and reduces duplication of effort. These frameworks generally implement some great features which aren’t necessarily present or easy to use in lower-level libraries, such as content negotiation, caching, and authentication.

Some of the most mature options are Jersey, RESTEasy, and Restlet.

I’m most familiar with Restlet, and Jersey, let’s look at how we’d make a POST request with both APIs.

Jersey Example

Form form = new Form(); form.add("x", "foo"); form.add("y", "bar"); Client client = ClientBuilder.newClient(); WebTarget resource = client.target("http://localhost:8080/someresource"); Builder request = resource.request(); request.accept(MediaType.APPLICATION_JSON); Response response = request.get(); if (response.getStatusInfo().getFamily() == Family.SUCCESSFUL) < System.out.println("Success! " + response.getStatus()); System.out.println(response.getEntity()); >else

Restlet Example

Form form = new Form(); form.add("x", "foo"); form.add("y", "bar"); ClientResource resource = new ClientResource("http://localhost:8080/someresource"); Response response = resource.post(form.getWebRepresentation()); if (response.getStatus().isSuccess()) < System.out.println("Success! " + response.getStatus()); System.out.println(response.getEntity().getText()); >else

Of course, GET requests are even simpler, and you can also specify things like entity tags and Accept headers, but hopefully these examples are usefully non-trivial but not too complex.

As you can see, Restlet and Jersey have similar client APIs. I believe they were developed around the same time, and therefore influenced each other.

Читайте также:  !DOCTYPE

I find the Restlet API to be a little more semantic, and therefore a little clearer, but YMMV.

As I said, I’m most familiar with Restlet, I’ve used it in many apps for years, and I’m very happy with it. It’s a very mature, robust, simple, effective, active, and well-supported framework. I can’t speak to Jersey or RESTEasy, but my impression is that they’re both also solid choices.

Источник

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 для средств функционального программирования

Читайте также:  Основные алгоритмы программирования java

Также создадим основной пустой класс 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 , который представляет собой учетные данные, жестко закодированные в нашем примере.

Читайте также:  Php if equal to null

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

Если вы не укажете заголовок, то 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 , поэтому все концепции, описанные в этой статье, вы можете изучить на реальном фреймворке, который, к тому же, достаточно мал для изучения его кода.

Источник

Оцените статью