Client HTTP Ratpack

Ratpack HTTP Client

1. introduction

Au cours des dernières années, nous avons assisté à la montée en puissance de la manière fonctionnelle et réactive de créer des applications en Java. Ratpack offre un moyen de créer des applications HTTP dans le même sens.

Puisqu'il utilise Netty pour ses besoins en réseau,it’s completely asynchronous and non-blocking. Ratpack fournit également une assistance pour les tests en fournissant une bibliothèque de tests complémentaire.

Dans ce tutoriel, nous allons passer en revue l'utilisation du client HTTP Ratpack et des composants associés.

Et ce faisant, nous allons essayer de pousser notre compréhension plus loin du point où nous sommes partis à la fin de nosintroductory Ratpack tutorial.

2. Dépendances Maven

Pour commencer, ajoutons lesRatpack dependencies requis:


    io.ratpack
    ratpack-core
    1.5.4


    io.ratpack
    ratpack-test
    1.5.4
    test

Fait intéressant, nous avons seulement besoin de cela pour créer et tester notre application.

Cependant, nous pouvons toujours choisir d'ajouter et d'étendre en utilisant d'autres bibliothèques Ratpack.

3. Contexte

Avant de plonger, voyons comment les choses sont faites dans les applications Ratpack.

3.1. Approche basée sur le gestionnaire

Ratpack utilise une approche basée sur le gestionnaire pour le traitement des demandes. L'idée en elle-même est assez simple.

Et dans sa forme la plus simple, chaque gestionnaire pourrait traiter les demandes sur chaque chemin spécifique:

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

3.2. Chaîne, registre et contexte

Handlers interact with the incoming request using a Context object. Grâce à lui, nous avons accès à la requête et à la réponse HTTP, ainsi que des capacités à déléguer à d'autres gestionnaires.

Prenons par exemple le gestionnaire suivant:

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

Ce gestionnaire est chargé de faire un prétraitement, de placer le résultat dans les Registryet de déléguer la demande aux autres gestionnaires.

Through the use of the Registry, we can achieve inter-handler communication. Leshandler suivants interrogent le résultat précédemment calculé à partir deRegistry en utilisant le type d'objet:

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

Nous devons garder à l'esprit que dans une application de production, nous aurions ces gestionnaires en tant que classes distinctes pour une meilleure abstraction, le débogage et le développement d'une logique métier élaborée.

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

Par exemple:

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

Nous pouvons aller plus loin dans cette approche en composant plusieurs chaînes ensemble en utilisant la méthodeinsert(..) enChain et rendre chacune responsable d'un problème différent.

Le scénario de test suivant illustre l'utilisation de ces constructions:

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

Ici, nous utilisons la bibliothèque de test de Ratpack pour tester nos fonctionnalités de manière isolée et sans démarrer un serveur réel.

4. HTTP avec Ratpack

4.1. Travailler vers l'asynchronie

Le protocole HTTP est de nature synchrone. Par conséquent, le plus souvent, les applications Web sont synchrones et donc bloquantes. Cette approche nécessite beaucoup de ressources, car nous créons un thread pour chaque demande entrante.

Nous préférons créer des applications non bloquantes et asynchrones. Cela garantirait que nous n'aurions besoin que d'un petit groupe de threads pour traiter les demandes.

4.2. Fonctions de rappel

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. En Java, cela prend généralement la forme de classes internes anonymes et d'expressions lambda. Mais au fur et à mesure que notre application évolue ou qu'il y a plusieurs appels asynchrones imbriqués, une telle solution serait difficile à maintenir et à déboguer.

Ratpack fournit une solution élégante pour gérer cette complexité sous la forme dePromises.

4.3. Promesses de Ratpack

Un RatpackPromise pourrait être considéré comme un objet JavaFuture. It’s essentially a representation of a value which will become available later.

Nous pouvons spécifier un pipeline d'opérations que la valeur passera au fur et à mesure de sa disponibilité. Chaque opération renverrait un nouvel objet de promesse, une version transformée de l'objet de promesse précédent.

Comme on pouvait s'y attendre, cela entraîne peu de changements de contexte entre les threads et rend notre application efficace.

Voici une implémentation de gestionnaire qui utilisePromise:

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

Nous devons garder à l'esprit qu'unpromise is especially useful when we define what to do with the eventual value. Nous pouvons le faire en appelant l'opération de terminalthen(Action) on fils.

Si nous devons renvoyer une promesse mais que la source de données est synchrone, nous pourrons toujours le faire:

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

4.4. Le client HTTP

Ratpack fournit un client HTTP asynchrone, dont une instance peut être extraite du registre du serveur. Cependant,we’re encouraged to create and use alternative instances as the default one doesn’t use connection pooling and has quite conservative defaults.

On peut créer une instance en utilisant la méthodeof(Action) qui prend comme paramètre unAction de typeHttpClientSpec.

En utilisant cela, nous pouvons adapter notre client à nos préférences:

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

Comme nous l'avons peut-être deviné par sa nature asynchrone,HttpClient renvoie un objetPromise. En conséquence, nous pouvons avoir un pipeline d'opérations complexe d'une manière non bloquante.

Par exemple, demandons à un client d'appeler nosEmployeeHandler en utilisant ceHttpClient:

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

Un rapide appel àcURL confirmerait que nous avons obtenu une réponse attendue:

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

5. Conclusion

Dans cet article, nous avons passé en revue les structures de bibliothèque primaires disponibles dans Ratpack, qui nous permettent de développer des applications Web non bloquantes et asynchrones.

Nous avons jeté un coup d'œil au RatpackHttpClient and la classePromise qui l'accompagne qui représente tout ce qui est asynchrone dans Ratpack. Nous avons également vu comment nous pourrions facilement tester notre application HTTP en utilisant lesTestHttpClient.

Et, comme toujours, les extraits de code de ce tutoriel sont disponibles dans nosGitHub repository.