Перейти к основному содержимому

Клиент StackExchange REST с Spring и RestTemplate

· 5 мин. чтения

В этой статье будет рассмотрен небольшой побочный проект — бот для автоматического твитирования популярных вопросов с различных сайтов вопросов и ответов StackExchange , таких как StackOverflow , ServerFault , SuperUser и т. д . Мы создадим простой клиент для API StackExchange, а затем настроим его. взаимодействие с Twitter API с использованием Spring Social — в этой первой части речь пойдет только о клиенте StackExchange.

Первоначальная цель этой реализации не состоит в том, чтобы быть полноценным клиентом для всего API StackExchange — это выходит за рамки этого проекта. Единственная причина, по которой существует клиент, заключается в том, что я не смог найти тот, который работал бы против версии 2.x официального API.

1. Зависимости Maven

Чтобы использовать StackExchange REST API, нам понадобится очень мало зависимостей — по сути, только HTTP-клиент — Apache HttpClient отлично подойдет для этой цели:

<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.3</version>
</dependency>

Spring `` RestTemplate также можно было бы использовать для взаимодействия с HTTP API, но это привело бы к введению в проект довольно большого количества других зависимостей, связанных со Spring, зависимостей, которые не являются строго необходимыми, поэтому HttpClient сделает все легким и простым.

2. Клиент вопросов

Целью этого клиента является использование службы REST /questions , которую публикует StackExchange , а не предоставление клиента общего назначения для всех API StackExchange, поэтому в этой статье мы рассмотрим только это.

Фактическая HTTP-связь с использованием HTTPClient относительно проста:

public String questions(int min, String questionsUri) {
HttpGet request = null;
try {
request = new HttpGet(questionsUri);
HttpResponse httpResponse = client.execute(request);
InputStream entityContentStream = httpResponse.getEntity().getContent();
return IOUtils.toString(entityContentStream, Charset.forName("utf-8"));
} catch (IOException ex) {
throw new IllegalStateException(ex);
} finally {
if (request != null) {
request.releaseConnection();
}
}
}

Этого простого взаимодействия вполне достаточно для получения необработанных вопросов в формате JSON, которые публикует API. Следующим шагом будет обработка этого JSON.

Здесь есть одна важная деталь — это аргумент метода questionsUri — существует несколько API StackExchange, которые могут публиковать вопросы (как предполагает официальная документация ), и этот метод должен быть достаточно гибким, чтобы использовать их все. Например, он может использовать простейший API, который возвращает вопросы, установив для questionUri значение https://api.stackexchange.com/2.1/questions?site=stackoverflow , или он может использовать тег на основе https://api.stackexchange.com/ . 2.1/теги/{теги}/faq?site=stackoverflow Вместо этого API, в зависимости от того, что нужно клиенту.

Запрос к API StackExchange полностью настроен с параметрами запроса, даже для более сложных расширенных поисковых запросов — тело не отправляется. Чтобы построить questionsUri , мы создадим базовый динамический класс RequestBuilder , который будет использовать URIBuilder из библиотеки HttpClient. Это позаботится о правильном кодировании URI и, как правило, о том, что конечный результат действителен:

public class RequestBuilder {
private Map<String, Object> parameters = new HashMap<>();

public RequestBuilder add(String paramName, Object paramValue) {
this.parameters.put(paramName, paramValue);
return this;
}
public String build() {
URIBuilder uriBuilder = new URIBuilder();
for (Entry<String, Object> param : this.parameters.entrySet()) {
uriBuilder.addParameter(param.getKey(), param.getValue().toString());
}

return uriBuilder.toString();
}
}

Итак, теперь, чтобы создать действительный URI для StackExchange API:

String params = new RequestBuilder().
add("order", "desc").add("sort", "votes").add("min", min).add("site", site).build();
return "https://api.stackexchange.com/2.1/questions" + params;

3. Тестирование клиента

