SpringとRestTemplateを使用したStackExchange RESTクライアント

この記事では、簡単なサイドプロジェクト(http://stackoverflow.com/[StackOverflow]、http://serverfault.com/[ServerFault]など、さまざまなQ&A StackExchangeサイトからのトップ質問を自動的に** ツイートするボットについて説明します。]、http://superuser.com/[SuperUser]など。https://api.stackexchange.com/docs[StackExchange API]用の簡単なクライアントを作成し、Twitterとの対話を設定します。 Spring Social を使用したAPI - この最初の部分では、StackExchangeクライアントのみに焦点を当てます。

この実装の最初の目的は、StackExchange API全体に対して** 本格的なクライアントではないことです - これは、このプロジェクトの範囲外です。クライアントが存在する唯一の理由は、公式のAPIの2.xバージョンに対して機能するようなものがうまくできないということです。

1 Mavenの依存関係

StackExchange REST APIを使用するには、ほとんどの場合、依存関係はほとんど必要ありません。基本的にはHTTPクライアントだけです。

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

リンク:/春の基本認証付きの使用方法 - resttemplate#resttemplate[Spring RestTemplate ]は、HTTP APIと対話するためにも使用できますが、それは他の多くのSpringを導入したでしょうプロジェクトに関連する依存関係、厳密には必要ではない依存関係、HttpClientは物事を軽くてシンプルに保ちます。

2質問クライアント

このクライアントの目的は、StackExchange publishes という /questions RESTサービスを消費することであり、StackExchange API全体の汎用クライアントを提供することではありませんこの記事の目的はそれだけです。 + HTTPクライアントを使用した実際のHTTP通信は比較的簡単です。

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();
      }
   }
}

この単純な対話は、APIが公開する生のJSONという質問を取得するのに完全に適しています - 次のステップは、そのJSONを処理することです。ここに1つの関連詳細があります - そしてそれは questionsUri メソッド引数 です - (https://api.stackexchange.com/docs/types/question[official documentation]が示唆するように)質問を公開できる複数のStackExchange APIがありますそして、この方法はそれらすべてを消費するのに十分柔軟である必要があります。たとえば、 questionUri https://api.stackexchange.com/2.1/questions?site = stackoverflow に設定することで質問を返す最も単純なAPIを使用することも、 https://api.stackexchange.com/タグを使用することもできます。 2.1/tags/\ {tags}/faq?site = stackoverflow 代わりに、クライアントのニーズに応じたAPI。

StackExchange APIへのリクエストは、より複雑な高度な検索クエリであってもクエリパラメータで完全に設定されています - 送信される本文はありません。 questionsUri を構築するために、HttpClientライブラリの URIBuilder ** を利用する基本的な流暢な RequestBuilder クラスを構築します。これは、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();
   }
}

それでは、StackExchange APIの有効なURIを作成します。

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処理ライブラリ、特にhttp://wiki.fasterxml.com/JacksonHome[ Jackson 2 ]が必要になります。

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

我々が見るテストは実際のStackExchange APIと相互作用するでしょう:

@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要求は実際に成功しました。その基本的な条件が確保された後、 Content-Type HTTPヘッダーで指定されているように、JSONである必要があるRepresentationに移動しました。次に、実際にJSONを解析し、その出力に実際に質問があることを確認します - 構文解析ロジック自体は低レベルで単純で、テストの目的には十分です。

これらのリクエストは、APIによって指定されたhttps://api.stackexchange.com/docs/throttle[rate limits]にカウントされることに注意してください。そのため、Liveテストは標準の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次のステップ

現在のクライアントは、StackExchange APIによって公開されている多くの利用可能なタイプのうちの 単一タイプ のリソースにのみ焦点を当てています。これは最初の目的が制限されているためです。ユーザーがStackExchangeポートフォリオのさまざまなサイトから Questions を使用できるようにするだけでよいのです。その結果、クライアントはこの最初のユースケースの範囲を超えて改善され、https://api.stackexchange.com/docs?tab=type#docs[その他の種類のAPI]を使用できるようになります。この実装は、Questions RESTサービスを使用した後でも、 raw - JSON出力を単にStringとして返す - その出力からのいかなる種類のQuestionsモデルでもありません。したがって、次のステップとして、このJSONを適切なドメインDTOにアンマーシャルして、生のJSONではなくそれを返すことが考えられます。

5結論

この記事の目的は、StackExchange API、または実際にはHTTPベースのAPIとの統合を構築する方法を示すことです。ライブAPIに対して統合テストを作成し、エンドツーエンドの対話が実際に機能することを確認する方法について説明しました。

この記事の後半部分では、Spring Socialライブラリを使用してTwitter APIと対話する方法、およびここで作成したStackExchangeクライアントを使用して新しいTwitterアカウントについてツイートする方法について説明します。

私はすでにいくつかのTwitterアカウントを設定しています。これは現在、さまざまな分野のために、1日あたりの2つの質問にツイートしています。

StackOverflowからの毎日の質問 ** JavaTopSO - 2つの最高のJava

StackOverflowからの毎日の質問 ** AskUbuntuBest - 最高の二つ

AskUbuntuからの毎日の質問 ** BestBash - 最高の2つのBash

毎日すべてのStackExchangeサイトからの質問 ** ServerFaultBest - 最高の2つ

毎日ServerFaultからの質問

このStackExchangeクライアントの完全な実装はhttps://github.com/eugenp/java-stackexchange#readme[on github]です。