Movatterモバイル変換


[0]ホーム

URL:


Chapter 5. Client API
Prev   Next

Links: Table of Contents | Single HTML

Chapter 5. Client API

Table of Contents

5.1. Uniform Interface Constraint
5.2. Ease of use and reusing JAX-RS artifacts
5.3. Overview of the Client API
5.3.1. Getting started with the client API
5.3.2. Creating and configuring a Client instance
5.3.3. Targeting a web resource
5.3.4. Identifying resource on WebTarget
5.3.5. Invoking a HTTP request
5.3.6. Example summary
5.3.7. Setting ExecutorService and ScheduledExecutorService
5.4. Java instances and types for representations
5.4.1. Adding support for new representations
5.5. Client Transport Connectors
5.5.1. The defaultHttpUrlConnector
5.5.2. Client Connectors Properties
5.5.3. Applying additional settings to Connectors
5.6. Using client request and response filters
5.7. Closing connections
5.8. Injections into client providers
5.9. Securing a Client
5.9.1. Http Authentication Support
5.9.2. Server Name Indication (SNI) Support
5.10. InvocationInterceptors
5.10.1. PreInvocationInterceptor
5.10.2. PostInvocationInterceptor
5.11. InvocationBuilderListener
5.12. Header Expect:100-continue support

This section introduces the JAX-RS Client API, which is a fluent Java based API for communication with RESTful Web services. This standard API that is also part of Jakarta EE 9 is designed to make it very easy to consume a Web service exposed via HTTP protocol and enables developers to concisely and efficiently implement portable client-side solutions that leverage existing and well established client-side HTTP connector implementations.

The JAX-RS client API can be utilized to consume any Web service exposed on top of a HTTP protocol or it's extension (e.g. WebDAV), and is not restricted to services implemented using JAX-RS. Yet, developers familiar with JAX-RS should find the client API complementary to their services, especially if the client API is utilized by those services themselves, or to test those services. The JAX-RS client API finds inspiration in the proprietary Jersey 1.x Client API and developers familiar with the Jersey 1.x Client API should find it easy to understand all the concepts introduced in the new JAX-RS Client API.

The goals of the client API are threefold:

  1. Encapsulate a key constraint of the REST architectural style, namely the Uniform Interface Constraint and associated data elements, as client-side Java artifacts;

  2. Make it as easy to consume RESTful Web services exposed over HTTP, same as the JAX-RS server-side API makes it easy to develop RESTful Web services; and

  3. Share common concepts and extensibility points of the JAX-RS API between the server and the client side programming models.

As an extension to the standard JAX-RS Client API, the Jersey Client API supports a pluggable architecture to enable the use of different underlying HTTP clientConnector implementations. Several such implementations are currently provided with Jersey. We have a default client connector usingHttp(s)URLConnection supplied with the JDK as well as connector implementations based on Apache HTTP Client, Jetty HTTP client and Grizzly Asynchronous Client.

5.1. Uniform Interface Constraint

The uniform interface constraint bounds the architecture of RESTful Web services so that a client, such as a browser, can utilize the same interface to communicate with any service. This is a very powerful concept in software engineering that makes Web-based search engines and service mash-ups possible. It induces properties such as:

  1. simplicity, the architecture is easier to understand and maintain; and

  2. evolvability or loose coupling, clients and services can evolve over time perhaps in new and unexpected ways, while retaining backwards compatibility.

Further constraints are required:

  1. every resource is identified by a URI;

  2. a client interacts with the resource via HTTP requests and responses using a fixed set of HTTP methods;

  3. one or more representations can be returned and are identified by media types; and

  4. the contents of which can link to further resources.

The above process repeated over and again should be familiar to anyone who has used a browser to fill in HTML forms and follow links. That same process is applicable to non-browser based clients.

Many existing Java-based client APIs, such as the Apache HTTP client API orHttpUrlConnection supplied with the JDK place too much focus on the Client-Server constraint for the exchanges of request and responses rather than a resource, identified by a URI, and the use of a fixed set of HTTP methods.

A resource in the JAX-RS client API is an instance of the Java classWebTarget. and encapsulates an URI. The fixed set of HTTP methods can be invoked based on theWebTarget. The representations are Java types, instances of which, may contain links that new instances ofWebTarget may be created from.

5.2. Ease of use and reusing JAX-RS artifacts

Since a JAX-RS component is represented as an annotated Java type, it makes it easy to configure, pass around and inject in ways that are not so intuitive or possible with other client-side APIs. The Jersey Client API reuses many aspects of the JAX-RS and the Jersey implementation such as:

  1. URI building usingUriBuilder andUriTemplate to safely build URIs;

  2. Built-in support for Java types of representations such asbyte[],String,Number,Boolean,Character,InputStream,java.io.Reader,File,DataSource, JAXB beans as well as additional Jersey-specific JSON andMulti Part support.

  3. Using the fluent builder-style API pattern to make it easier to construct requests.

Some APIs, like the Apache HTTP Client orHttpURLConnection can be rather hard to use and/or require too much code to do something relatively simple, especially when the client needs to understand different payload representations. This is why the Jersey implementation of JAX-RS Client API provides support for wrappingHttpUrlConnection and the Apache HTTP client. Thus it is possible to get the benefits of the established JAX-RS implementations and features while getting the ease of use benefit of the simple design of the JAX-RS client API. For example, with a low-level HTTP client library, sending a POST request with a bunch of typed HTML form parameters and receiving a response de-serialized into a JAXB bean is not straightforward at all. With the new JAX-RS Client API supported by Jersey this task is very easy:

Example 5.1. POST request with form parameters

Client client = ClientBuilder.newClient();WebTarget target = client.target("http://localhost:9998").path("resource");Form form = new Form();form.param("x", "foo");form.param("y", "bar");MyJAXBBean bean =target.request(MediaType.APPLICATION_JSON_TYPE)    .post(Entity.entity(form,MediaType.APPLICATION_FORM_URLENCODED_TYPE),        MyJAXBBean.class);


In theExample 5.1, “POST request with form parameters” a newWebTarget instance is created using a newClient instance first, next aForm instance is created with two form parameters. Once ready, theForm instance isPOSTed to the target resource. First, the acceptable media type is specified in therequest(...) method. Then in thepost(...) method, a call to a static method on JAX-RSEntity is made to construct the request entity instance and attach the proper content media type to the form entity that is being sent. The second parameter in thepost(...) method specifies the Java type of the response entity that should be returned from the method in case of a successful response. In this case an instance of JAXB bean is requested to be returned on success. The Jersey client API takes care of selecting the properMessageBodyWriter<T> for the serialization of theForm instance, invoking thePOST request and producing and de-serialization of the response message payload into an instance of a JAXB bean using a properMessageBodyReader<T>.

If the code above had to be written usingHttpUrlConnection, the developer would have to write custom code to serialize the form data that are sent within the POST request and de-serialize the response input stream into a JAXB bean. Additionally, more code would have to be written to make it easy to reuse the logic when communicating with the same resource“http://localhost:8080/resource” that is represented by the JAX-RSWebTarget instance in our example.

5.3. Overview of the Client API

5.3.1. Getting started with the client API

Refer to thedependencies for details on the dependencies when using the Jersey JAX-RS Client support.

You may also want to use a customConnector implementation. In such case you would need to include additional dependencies on the module(s) containing the custom client connector that you want to use. See section"Configuring custom Connectors" about how to use and configure a custom Jersey client transportConnector.

5.3.2.  Creating and configuring a Client instance

JAX-RS Client API is designed to allow fluent programming model. This means, a construction of aClient instance, from which aWebTarget is created, from which a requestInvocation is built and invoked can be chained in a single "flow" of invocations. The individual steps of the flow will be shown in the following sections. To utilize the client API it is first necessary to build an instance of aClient using one of the staticClientBuilder factory methods. Here's the most simple example:

Client client = ClientBuilder.newClient();

TheClientBuilder is a JAX-RS API used to create new instances ofClient. In a slightly more advanced scenarios,ClientBuilder can be used to configure additional client instance properties, such as a SSL transport settings, if needed (see??? below).

AClient instance can be configured during creation by passing aClientConfig to thenewClient(Configurable)ClientBuilder factory method.ClientConfig implementsConfigurable and therefore it offers methods to register providers (e.g. features or individual entity providers, filters or interceptors) and setup properties. The following code shows a registration of custom client filters:

ClientConfig clientConfig = new ClientConfig();clientConfig.register(MyClientResponseFilter.class);clientConfig.register(new AnotherClientFilter());Client client = ClientBuilder.newClient(clientConfig);

In the example, filters are registered using theClientConfig.register(...) method. There are multiple overloaded versions of the method that support registration of feature and provider classes or instances. Once aClientConfig instance is configured, it can be passed to theClientBuilder to create a pre-configuredClient instance.

Note that the JerseyClientConfig supports the fluent API model ofConfigurable. With that the code that configures a new client instance can be also written using a more compact style as shown below.

