Ratpack-HTTP-Client

Ratpack HTTP Client

1. Einführung

In den letzten Jahren haben wir den Aufstieg der funktionalen und reaktiven Methode zum Erstellen von Anwendungen in Java erlebt. Ratpack bietet die Möglichkeit, HTTP-Anwendungen nach dem gleichen Prinzip zu erstellen.

Da es Netty für seine Netzwerkanforderungen verwendet,it’s completely asynchronous and non-blocking. Ratpack bietet auch Unterstützung beim Testen, indem es eine Companion-Testbibliothek bereitstellt.

In diesem Tutorial werden wir die Verwendung des Ratpack-HTTP-Clients und verwandter Komponenten erläutern.

Auf diese Weise werden wir versuchen, unser Verständnis weiter von dem Punkt zu entfernen, an dem wir am Ende unsererintroductory Ratpack tutorial abgereist sind.

2. Maven-Abhängigkeiten

Fügen Sie zunächst die erforderlichenRatpack dependencies hinzu:


    io.ratpack
    ratpack-core
    1.5.4


    io.ratpack
    ratpack-test
    1.5.4
    test

Interessanterweise brauchen wir nur so viel, um unsere Anwendung zu erstellen und zu testen.

Wir können uns jedoch jederzeit dafür entscheiden, andere Ratpack-Bibliotheken hinzuzufügen und zu erweitern.

3. Hintergrund

Bevor wir eintauchen, wollen wir uns ein Bild davon machen, wie Dinge in Ratpack-Anwendungen gemacht werden.

3.1. Handler-basierter Ansatz

Ratpack verwendet einen handlerbasierten Ansatz für die Anforderungsverarbeitung. Die Idee an sich ist einfach genug.

Und in seiner einfachsten Form könnten wir jeden Handler auf jedem bestimmten Pfad warten lassen:

public class FooHandler implements Handler {
    @Override
    public void handle(Context ctx) throws Exception {
        ctx.getResponse().send("Hello Foo!");
    }
}

3.2. Kette, Registrierung und Kontext

Handlers interact with the incoming request using a Context object. Dadurch erhalten wir Zugriff auf die HTTP-Anforderung und -Antwort sowie Funktionen zum Delegieren an andere Handler.

Nehmen Sie zum Beispiel den folgenden Handler:

Handler allHandler = context -> {
    Long id = Long.valueOf(context.getPathTokens().get("id"));
    Employee employee = new Employee(id, "Mr", "NY");
    context.next(Registry.single(Employee.class, employee));
};

Dieser Handler ist dafür verantwortlich, einige Vorverarbeitungen durchzuführen, das Ergebnis in Registryzu setzen und die Anforderung dann an die anderen Handler zu delegieren.

Through the use of the Registry, we can achieve inter-handler communication. Die folgendenhandler fragen das zuvor berechnete Ergebnis vonRegistry unter Verwendung des Objekttyps ab:

Handler empNameHandler = ctx -> {
    Employee employee = ctx.get(Employee.class);
    ctx.getResponse()
      .send("Name of employee with ID " + employee.getId() + " is " + employee.getName());
};

Wir sollten bedenken, dass wir in einer Produktionsanwendung diese Handler als separate Klassen für eine bessere Abstraktion, Fehlerbehebung und Entwicklung einer ausgeklügelten Geschäftslogik haben.

Now we can use these handlers inside a Chain in order to create complex custom request processing pipelines.

Zum Beispiel:

Action chainAction = chain -> chain.prefix("employee/:id", empChain -> {
    empChain.all(allHandler)
      .get("name", empNameHandler)
      .get("title", empTitleHandler);
});

Wir können diesen Ansatz weiter verfolgen, indem wir mehrere Ketten unter Verwendung derinsert(..)-Methode inChain zusammensetzen und jede für ein anderes Problem verantwortlich machen.

Der folgende Testfall zeigt die Verwendung dieser Konstrukte:

@Test
public void givenAnyUri_GetEmployeeFromSameRegistry() throws Exception {
    EmbeddedApp.fromHandlers(chainAction)
      .test(testHttpClient -> {
          assertEquals("Name of employee with ID 1 is NY", testHttpClient.get("employee/1/name")
            .getBody()
            .getText());
          assertEquals("Title of employee with ID 1 is Mr", testHttpClient.get("employee/1/title")
            .getBody()
            .getText());
      });
}

Hier verwenden wir die Testbibliothek von Ratpack, um unsere Funktionalität isoliert und ohne Starten eines tatsächlichen Servers zu testen.

4. HTTP mit Ratpack

4.1. Auf dem Weg zur Asynchronität

Das HTTP-Protokoll ist von Natur aus synchron. Infolgedessen sind Webanwendungen häufig synchron und blockieren daher. Dies ist ein äußerst ressourcenintensiver Ansatz, da für jede eingehende Anforderung ein Thread erstellt wird.

Wir möchten lieber nicht blockierende und asynchrone Anwendungen erstellen. Dies würde sicherstellen, dass wir nur einen kleinen Pool von Threads verwenden müssen, um Anfragen zu bearbeiten.

4.2. Rückruffunktionen

When dealing with asynchronous API’s, we usually provide a callback function to the receiver so that the data can be returned to the caller. In Java erfolgt dies normalerweise in Form von anonymen inneren Klassen und Lambda-Ausdrücken. Da sich unsere Anwendung jedoch skalieren lässt oder mehrere verschachtelte asynchrone Aufrufe vorliegen, ist eine solche Lösung schwierig zu warten und schwerer zu debuggen.

Ratpack bietet eine elegante Lösung, um diese Komplexität in Form vonPromises zu bewältigen.

4.3. Ratpack verspricht

Ein RatpackPromise kann als einem JavaFuture-Objekt ähnlich angesehen werden. It’s essentially a representation of a value which will become available later.

Wir können eine Pipeline von Operationen angeben, die der Wert durchläuft, sobald er verfügbar wird. Jede Operation würde ein neues Versprechungsobjekt zurückgeben, eine transformierte Version des vorherigen Versprechungsobjekts.

Wie zu erwarten ist, führt dies zu wenigen Kontextwechseln zwischen Threads und macht unsere Anwendung effizient.

Es folgt eine Handler-Implementierung, diePromise verwendet:

public class EmployeeHandler implements Handler {
    @Override
    public void handle(Context ctx) throws Exception {
        EmployeeRepository repository = ctx.get(EmployeeRepository.class);
        Long id = Long.valueOf(ctx.getPathTokens().get("id"));
        Promise employeePromise = repository.findEmployeeById(id);
        employeePromise.map(employee -> employee.getName())
          .then(name -> ctx.getResponse()
          .send(name));
    }
}

Wir müssen bedenken, dass apromise is especially useful when we define what to do with the eventual value. Wir können das tun, indem wir die Terminaloperationthen(Action) on it aufrufen.

Wenn wir ein Versprechen zurücksenden müssen, die Datenquelle jedoch synchron ist, können wir dies trotzdem tun:

@Test
public void givenSyncDataSource_GetDataFromPromise() throws Exception {
    String value = ExecHarness.yieldSingle(execution -> Promise.sync(() -> "Foo"))
      .getValueOrThrow();
    assertEquals("Foo", value);
}

4.4. Der HTTP-Client

Ratpack stellt einen asynchronen HTTP-Client bereit, von dem eine Instanz aus der Serverregistrierung abgerufen werden kann. we’re encouraged to create and use alternative instances as the default one doesn’t use connection pooling and has quite conservative defaults.

Wir können eine Instanz mit der Methodeof(Action) erstellen, die als Parameter einAction vom TypHttpClientSpec. verwendet

Auf diese Weise können wir unseren Kunden an unsere Vorlieben anpassen:

HttpClient httpClient = HttpClient.of(httpClientSpec -> {
    httpClientSpec.poolSize(10)
      .connectTimeout(Duration.of(60, ChronoUnit.SECONDS))
      .maxContentLength(ServerConfig.DEFAULT_MAX_CONTENT_LENGTH)
      .responseMaxChunkSize(16384)
      .readTimeout(Duration.of(60, ChronoUnit.SECONDS))
      .byteBufAllocator(PooledByteBufAllocator.DEFAULT);
});

Wie wir vielleicht aufgrund seiner asynchronen Natur erraten haben, gibtHttpClient einPromise-Objekt zurück. Infolgedessen können wir eine komplexe Pipeline von Operationen auf nicht blockierende Weise haben.

Lassen Sie uns zur Veranschaulichung einen Client unsereEmployeeHandler mit diesenHttpClient aufrufen:

public class RedirectHandler implements Handler {

    @Override
    public void handle(Context ctx) throws Exception {
        HttpClient client = ctx.get(HttpClient.class);
        URI uri = URI.create("http://localhost:5050/employee/1");
        Promise responsePromise = client.get(uri);
        responsePromise.map(response -> response.getBody()
          .getText()
          .toUpperCase())
          .then(responseText -> ctx.getResponse()
            .send(responseText));
    }
}

Ein kurzer Anruf voncURLwürde bestätigen, dass wir eine erwartete Antwort erhalten haben:

curl http://localhost:5050/redirect
JANE DOE

5. Fazit

In diesem Artikel gingen wir auf die in Ratpack verfügbaren primären Bibliothekskonstrukte ein, mit denen wir nicht blockierende und asynchrone Webanwendungen entwickeln können.

Wir haben uns das RatpackHttpClient und die zugehörigePromise -Skala angesehen, die alle asynchronen Dinge in Ratpack darstellt. Wir haben auch gesehen, wie wir unsere HTTP-Anwendung mit den zugehörigenTestHttpClient problemlos testen können.

Und wie immer sind die Codefragmente aus diesem Tutorial in unserenGitHub repository verfügbar.