Клиент будет выводить необработанный JSON, но для проверки нам понадобится библиотека обработки JSON, в частности, Jackson 2 :

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.3.3</version>
<scope>test</scope>
</dependency>

Тесты, которые мы рассмотрим, будут взаимодействовать с реальным API StackExchange:

@Test
public void whenRequestIsPerformed_thenSuccess()
throws ClientProtocolException, IOException {
HttpResponse response = questionsApi.questionsAsResponse(50, Site.serverfault);
assertThat(response.getStatusLine().getStatusCode(), equalTo(200));
}
@Test
public void whenRequestIsPerformed_thenOutputIsJson()
throws ClientProtocolException, IOException {
HttpResponse response = questionsApi.questionsAsResponse(50, Site.serverfault);
String contentType = httpResponse.getHeaders(HttpHeaders.CONTENT_TYPE)[0].getValue();
assertThat(contentType, containsString("application/json"));
}
@Test
public void whenParsingOutputFromQuestionsApi_thenOutputContainsSomeQuestions()
throws ClientProtocolException, IOException {
String questionsAsJson = questionsApi.questions(50, Site.serverfault);

JsonNode rootNode = new ObjectMapper().readTree(questionsAsJson);
ArrayNode questionsArray = (ArrayNode) rootNode.get("items");
assertThat(questionsArray.size(), greaterThan(20));
}

Первый тест подтвердил, что ответ, предоставленный API, действительно был 200 OK, поэтому запрос GET для получения вопросов был действительно успешным. После того, как это основное условие выполнено, мы перешли к представлению — как указано в HTTP-заголовке Content-Type — которое должно быть JSON. Затем мы фактически анализируем JSON и проверяем, действительно ли в этом выводе есть вопросы — сама логика анализа является низкоуровневой и простой, чего достаточно для целей теста.

Обратите внимание, что эти запросы учитываются в ваших ограничениях скорости, установленных API — по этой причине живые тесты исключены из стандартной сборки Maven:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.17</version>
<configuration>
<excludes>
<exclude>**/*LiveTest.java</exclude>
</excludes>
</configuration>
</plugin>

4. Следующий шаг

Текущий Клиент ориентирован только на один тип Ресурса из множества доступных типов, опубликованных API StackExchange. Это связано с тем, что его первоначальная цель ограничена — ему нужно только разрешить пользователю использовать Вопросы с различных сайтов в портфолио StackExchange. Следовательно, клиент может быть улучшен за пределами этого начального варианта использования, чтобы иметь возможность использовать другие типы API .

Реализация также очень сырая — после использования службы REST вопросов она просто возвращает вывод JSON в виде строки, а не какую-либо модель вопросов из этого вывода. Таким образом, потенциальным следующим шагом может быть преобразование этого JSON в правильный DTO домена и возврат его обратно вместо необработанного JSON.

5. Вывод

Цель этой статьи состояла в том, чтобы показать, как начать интеграцию с API StackExchange или с существующим API на основе HTTP. В нем рассказывалось, как писать интеграционные тесты для живого API и убедиться, что сквозное взаимодействие действительно работает.

Во второй части этой статьи будет показано, как взаимодействовать с API Twitter с помощью библиотеки Spring Social и как использовать клиент StackExchange, который мы создали здесь, чтобы публиковать вопросы в новой учетной записи Twitter.

Я уже создал несколько учетных записей в Твиттере, которые теперь твитят 2 главных вопроса в день по различным дисциплинам:

  • SpringTip — Два лучших вопроса Spring от StackOverflow каждый день.
  • JavaTopSO — Два лучших вопроса по Java от StackOverflow каждый день.
  • AskUbuntuBest — Два лучших вопроса от AskUbuntu каждый день.
  • BestBash — Два лучших вопроса Bash со всех сайтов StackExchange каждый день.
  • ServerFaultBest — Два лучших вопроса от ServerFault каждый день.

Полная реализация этого StackExchange Client есть на github .