Client client = ClientBuilder.newClient(new ClientConfig()        .register(MyClientResponseFilter.class)        .register(new AnotherClientFilter());

The ability to leverage this compact pattern is inherent to all JAX-RS and Jersey Client API components.

SinceClient implementsConfigurable interface too, it can be configured further even after it has been created. Important is to mention that any configuration change done on aClient instance will not influence theClientConfig instance that was used to provide the initialClient instance configuration at the instance creation time. The next piece of code shows a configuration of an existingClient instance.

client.register(ThirdClientFilter.class);

Similarly to earlier examples, sinceClient.register(...) method supports the fluent API style, multiple client instance configuration calls can be chained:

client.register(FilterA.class)      .register(new FilterB())      .property("my-property", true);

To get the current configuration of theClient instance agetConfiguration() method can be used.

ClientConfig clientConfig = new ClientConfig();clientConfig.register(MyClientResponseFilter.class);clientConfig.register(new AnotherClientFilter());Client client = ClientBuilder.newClient(clientConfig);client.register(ThirdClientFilter.class);Configuration newConfiguration = client.getConfiguration();

In the code, an additionalMyClientResponseFilter class andAnotherClientFilter instance are registered in theclientConfig. TheclientConfig is then used to construct a newClient instance. TheThirdClientFilter is added separately to the constructedClient instance. This does not influence the configuration represented by the originalclientConfig. In the last step anewConfiguration is retrieved from theclient. This configuration contains all three registered filters while the originalclientConfig instance still contains only two filters. UnlikeclientConfig created separately, thenewConfiguration retrieved from theclient instance represents a live client configuration view. Any additional configuration changes made to theclient instance are also reflected in thenewConfiguration. So,newConfiguration is really a view of theclient configuration and not a configuration state copy. These principles are important in the client API and will be used in the following sections too. For example, you can construct a common base configuration for all clients (in our case it would beclientConfig) and then reuse this common configuration instance to configure multipleclient instances that can be further specialized. Similarly, you can use an existingclient instance configuration to configure another client instance without having to worry about any side effects in the originalclient instance.

5.3.3. Targeting a web resource

Once you have aClient instance you can create aWebTarget from it.

WebTarget webTarget = client.target("http://example.com/rest");

AClient contains severaltarget(...) methods that allow for creation ofWebTarget instance. In this case we're usingtarget(String uri) version. Theuri passed to the method as aString is the URI of the targeted web resource. In more complex scenarios it could be the context root URI of the whole RESTful application, from whichWebTarget instances representing individual resource targets can be derived and individually configured. This is possible, because JAX-RSWebTarget also implementsConfigurable:

WebTarget webTarget = client.target("http://example.com/rest");webTarget.register(FilterForExampleCom.class);

The configuration principles used in JAX-RS client API apply toWebTarget as well. EachWebTarget instance inherits a configuration from its parent (either a client or another web target) and can be further custom-configured without affecting the configuration of the parent component. In this case, theFilterForExampleCom will be registered only in thewebTarget and not inclient. So, theclient can still be used to create newWebTarget instances pointing at other URIs using just the common client configuration, whichFilterForExampleCom filter is not part of.

5.3.4. Identifying resource on WebTarget

Let's assume we have awebTarget pointing at"http://example.com/rest" URI that represents a context root of a RESTful application and there is a resource exposed on the URI"http://example.com/rest/resource". As already mentioned, aWebTarget instance can be used to derive other web targets. Use the following code to define a path to the resource.

WebTarget resourceWebTarget = webTarget.path("resource");

TheresourceWebTarget now points to the resource on URI"http://example.com/rest/resource". Again if we configure theresourceWebTarget with a filter specific to theresource, it will not influence the originalwebTarget instance. However, the filterFilterForExampleCom registration will still be inherited by theresourceWebTarget as it has been created fromwebTarget. This mechanism allows you to share the common configuration of related resources (typically hosted under the same URI root, in our case represented by thewebTarget instance), while allowing for further configuration specialization based on the specific requirements of each individual resource. The same configuration principles of inheritance (to allow common config propagation) and decoupling (to allow individual config customization) applies to all components in JAX-RS Client API discussed below.

Let's say there is a sub resource on the path"http://example.com/rest/resource/helloworld". You can derive aWebTarget for this resource simply by:

WebTarget helloworldWebTarget = resourceWebTarget.path("helloworld");

Let's assume that thehelloworld resource accepts a query param forGET requests which defines the greeting message. The next code snippet shows a code that creates a newWebTarget with the query param defined.

WebTarget helloworldWebTargetWithQueryParam =        helloworldWebTarget.queryParam("greeting", "Hi World!");

Please note that apart from methods that can derive newWebTarget instance based on a URI path or query parameters, the JAX-RSWebTarget API contains also methods for working with matrix parameters too.

5.3.5. Invoking a HTTP request

Let's now focus on invoking aGET HTTP request on the created web targets. To start building a new HTTP request invocation, we need to create a newInvocation.Builder.

Invocation.Builder invocationBuilder =        helloworldWebTargetWithQueryParam.request(MediaType.TEXT_PLAIN_TYPE);invocationBuilder.header("some-header", "true");

A new invocation builder instance is created using one of therequest(...) methods that are available onWebTarget. A couple of these methods accept parameters that let you define the media type of the representation requested to be returned from the resource. Here we are saying that we request a"text/plain" type. This tells Jersey to add aAccept: text/plain HTTP header to our request.

TheinvocationBuilder is used to setup request specific parameters. Here we can setup headers for the request or for example cookie parameters. In our example we set up a"some-header" header to valuetrue.

Once finished with request customizations, it's time to invoke the request. We have two options now. We can use theInvocation.Builder to build a genericInvocation instance that will be invoked some time later. UsingInvocation we will be able to e.g. set additional request properties which are properties in a batch of several requests and use the generic JAX-RSInvocation API to invoke the batch of requests without actually knowing all the details (such as request HTTP method, configuration etc.). Any properties set on an invocation instance can be read during the request processing. For example, in a customClientRequestFilter you can callgetProperty() method on the suppliedClientRequestContext to read a request property. Note that these request properties are different from the configuration properties set onConfigurable. As mentioned earlier, anInvocation instance provides generic invocation API to invoke the HTTP request it represents either synchronously or asynchronously. See theChapter 11,Asynchronous Services and Clients for more information on asynchronous invocations.

In case you do not want to do any batch processing on your HTTP request invocations prior to invoking them, there is another, more convenient approach that you can use to invoke your requests directly from anInvocation.Builder instance. This approach is demonstrated in the next Java code listing.

Response response = invocationBuilder.get();

While short, the code in the example performs multiple actions. First, it will build the the request from theinvocationBuilder. The URI of request will behttp://example.com/rest/resource/helloworld?greeting="Hi%20World!" and the request will containsome-header: true andAccept: text/plain headers. The request will then pass trough all configured request filters (AnotherClientFilter,ThirdClientFilter andFilterForExampleCom). Once processed by the filters, the request will be sent to the remote resource. Let's say the resource then returns an HTTP 200 message with a plain text response content that contains the value sent in the requestgreeting query parameter. Now we can observe the returned response:

System.out.println(response.getStatus());System.out.println(response.readEntity(String.class));

which will produce the following output to the console:

200Hi World!

As we can see, the request was successfully processed (code 200) and returned an entity (representation) is"Hi World!". Note that since we have configured aMyClientResponseFilter in the resource target, whenresponse.readEntity(String.class) gets called, the response returned from the remote endpoint is passed through the response filter chain (including theMyClientResponseFilter) and entity interceptor chain and at last a properMessageBodyReader<T> is located to read the response content bytes from the response stream into a JavaString instance. CheckChapter 10,Filters and Interceptors to lear more about request and response filters and entity interceptors.

Imagine now that you would like to invoke aPOST request but without any query parameters. You would just use thehelloworldWebTarget instance created earlier and call thepost() instead ofget().

Response postResponse =        helloworldWebTarget.request(MediaType.TEXT_PLAIN_TYPE)                .post(Entity.entity("A string entity to be POSTed", MediaType.TEXT_PLAIN));

5.3.6. Example summary

The following code puts together the pieces used in the earlier examples.

Example 5.2. Using JAX-RS Client API

ClientConfig clientConfig = new ClientConfig();clientConfig.register(MyClientResponseFilter.class);clientConfig.register(new AnotherClientFilter());Client client = ClientBuilder.newClient(clientConfig);client.register(ThirdClientFilter.class);WebTarget webTarget = client.target("http://example.com/rest");webTarget.register(FilterForExampleCom.class);WebTarget resourceWebTarget = webTarget.path("resource");WebTarget helloworldWebTarget = resourceWebTarget.path("helloworld");WebTarget helloworldWebTargetWithQueryParam =        helloworldWebTarget.queryParam("greeting", "Hi World!");Invocation.Builder invocationBuilder =        helloworldWebTargetWithQueryParam.request(MediaType.TEXT_PLAIN_TYPE);invocationBuilder.header("some-header", "true");Response response = invocationBuilder.get();System.out.println(response.getStatus());System.out.println(response.readEntity(String.class));


Now we can try to leverage the fluent API style to write this code in a more compact way.

Example 5.3. Using JAX-RS Client API fluently

Client client = ClientBuilder.newClient(new ClientConfig()            .register(MyClientResponseFilter.class)            .register(new AnotherClientFilter()));String entity = client.target("http://example.com/rest")            .register(FilterForExampleCom.class)            .path("resource/helloworld")            .queryParam("greeting", "Hi World!")            .request(MediaType.TEXT_PLAIN_TYPE)            .header("some-header", "true")            .get(String.class);


The code above does the same thing except it skips the genericResponse processing and directly requests an entity in the lastget(String.class) method call. This shortcut method let's you specify that (in case the response was returned successfully with a HTTP 2xx status code) the response entity should be returned as JavaString type. This compact example demonstrates another advantage of the JAX-RS client API. The fluency of JAX-RS Client API is convenient especially with simple use cases. Here is another a very simple GET request returning a String representation (entity):

String responseEntity = ClientBuilder.newClient()            .target("http://example.com").path("resource/rest")                        .request().get(String.class);

5.3.7. Setting ExecutorService and ScheduledExecutorService

Some client invocations, like asynchronous or reactive, could lead to a need to start a new thread. This is being done on provided ExecutorService or ScheduledExecutorService.ClientBuilder has two methods, which can be used to define them:executorService(ExecutorService) andscheduledExecutorService(ScheduledExecutorService). When specified, all invocations which do require running on another thread, should be executed using provided services.

Default values do depend on the environment - in Java/Jakarta EE container, it has to beManagedExecutorService andManagedScheduledExecutorService, for Java SE it would beForkJoinPool.commonPool for Executor service and something undefined for Scheduled executor service.

Example 5.4. Setting JAX-RS Client ExecutorService

ExecutorService myExecutorService = Executors.newCachedThreadPool();Client client = ClientBuilder.newBuilder().executorService(myExecutorService).build();

5.4. Java instances and types for representations

All the Java types and representations supported by default on the Jersey server side for requests and responses are also supported on the client side. For example, to process a response entity (or representation) as a stream of bytes use InputStream as follows:

InputStream in = response.readEntity(InputStream.class);... // Read from the streamin.close();

Note that it is important to close the stream after processing so that resources are freed up.

ToPOST a file use aFile instance as follows:

File f = ......webTarget.request().post(Entity.entity(f, MediaType.TEXT_PLAIN_TYPE));

5.4.1. Adding support for new representations

The support for new application-defined representations as Java types requires the implementation of the same JAX-RS entity provider extension interfaces as for the server side JAX-RS API, namelyMessageBodyReader<T> andMessageBodyWriter<T> respectively, for request and response entities (or inbound and outbound representations).

Classes or implementations of the provider-based interfaces need to be registered as providers within the JAX-RS or Jersey Client API components that implementConfigurable contract (ClientBuilder,Client,WebTarget orClientConfig), as was shown in the earlier sections. Some media types are provided in the form of JAX-RSFeature a concept that allows the extension providers to group together multiple different extension providers and/or configuration properties in order to simplify the registration and configuration of the provided feature by the end users. For example,MoxyJsonFeature can be register to enable and configure JSON binding support via MOXy library.

5.5. Client Transport Connectors

By default, the transport layer in Jersey is provided byHttpUrlConnection. This transport is implemented in Jersey viaHttpUrlConnectorProvider that implements Jersey-specificConnector SPI. You can implement and/or register your ownConnector instance to the JerseyClient implementation, that will replace the defaultHttpUrlConnection-based transport layer. Jersey provides several alternative client transport connector implementations that are ready-to-use.

Table 5.1. List of Jersey Connectors

Transport frameworkJersey Connector implementationMaven dependency
Grizzly NIO frameworkGrizzlyConnectorProviderorg.glassfish.jersey.connectors:jersey-grizzly-connector
Apache HTTP clientApacheConnectorProviderorg.glassfish.jersey.connectors:jersey-apache-connector
Apache 5 HTTP clientApache5ConnectorProviderorg.glassfish.jersey.connectors:jersey-apache5-connector
Helidon HTTP clientHelidonConnectorProviderorg.glassfish.jersey.connectors:jersey-helidon-connector
Jetty HTTP clientJettyConnectorProviderorg.glassfish.jersey.connectors:jersey-jetty-connector
Jetty HTTP/2 clientJettyHttp2ConnectorProviderorg.glassfish.jersey.connectors:jersey-jetty-http2-connector
Netty NIO frameworkNettyConnectorProviderorg.glassfish.jersey.connectors:jersey-netty-connector
JDK NIO clientJdkConnectorProviderorg.glassfish.jersey.connectors:jersey-jdk-connector


As indicated earlier,Connector andConnectorProvider contracts are Jersey-specific extension APIs that would only work with Jersey and as such are not part of JAX-RS. Following example shows how to setup the custom Grizzly Asynchronous HTTP Client basedConnectorProvider in a Jersey client instance:

ClientConfig clientConfig = new ClientConfig();                clientConfig.connectorProvider(new GrizzlyConnectorProvider());                Client client = ClientBuilder.newClient(clientConfig);

Client accepts as a constructor argument aConfigurable instance. Jersey implementation of theConfigurable provider for the client isClientConfig. By using the JerseyClientConfig you can configure the customConnectorProvider into theClientConfig. TheGrizzlyConnectorProvider is used as a custom connector provider in the example above. Please note that the connector provider cannot be registered as a provider usingConfigurable.register(...). Also, please note that in this API has changed since Jersey 2.5, where theConnectorProvider SPI has been introduced in order to decouple client initialization from the connector instantiation. Starting with Jersey 2.5 it is therefore not possible to directly registerConnector instances in the JerseyClientConfig. The newConnectorProvider SPI must be used instead to configure a custom client-side transport connector.

AConnectorProvider can also be set by a property on aClientBuilder starting with Jersey 2.40. The following example shows how to setup the custom Grizzly Asynchronous HTTP Client basedConnectorProvider in a Jersey client instance:

Client client = ClientBuilder.newBuilder()                .property(ClientProperties.CONNECTOR_PROVIDER, "org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider")                .build();

For more information about the property seeAppendix A,Configuration Properties.

Warning

Be aware of using other than defaultConnector implementation. There is an issue handling HTTP headers inWriterInterceptor orMessageBodyWriter<T>. If you need to change header fields do not use neitherApacheConnectorProvider norGrizzlyConnectorProvider norJettyConnectorProvider (for asynchronous requests). Other older version connectors can be affected by this issue as well. The issue for example applies to JerseyMultipart feature that also modifies HTTP headers.

5.5.1. The defaultHttpUrlConnector

The default connector is the most advanced connector, and it supports the most features Jersey has to offer. However, there are a few limitations coming from theHttpUrlConnection

.

One limitation is in the variety of HTTP methods supported by theHttpUrlConnection, since only the original HTTP/1.1 methods are supported. For instance, HTTP Patch method is not supported. See propertyHttpUrlConnector.SET_METHOD_WORKAROUND in the AppendixSection A.6, “The default HttpUrlConnector properties” for a possible workaround.

Also, in the default transport connector, there are some restrictions on the headers, that can be sent in the default configuration.HttpUrlConnectorProvider usesHttpUrlConnection as an underlying connection implementation. This JDK class by default restricts the use of following headers:

  • Access-Control-Request-Headers
  • Access-Control-Request-Method
  • Connection (with one exception -Connection header with valueClosed is allowed by default)
  • Content-Length
  • Content-Transfer-Encoding-
  • Host
  • Keep-Alive
  • Origin
  • Trailer
  • Transfer-Encoding
  • Upgrade
  • Via
  • all the headers starting withSec-

The underlying connection can be configured to permit all headers to be sent, however this behaviour can be changed only by setting the system propertysun.net.http.allowRestrictedHeaders.

Example 5.5. Sending restricted headers withHttpUrlConnector

                        Client client = ClientBuilder.newClient();                        System.setProperty("sun.net.http.allowRestrictedHeaders", "true");                        Response response = client.target(yourUri).path(yourPath).request().                        header("Origin", "http://example.com").                        header("Access-Control-Request-Method", "POST").                        get();


Warning

Internally, theHttpUrlConnection instances are pooled, so (un)setting the property after already creating a target typically does not have any effect. The property influences all the connectionscreated after the property has been (un)set, but there is no guarantee, that your request will use a connection created after the property change.

In a simple environment, setting the property before creating the first target is sufficient, but in complex environments (such as application servers), where some poolable connections might exist before your application even bootstraps, this approach is not 100% reliable and we recommend using a different client transport connector, such as Apache Connector. These limitations have to be considered especially when invokingCORS (Cross Origin Resource Sharing) requests.

The limited configurability of theHttpUrlConnection is another aspect to consider. For details, seeJava Networking Properties, for instance propertyhttp.maxConnections.

5.5.2. Client Connectors Properties

For eachConnector a property file defining properties tweaking the actualConnector exists. There areClientProperties properties that apply to the defaultHttpUrlConnectorProvider. Many of the properties are also understood by variousConnectors. Each of theConnectorProvider defines properties that apply to it.

Moreover, eachConnector supports a list of properties that are unique for theConnector. The list of the client connector properties can be found in theAppendix A,Configuration Properties.

5.5.3. Applying additional settings to Connectors

It is not possible to provide all the settings the underlying HTTP Clients support. For Apache HTTP Client, and for Jetty HTTP Client, it is possible to access directly the HTTP Client classes and invoke setter methods there.

5.5.3.1. Apache HttpClientBuilder Configuration

For Apache Connector, anApacheHttpClientBuilderConfigurator SPI allows for invoking methods oforg.apache.http.impl.client.HttpClientBuilder, such assetDefaultCredentialsProvider:

                        org.apache.http.client.CredentialsProvider credentialsProvider = new org.apache.http.impl.client.BasicCredentialsProvider();                        credentialsProvider.setCredentials(                            org.apache.http.auth.AuthScope.ANY,                            new org.apache.http.auth.UsernamePasswordCredentials("name", "password")                        );                        ApacheHttpClientBuilderConfigurator apacheHttpClientBuilderConfigurator = (httpClientBuilder) -> {                            return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);                        };                        ClientConfig cc = new ClientConfig();                        cc.register(apacheHttpClientBuilderConfigurator);                        cc.connectorProvider(new ApacheConnectorProvider());                        Client client = ClientBuilder.newClient(cc);                        ...

