Ich habe gerade den neuen Learn Spring- Kurs angekündigt , der sich auf die Grundlagen von Spring 5 und Spring Boot 2 konzentriert:
>> Überprüfen Sie den Kurs
1. Übersicht
Oft müssen unsere Webdienste andere Webdienste verwenden, um ihre Arbeit zu erledigen. Es kann schwierig sein, Benutzeranforderungen zu bearbeiten, während die Antwortzeit niedrig gehalten wird. Ein langsamer externer Service kann unsere Antwortzeit verlängern und dazu führen, dass unser System Anforderungen mit mehr Ressourcen stapelt. Hier kann ein nicht blockierender Ansatz sehr hilfreich sein
In diesem Tutorial werden mehrere asynchrone Anforderungen an einen Dienst aus einer Play Framework-Anwendung ausgelöst. Durch die Nutzung der nicht blockierenden HTTP-Funktion von Java können wir externe Ressourcen reibungslos abfragen, ohne unsere eigene Hauptlogik zu beeinträchtigen.
In unserem Beispiel untersuchen wir die Play WebService Library.
2. Die Play WebService (WS) -Bibliothek
WS ist eine leistungsstarke Bibliothek, die asynchrone HTTP-Aufrufe mit Java Action bereitstellt .
Mit dieser Bibliothek sendet unser Code diese Anforderungen und fährt fort, ohne zu blockieren. Um das Ergebnis der Anfrage zu verarbeiten, stellen wir eine konsumierende Funktion bereit, dh eine Implementierung der Consumer- Schnittstelle.
Dieses Muster weist einige Ähnlichkeiten mit der JavaScript-Implementierung von Rückrufen, Versprechungen und dem asynchronen / wartenden Muster auf.
Erstellen wir einen einfachen Consumer , der einige der Antwortdaten protokolliert:
Unser Verbraucher meldet sich lediglich in diesem Beispiel an. Der Verbraucher kann jedoch alles tun, was wir mit dem Ergebnis tun müssen, z. B. das Ergebnis in einer Datenbank speichern.
Wenn wir uns die Implementierung der Bibliothek genauer ansehen , können wir feststellen, dass WS den AsyncHttpClient von Java umschließt und konfiguriert , der Teil des Standard-JDK ist und nicht von Play abhängt.
3. Bereiten Sie ein Beispielprojekt vor
Um mit dem Framework zu experimentieren, erstellen wir einige Komponententests, um Anforderungen zu starten. Wir erstellen eine Skelett-Webanwendung, um sie zu beantworten, und verwenden das WS-Framework, um HTTP-Anforderungen zu stellen.
3.1. Die Skeleton-Webanwendung
Zunächst erstellen wir das erste Projekt mit dem Befehl sbt new :
sbt new playframework/play-java-seed.g8
Im neuen Ordner bearbeiten wir dann die Datei build.sbt und fügen die Abhängigkeit der WS-Bibliothek hinzu:
libraryDependencies += javaWs
Jetzt können wir den Server mit dem Befehl sbt run starten :
$ sbt run ... --- (Running the application, auto-reloading is enabled) --- [info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
Sobald die Anwendung gestartet wurde, können wir überprüfen, ob alles in Ordnung ist, indem wir // localhost: 9000 durchsuchen , wodurch die Begrüßungsseite von Play geöffnet wird.
3.2. Die Testumgebung
Zum Testen unserer Anwendung verwenden wir die Unit-Test-Klasse HomeControllerTest .
Zunächst müssen wir WithServer erweitern, um den Serverlebenszyklus bereitzustellen:
public class HomeControllerTest extends WithServer {
Dank ihres übergeordneten Elements startet diese Klasse unseren Skeleton-Webserver jetzt im Testmodus und an einem zufälligen Port , bevor die Tests ausgeführt werden. Die WithServer- Klasse stoppt die Anwendung auch, wenn der Test abgeschlossen ist.
Als nächstes müssen wir eine Anwendung zum Ausführen bereitstellen.
Wir können es mit erstellen Guice ‚s GuiceApplicationBuilder :
@Override protected Application provideApplication() { return new GuiceApplicationBuilder().build(); }
Und schließlich richten wir die Server-URL für unsere Tests unter Verwendung der vom Testserver bereitgestellten Portnummer ein:
@Override @Before public void setup() { OptionalInt optHttpsPort = testServer.getRunningHttpsPort(); if (optHttpsPort.isPresent()) { port = optHttpsPort.getAsInt(); url = "//localhost:" + port; } else { port = testServer.getRunningHttpPort() .getAsInt(); url = "//localhost:" + port; } }
Jetzt können wir Tests schreiben. Mit dem umfassenden Testframework können wir uns auf die Codierung unserer Testanforderungen konzentrieren.
4. Bereiten Sie eine WSRequest vor
Mal sehen, wie wir grundlegende Arten von Anforderungen wie GET oder POST und mehrteilige Anforderungen für das Hochladen von Dateien auslösen können.
4.1. Initialisieren Sie das WSRequest- Objekt
Zunächst müssen wir eine WSClient- Instanz erhalten, um unsere Anforderungen zu konfigurieren und zu initialisieren.
In einer realen Anwendung können wir einen Client, der mit Standardeinstellungen automatisch konfiguriert wird, über die Abhängigkeitsinjektion erhalten:
@Autowired WSClient ws;
In unserer Testklasse verwenden wir jedoch WSTestClient , das im Play Test Framework verfügbar ist:
Sobald wir unseren Client haben, können wir ein WSRequest- Objekt durch Aufrufen der URL- Methode initialisieren :
ws.url(url)
Die URL- Methode reicht aus, um eine Anfrage auszulösen. Wir können es jedoch weiter anpassen, indem wir einige benutzerdefinierte Einstellungen hinzufügen:
As we can see, it's pretty easy to add headers and query parameters.
After we've fully configured our request, we can call the method to initiate it.
4.2. Generic GET Request
To trigger a GET request we just have to call the get method on our WSRequest object:
ws.url(url) ... .get();
As this is a non-blocking code, it starts the request and then continues execution at the next line of our function.
The object returned by get is a CompletionStage instance, which is part of the CompletableFuture API.
Once the HTTP call has completed, this stage executes just a few instructions. It wraps the response in a WSResponse object.
Normally, this result would be passed on to the next stage of the execution chain. In this example, we have not provided any consuming function, so the result is lost.
For this reason, this request is of type “fire-and-forget”.
4.3. Submit a Form
Submitting a form is not very different from the get example.
To trigger the request we just call the post method:
This could be useful, for example, to provide a strong data consistency that we cannot achieve in other ways.
5.2. Process Response Asynchronously
To process an asynchronous response without blocking, we provide a Consumer or Function that is run by the asynchronous framework when the response is available.
For example, let's add a Consumer to our previous example to log the response:
It's worth noting that we used thenAccept, which requires a Consumer function since we don't need to return anything after logging.
When we want the current stage to return something, so that we can use it in the next stage, we need thenApply instead, which takes a Function.
These use the conventions of the standard Java Functional Interfaces.
5.3. Large Response Body
The code we've implemented so far is a good solution for small responses and most use cases. However, if we need to process a few hundreds of megabytes of data, we'll need a better strategy.
We should note: Request methods like get and post load the entire response in memory.
To avoid a possible OutOfMemoryError, we can use Akka Streams to process the response without letting it fill our memory.
The stream method returns a CompletionStage where the WSResponse has a getBodyAsStream method that provides a Source.
We can tell the code how to process this type of body by using Akka's Sink, which in our example will simply write any data passing through in the OutputStream.
5.4. Timeouts
When building a request, we can also set a specific timeout, so the request is interrupted if we don't receive the complete response in time.
This is a particularly useful feature when we see that a service we're querying is particularly slow and could cause a pile-up of open connections stuck waiting for the response.
We can set a global timeout for all our requests using tuning parameters. For a request-specific timeout, we can add to a request using setRequestTimeout:
There's still one case to handle, though: We may have received all the data, but our Consumer may be very slow processing it. This might happen if there is lots of data crunching, database calls, etc.
In low throughput systems, we can simply let the code run until it completes. However, we may wish to abort long-running activities.
To achieve that, we have to wrap our code with some futures handling.
We can set the desired log level, by changing our logback.xml configuration.
7. Caching Responses
WSClient also supports the caching of responses.
This feature is particularly useful when the same request is triggered multiple times and we don't need the freshest data every time.
It also helps when the service we're calling is temporarily down.
7.1. Add Caching Dependencies
To configure caching we need first to add the dependency in our build.sbt:
libraryDependencies += ehcache
This configures Ehcache as our caching layer.
If we don't want Ehcache specifically, we can use any other JSR-107 cache implementation.
7.2. Force Caching Heuristic
By default, Play WS won't cache HTTP responses if the server doesn't return any caching configuration.
To circumvent this, we can force the heuristic caching by adding a setting to our application.conf:
play.ws.cache.heuristics.enabled=true
This will configure the system to decide when it's useful to cache an HTTP response, regardless of the remote service's advertised caching.
8. Additional Tuning
Making requests to an external service may require some client configuration. We may need to handle redirects, a slow server, or some filtering depending on the user-agent header.
To address that, we can tune our WS client, using properties in our application.conf:
play.ws.followRedirects=false play.ws.useragent=MyPlayApplication play.ws.compressionEnabled=true # time to wait for the connection to be established play.ws.timeout.connection=30 # time to wait for data after the connection is open play.ws.timeout.idle=30 # max time available to complete the request play.ws.timeout.request=300
It's also possible to configure the underlying AsyncHttpClient directly.
The full list of available properties can be checked in the source code of AhcConfig.
9. Conclusion
In this article, we explored the Play WS library and its main features. We configured our project, learned how to fire common requests and to process their response, both synchronously and asynchronously.
We worked with large data downloads and saw how to cut short long-running activities.
Schließlich haben wir uns das Caching angesehen, um die Leistung zu verbessern und den Client zu optimieren.
Wie immer ist der Quellcode für dieses Tutorial auf GitHub verfügbar.
Java unten
Ich habe gerade den neuen Learn Spring- Kurs angekündigt , der sich auf die Grundlagen von Spring 5 und Spring Boot 2 konzentriert: