StackExchange-REST-Client mit Spring und RestTemplate

In diesem Artikel wird ein schnelles Nebenprojekt behandelt - ein Bot, mit dem automatisch die wichtigsten Fragen aus den verschiedenen Fragen beantwortet werden

Der anfängliche Zweck dieser Implementierung besteht darin, kein vollwertiger Client für die gesamte StackExchange-API zu sein - das wäre außerhalb des Projektumfangs. Der einzige Grund, warum der Client existiert, ist der, dass ich keinen Geldstrafe erhalten könnte, der mit der 2.x-Version der offiziellen API funktionieren würde.

** 1. Die Maven-Abhängigkeiten

Um die StackExchange-REST-API zu nutzen, benötigen wir sehr wenige Abhängigkeiten - im Wesentlichen nur einen HTTP-Client.

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

Der Link:/how-to-use-resttemplate-with-basic-authentication-in-spring # resttemplate[Spring RestTemplate ]hätte auch für die Interaktion mit der HTTP-API verwendet werden können, aber dies hätte eine Menge anderer Spring-Funktionen eingeführt Abhängige Abhängigkeiten im Projekt, Abhängigkeiten, die nicht unbedingt erforderlich sind, sodass HttpClient die Dinge leicht und einfach hält.

2. Der Fragen-Client

Das Ziel dieses Clients ist es, den /questions -REST-Service zu konsumieren, den StackExchange publishes verwendet, keinen allgemeinen Client für die gesamten StackExchange-APIs bereitzustellen Der Zweck dieses Artikels wird nur das betrachten. + Die tatsächliche HTTP-Kommunikation mit HTTPClient ist relativ unkompliziert:

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

Diese einfache Interaktion ist vollkommen ausreichend, um die von der API veröffentlichten Fragen zu erhalten, die von der API veröffentlicht werden. Im nächsten Schritt wird diese JSON verarbeitet. Hier gibt es ein relevantes Detail - und das ist das Argument der Methode questionsUri . - Es gibt mehrere StackExchange-APIs, die Fragen veröffentlichen können (wie in official documentation vorgeschlagen). und diese Methode muss flexibel genug sein, um alle davon zu verbrauchen. Es kann beispielsweise die einfachste API verwenden, die Fragen zurückgibt, indem questionUri auf https://api.stackexchange.com/2.1/questions?site = stackoverflow gesetzt wird. Andernfalls kann das Tag https://api.stackexchange.com/verwendet werden. 2.1/tags/\ {tags}/faq? Site = stackoverflow API, je nachdem, was der Client benötigt.

Eine Anforderung an die StackExchange-API ist vollständig mit Abfrageparametern konfiguriert, selbst für komplexere erweiterte Suchabfragen - es wird kein Text gesendet. Um questionsUri zu erstellen, erstellen wir eine grundlegende RequestBuilder -Klasse, die URIBuilder aus der HttpClient-Bibliothek verwendet. Dadurch wird sichergestellt, dass der URI korrekt codiert wird und im Allgemeinen sichergestellt wird, dass das Endergebnis gültig ist:

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

So erstellen Sie einen gültigen URI für die 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. Testen des Clients

Der Client gibt Roh-JSON aus, aber um dies zu testen, benötigen wir eine JSON-Verarbeitungsbibliothek, insbesondere Jackson 2 :

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

Die Tests, die wir uns anschauen, werden mit der tatsächlichen StackExchange-API interagieren:

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

Der erste Test hat bestätigt, dass die Antwort der API tatsächlich 200 OK war. Daher war die GET-Anforderung zum Abrufen der Fragen tatsächlich erfolgreich. Nachdem diese Grundbedingung sichergestellt ist, sind wir zu der Repräsentation übergegangen - wie durch den Content-Type -HTTP-Header angegeben -, die JSON sein muss. Als Nächstes analysieren wir den JSON und überprüfen, ob tatsächlich Fragen in dieser Ausgabe vorhanden sind - dass die Parsing-Logik selbst einen niedrigen Pegel aufweist und einfach ist, was für den Test ausreichend ist.

Beachten Sie, dass diese Anforderungen für Ihre von der API angegebenen raten-Grenzwerte gelten. Aus diesem Grund werden die Live-Tests vom Standard-Maven-Build ausgeschlossen:

<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. Der nächste Schritt

Der aktuelle Client konzentriert sich nur auf einen einzelnen Ressourcentyp aus den vielen verfügbaren Typen, die von den StackExchange-APIs veröffentlicht werden. Dies ist darauf zurückzuführen, dass der anfängliche Zweck begrenzt ist. Der Benutzer muss lediglich die Möglichkeit haben, Questions von den verschiedenen Websites des StackExchange-Portfolios zu verwenden. Folglich kann der Client über den Umfang dieser anfänglichen Verwendung hinaus verbessert werden, um die https://api.stackexchange.com/docs?tab=type#docs (anderen Typen der API) verwenden zu können. Die Implementierung ist auch sehr unkompliziert. Nach dem Konsumieren des Fragen-REST-Service gibt sie einfach die JSON-Ausgabe als String zurück - nicht irgendein Fragenmodell aus dieser Ausgabe. Ein potenzieller nächster Schritt wäre also, diese JSON in ein richtiges Domain-DTO umzuwandeln und dieses anstelle von rohem JSON zurückzugeben.

5. Fazit

In diesem Artikel wurde gezeigt, wie Sie mit der Erstellung einer Integration mit der StackExchange-API oder mit einer HTTP-basierten API beginnen. Es ging um das Schreiben von Integrationstests gegen die Live-API und um sicherzustellen, dass die Interaktion von Ende zu Ende tatsächlich funktioniert.

Der zweite Teil dieses Artikels zeigt, wie Sie mithilfe der Spring Social-Bibliothek mit der Twitter-API interagieren und wie Sie den hier erstellten StackExchange-Client verwenden, um Fragen in einem neuen Twitter-Konto zu twittern.

Ich habe bereits ein paar Twitter-Accounts eingerichtet, die jetzt die 2 Top-Fragen pro Tag für verschiedene Disziplinen twittern:

Fragen von StackOverflow jeden Tag ** JavaTopSO - Zwei der besten Java-Versionen

Fragen von StackOverflow jeden Tag ** AskUbuntuBest - Zwei der besten

Fragen von AskUbuntu jeden Tag ** BestBash - Zwei der besten Bash

Fragen von allen StackExchange-Sites täglich ** ServerFaultBest - Zwei der besten

Fragen von ServerFault jeden Tag

Die vollständige Implementierung dieses StackExchange-Clients ist on github .