5.5.3.2. Apache 5 HttpClientBuilder Configuration

For Apache 5 Connector, anApache5HttpClientBuilderConfigurator SPI allows for invoking methods oforg.apache.hc.client5.http.impl.classic.HttpClientBuilder, such assetDefaultCredentialsProvider:

                        org.apache.hc.client5.http.auth.CredentialsStore credentialsProvider = new org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider();                        credentialsProvider.setCredentials(                            new org.apache.hc.client5.http.auth.AuthScope("localhost", getPort()),                            new org.apache.hc.client5.http.auth.UsernamePasswordCredentials("name", "password".toCharArray())                        );                        Apache5HttpClientBuilderConfigurator apache5HttpClientBuilderConfigurator = (httpClientBuilder) -> {                            return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);                        };                        ClientConfig cc = new ClientConfig();                        cc.register(apache5HttpClientBuilderConfigurator);                        cc.connectorProvider(new Apache5ConnectorProvider());                        Client client = ClientBuilder.newClient(cc);                        ...

5.5.3.3. Jetty HttpClient Configuration

For Jetty Connector, anJettyHttpClientSupplier SPI allows for providing a configured instance oforg.eclipse.jetty.client.HttpClient:

                        HttpClient httpClient = new HttpClient(...);                        ClientConfig clientConfig = new ClientConfig()                            .connectorProvider(new JettyConnectorProvider())                            .register(new JettyHttpClientSupplier(httpClient));                        Client client = ClientBuilder.newClient(clientConfig);                        ...

5.6. Using client request and response filters

Filtering requests and responses can provide useful lower-level concept focused on a certain independent aspect or domain that is decoupled from the application layer of building and sending requests, and processing responses. Filters can read/modify the request URI, headers and entity or read/modify the response status, headers and entity.

Jersey contains the following useful client-side filters (and features registering filters) that you may want to use in your applications:

CsrfProtectionFilter: Cross-site request forgery protection filter (addsX-Requested-By to each state changing request).
EncodingFeature: Feature that registers encoding filter which use registeredContentEncoders to encode and decode the communication. The encoding/decoding is performed in interceptor (you don't need to register this interceptor). Check the javadoc of theEncodingFeature in order to use it.
HttpAuthenticationFeature: HTTP Authentication Feature (seeauthentication below).

Note that these features are provided by Jersey, but since they use and implement JAX-RS API, the features should be portable and run in any JAX-RS implementation, not just Jersey. SeeChapter 10,Filters and Interceptors chapter for more information on filters and interceptors.

5.7. Closing connections

The underlying connections are opened for each request and closed after the response is received and entity is processed (entity is read). See the following example:

Example 5.6. Closing connections

final WebTarget target = ... some web targetResponse response = target.path("resource").request().get();System.out.println("Connection is still open.");System.out.println("string response: " + response.readEntity(String.class));System.out.println("Now the connection is closed.");

If you don't read the entity, then you need to close the response manually byresponse.close(). Also if the entity is read into anInputStream (byresponse.readEntity(InputStream.class)), the connection stays open until you finish reading from theInputStream. In that case, the InputStream or the Response should be closed manually at the end of reading from InputStream.

5.8. Injections into client providers

In some cases you might need to inject some custom types into your client provider instance. JAX-RS types do not need to be injected as they are passed as arguments into API methods. Injections into client providers (filters, interceptor) are possible as long as the provider is registered as a class. If the provider is registered as an instance then runtime will not inject the provider. The reason is that this provider instance might be registered into multiple client configurations. For example one instance ofClientRequestFilter can be registered to twoClients.

To solve injection of a custom type into a client provider instance useInjectionManagerClientProvider to extractInjectionManager which can return the required injection. The following example shows how to utilizeInjectionManagerClientProvider:

Example 5.7. InjectionManagerClientProvider example

public static class MyRequestFilter implements ClientRequestFilter {    // this injection does not work as filter is registered as an instance:    // @Inject    // private MyInjectedService service;    @Override    public void filter(ClientRequestContext requestContext) throws IOException {        // use InjectionManagerClientProvider to extract InjectionManager from request        final InjectionManager injectionManager = InjectionManagerClientProvider.getInjectionManager(requestContext);        // and ask for MyInjectedService:        final MyInjectedService service = injectionManager.getInstance(MyInjectedService.class);        final String name = service.getName();        ...    }}

For more information see javadoc ofInjectionManagerClientProvider (and javadoc ofInjectionManagerProvider which supports common Jakarta REST components).

5.9. Securing a Client

This section describes how to setup SSL configuration on Jersey client (using JAX-RS API). The SSL configuration is setup inClientBuilder. The client builder contains methods for definition ofKeyStore,TrustStore or entireSslContext. See the following example:

SSLContext ssl = ... your configured SSL context;Client client = ClientBuilder.newBuilder().sslContext(ssl).build();Response response = client.target("https://example.com/resource").request().get();

The example above shows how to setup a customSslContext to theClientBuilder. Creating aSslContext can be more difficult as you might need to init instance properly with the protocol,KeyStore,TrustStore, etc. Jersey offers a utilitySslConfigurator class that can be used to setup theSslContext. TheSslConfigurator can be configured based on standardized system properties for SSL configuration, so for example you can configure theKeyStore file name using a environment variablejavax.net.ssl.keyStore andSslConfigurator will use such a variable to setup theSslContext. See javadoc ofSslConfigurator for more details. The following code shows how aSslConfigurator can be used to create a custom SSL context.

SslConfigurator sslConfig = SslConfigurator.newInstance()        .trustStoreFile("./truststore_client")        .trustStorePassword("secret-password-for-truststore")        .keyStoreFile("./keystore_client")        .keyPassword("secret-password-for-keystore");SSLContext sslContext = sslConfig.createSSLContext();Client client = ClientBuilder.newBuilder().sslContext(sslContext).build();

Note that you can also setupKeyStore andTrustStore directly on aClientBuilder instance without wrapping them into theSslContext. However, if you setup aSslContext it will override any previously definedKeyStore andTrustStore settings.ClientBuilder also offers a method for defining a customHostnameVerifier implementation.HostnameVerifier implementations are invoked when default host URL verification fails.

Important

A behaviour ofHostnameVerifier is dependent on an http client implementation.HttpUrlConnectorProvider andApacheConnectorProvider work properly, that means that after the unsuccessful URL verificationHostnameVerifier is called and by means of it is possible to revalidate URL using a custom implementation ofHostnameVerifier and go on in a handshake processing.JettyConnectorProvider andGrizzlyConnectorProvider provide only host URL verification and throw aCertificateException without any possibility to use customHostnameVerifier. Moreover, in case ofJettyConnectorProvider there is a propertyJettyClientProperties.ENABLE_SSL_HOSTNAME_VERIFICATION to disable an entire host URL verification mechanism in a handshake.

Important

Note that to utilize HTTP with SSL it is necessary to utilize the“https” scheme.

Currently the default connector providerHttpUrlConnectorProvider provides connectors based onHttpUrlConnection which implement support for SSL defined by JAX-RS configuration discussed in this example.

5.9.1. Http Authentication Support

Jersey supports Basic and Digest HTTP Authentication.

Important

In version of Jersey 3.x both authentication methods are provided by singleFeatureHttpAuthenticationFeature. For migration of older applications:org.glassfish.jersey.client.filter.HttpBasicAuthFilter andorg.glassfish.jersey.client.filter.HttpDigestAuthFilter shall be replaced by those two authentication methods.

In order to enable http authentication support in Jersey client register theHttpAuthenticationFeature. This feature can provide both authentication methods, digest and basic. Feature can work in the following modes:

  • BASIC: Basic preemptive authentication. In preemptive mode the authentication information is send always with each HTTP request. This mode is more usual than the following non-preemptive mode (if you require BASIC authentication you will probably use this preemptive mode). This mode must be combined with usage of SSL/TLS as the password is send only BASE64 encoded.

  • BASIC NON-PREEMPTIVE:Basic non-preemptive authentication. In non-preemptive mode the authentication information is added only when server refuses the request with401 status code and then the request is repeated with authentication information. This mode has negative impact on the performance. The advantage is that it does not send credentials when they are not needed. This mode must be combined with usage of SSL/TLS as the password is send only BASE64 encoded.

  • DIGEST: Http digest authentication. Does not require usage of SSL/TLS.

  • UNIVERSAL: Combination of basic and digest authentication. The feature works in non-preemptive mode which means that it sends requests without authentication information. If401 status code is returned, the request is repeated and an appropriate authentication is used based on the authentication requested in the response (defined inWWW-Authenticate HTTP header). The feature remembers which authentication requests were successful for given URI and next time tries to preemptively authenticate against this URI with latest successful authentication method.

To initialize the feature use static methods and builder of this feature. Example of building the feature in Basic authentication mode:

HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("user", "superSecretPassword");

Example of building the feature in basic non-preemptive mode:

HttpAuthenticationFeature feature = HttpAuthenticationFeature.basicBuilder()    .nonPreemptive().credentials("user", "superSecretPassword").build();

You can also build the feature without any default credentials:

HttpAuthenticationFeature feature = HttpAuthenticationFeature.basicBuilder().build();

In this case you need to supply username and password for each request using request properties:

Response response = client.target("http://localhost:8080/rest/homer/contact").request()    .property(HTTP_AUTHENTICATION_BASIC_USERNAME, "homer")    .property(HTTP_AUTHENTICATION_BASIC_PASSWORD, "p1swd745").get();

This allows you to reuse the same client for authenticating with many different credentials.

See javadoc of theHttpAuthenticationFeature for more details.

5.9.2. Server Name Indication (SNI) Support

When using SSL/TLS protocols for the connection,SNIHostName is set automatically based on the host name in the HTTPS request.

There might be use-cases where theSNIHostName is required to be set for other host than the host specified in the HTTPS request. For those cases, when the HTTP headerHost is set, theSNIHostName is set for the host specified in theHost header. Note that onlyApache Connector, JDK Connector, Netty connector, and the defaultHttpUrlConnector do support this feature.

Sometimes, it may be required theSNIHostName is not set, or it differs from the HTTPHost header. In that case, theClientProperties.SNI_HOST_NAME property can be utilized. The property sets the host name to be used for calculating thejavax.net.ssl.SNIHostName. The property takes precedence over the HTTPHost header.

When the host name in the theClientProperties.SNI_HOST_NAME property matches the HTTP request host, theSNIHostName is not set, and the HTTPHost header is not used for setting theSNIHostName. Turning the SNI off allows for Domain Fronting.

5.10. InvocationInterceptors

Suppose a case that the start of the request is to be logged and even measured. This can be done byClientRequestFilter, which is usually invoked before the request is wired on the network. However, the filter may be called as a last of the filters in the chain. Sure, it can have the highest priority, but the other filters can have the very same priority! Some long-running operations can be performed before the measuring can actually start. Even worse, the filter may even be skipped from the chain by the previous#abortWith!

5.10.1. PreInvocationInterceptor

For this,PreInvocationInterceptor, the code that executes before theClientRequestFilters are invoked, has been added to the client request chain. Jersey ensures all the interceptors are invoked with each request. The interceptor contains a single#beforeRequest method, which corresponds toClientRequestFilter:

                /**                * The method invoked before the request starts.                * @param requestContext the request context shared with                * ClientRequestFilter.                */                void beforeRequest(ClientRequestContext requestContext);

Note that only a single#abortWith is allowed in allPreInvocationInterceptors, otherwise anIllegalStateException is thrown. All the exceptions accumulated inPreInvocationInterceptors are thrown in a single Exception, available through#getSuppressed().

5.10.2. PostInvocationInterceptor

Similarly,ClientResponseFilter seems to be a good place where the total time of the HTTP request can be measured, but similarly toClientRequestFilter, the response filter may not be invoked at all. For this,PostInvocationInterceptor has been introduced. Jersey runtime ensures that everyPostInvocationInterceptor is called. Since an exception can occur during the HTTP request,PostInvocationInterceptor comes with two methods:

                /**                * The method is invoked after a request when no                * is thrown, or the Throwables are resolved                * by previous PostInvocationInterceptor.                *                * @param requestContext the request context.                * @param responseContext the response context                * of the original Response or response context                * defined by the new resolving Response.                */                void afterRequest(ClientRequestContext requestContext, ClientResponseContext responseContext);                /**                * The method is invoked after a Throwable is caught                * during the client request chain processing.                *                * @param requestContext the request context.                * @param exceptionContext the context available to handle the                * caught Throwables.                */                void onException(ClientRequestContext requestContext, ExceptionContext exceptionContext);

The#afterRequest method is executed when no exception has been thrown during the HTTP request,#onException method is executed if the exception has been thrown during the request. It is possible to set a response in#onException, and the consecutivePostInvocationInterceptor will execute its#afterRequest method. The measuring example can looks as follows, then:

                String response = ClientBuilder.newClient().target("path")                    .register(new PreInvocationInterceptor() {                        @Override                        public void beforeRequest(ClientRequestContext requestContext) {                            startTime = System.currentTimeMillis();                        }                    })                    .register(new PostInvocationInterceptor() {                        @Override                        public void afterRequest(ClientRequestContext requestContext, ClientResponseContext responseContext) {                            logDuration(System.currentTimeMillis() - startTime);                        }                        @Override                        public void onException(ClientRequestContext requestContext, ExceptionContext exceptionContext) {                            logDuration(System.currentTimeMillis() - startTime);                        }                    })                    .request().get().readEntity(String.class);

5.11. InvocationBuilderListener

InvocationBuilderListener is an interface that is inspired by Microprofile REST ClientRestClientBuilderListener and it contains a single method:

            /**            * Whenever an Invocation.Builder is created, (i.e. when            * WebTarget#request() is called, this method would be invoked.            *            * @param context the updated InvocationBuilderContext.            */            void onNewBuilder(InvocationBuilderContext context);

InvocationBuilderContext a subset of methods of theInvocation.Builder. It can be used to call the default values of theInvocation.Builder. Since it is invoked at the timeInvocation.Builder is instantiated, any consequent calls of theInvocation.Builder‘s methods will replace the defaults set by theInvocationBuilderListener. For instance, if all the HTTP requests should contain a custom HTTP header, there can be created a feature that would be registered on the client:

            public static class MyFeature implements Feature {                @Override                public boolean configure(FeatureContext context) {                    context.register(                        (InvocationBuilderListener)(l)->                        l.getHeaders().add("MY_HEADER", "MY_VALUE")                    );                    return true;                }            }

5.12. Header Expect:100-continue support

This section describes support of Expect:100-continue in Jersey client using Expect100Continue feature. Jersey client supports given header for default JDK HTTP connector only.

Jersey client Expect100Continue feature

Since Jersey 2.32 it is possible to send Expect:100-continue header from Jersey client. Feature shall be registered in client using (for example)

                target(RESOURCE_PATH).register(Expect100ContinueFeature.basic());

Note that registration can be done in several ways: with basic settings, and with custom settings:

                target(RESOURCE_PATH).register(Expect100ContinueFeature.withCustomThreshold(100L));

Basic registration means that default sending threshold will be used. Value of the default threshold is

                DEFAULT_EXPECT_100_CONTINUE_THRESHOLD_SIZE = 65536L;

Threshold is used to determine allowed size of request after which 100-continue header shall be sent before sending request itself.

Environment properties configuration

Previous paragraph described programmatic way of configuration. However the Expect100Continue feature can be configured using environment variables as well.

Since Jersey client can be influenced through environment variables, there are two variables which come since Jersey 2.32:

                -Djersey.config.client.request.expect.100.continue.processing=true/false                -Djersey.config.client.request.expect.100.continue.threshold.size=12345

First variable can be used to forbid the Expect (100-continue) header be sent at all even though it is registered as described in the previous paragraph. If this property is not provided (or true) and the Expect100Continue feature is registered, sending of the Expect header is enabled.

The second property defines (or modifies) threshold size. So, if the Expect100Continue feature is registered using basic (default threshold size) parameters, value of the threshold can be modified using this property. This is valid for custom threshold as well - when the Expect100Continue feature is registered using withCustomThreshold method its value can be modified anyway by the environment propertyjersey.config.client.request.expect.100.continue.threshold.size.

Important

In other words this variable has precedence over any programmatically set value of the threshold.


Prev   Next
Chapter 4. Application Deployment and Runtime Environments Home Chapter 6. Reactive JAX-RS Client API

[8]ページ先頭

©2009-2025 Movatter.jp