JBoss.orgCommunity Documentation

第 51 章 RESTEasy Client API

51.1. JAX-RS 2.0 Client API
51.2. RESTEasy Proxy Framework
51.2.1. Abstract Responses
51.2.2. Response proxies
51.2.3. Giving client proxy an ad hoc URI
51.2.4. Sharing an interface between client and server
51.3. Apache HTTP Client 4.x and other backends
51.3.1. HTTP redirect
51.3.2. Configuring SSL
51.3.3. HTTP proxy
51.3.4. Apache HTTP Client 4.3 APIs
51.3.5. Asynchronous HTTP Request Processing
51.3.6. Jetty Client Engine
51.3.7. Vertx Client Engine
51.3.8. Reactor Netty Client Engine

JAX-RS 2.0 introduces a new client API so that you can make http requests to your remote RESTful web services. It is a 'fluent' request building API with really 3 main classes: Client, WebTarget, and Response. The Client interface is a builder of WebTarget instances. WebTarget represents a distinct URL or URL template from which you can build more sub-resource WebTargets or invoke requests on.

There are really two ways to create a Client. Standard way, or you can use the ResteasyClientBuilder class. The advantage of the latter is that it gives you a few more helper methods to configure your client.

            Client client = ClientBuilder.newClient();
            ... or...
            Client client = ClientBuilder.newBuilder().build();
            WebTarget target = client.target("http://foo.com/resource");
            Response response = target.request().get();
            String value = response.readEntity(String.class);
            response.close();  // You should close connections!

            Client client = ClientBuilder.newClient();
            WebTarget target = client.target("http://foo.com/resource");
        

RESTEasy will automatically load a set of default providers. (Basically all classes listed in all META-INF/services/javax.ws.rs.ext.Providers files). Additionally, you can manually register other providers, filters, and interceptors through the Configuration object provided by the method call Client.configuration(). Configuration also lets you set various configuration properties that may be needed.

Each WebTarget has its own Configuration instance which inherits the components and properties registered with its parent. This allows you to set specific configuration options per target resource. For example, username and password.

One RESTEasy extension to the client API is the ability to specify that requests should be sent in "chunked" transfer mode. There are two ways of doing that. One is to configure an org.jboss.resteasy.client.jaxrs.ResteasyWebTarget so that all requests to that target are sent in chunked mode:

      ResteasyClient client = (ResteasyClient)ClientBuilder.newClient();
      ResteasyWebTarget target = client.target("http://localhost:8081/test");
      target.setChunked(b.booleanValue());
      Invocation.Builder request = target.request();
        

Alternatively, it is possible to configure a particular request to be sent in chunked mode:

      ResteasyClient client = (ResteasyClient)ClientBuilder.newClient();
      ResteasyWebTarget target = client.target("http://localhost:8081/test");
      ClientInvocationBuilder request = (ClientInvocationBuilder) target.request();
      request.setChunked(b);
        

Note that org.jboss.resteasy.client.jaxrs.internal.ClientInvocationBuilder, unlike javax.ws.rs.client.Invocation.Builder, is a RESTEasy class.

Note. The ability to send in chunked mode depends on the underlying transport layer; in particular, it depends on which implementation of org.jboss.resteasy.client.jaxrs.ClientHttpEngine is being used. Currently, only the default implementation, ApacheHttpClient43Engine, supports chunked mode. See Section Apache HTTP Client 4.x and other backends for more information.

注意

To follow REST principles and avoid introducing state management in applications, javax.ws.rs.client.Client instances do not provide support for cookie management by default. However, you can enable it if necessary using ResteasyClientBuilder:

				Client client = ((ResteasyClientBuilder) ClientBuilder.newBuilder()).enableCookieManagement().build();
			

The RESTEasy Proxy Framework is the mirror opposite of the JAX-RS server-side specification. Instead of using JAX-RS annotations to map an incoming request to your RESTFul Web Service method, the client framework builds an HTTP request that it uses to invoke on a remote RESTful Web Service. This remote service does not have to be a JAX-RS service and can be any web resource that accepts HTTP requests.

RESTEasy has a client proxy framework that allows you to use JAX-RS annotations to invoke on a remote HTTP resource. The way it works is that you write a Java interface and use JAX-RS annotations on methods and the interface. For example:

public interface SimpleClient
{
   @GET
   @Path("basic")
   @Produces("text/plain")
   String getBasic();

   @PUT
   @Path("basic")
   @Consumes("text/plain")
   void putBasic(String body);

   @GET
   @Path("queryParam")
   @Produces("text/plain")
   String getQueryParam(@QueryParam("param")String param);

   @GET
   @Path("matrixParam")
   @Produces("text/plain")
   String getMatrixParam(@MatrixParam("param")String param);

   @GET
   @Path("uriParam/{param}")
   @Produces("text/plain")
   int getUriParam(@PathParam("param")int param);
}

RESTEasy has a simple API based on Apache HttpClient. You generate a proxy then you can invoke methods on the proxy. The invoked method gets translated to an HTTP request based on how you annotated the method and posted to the server. Here's how you would set this up:

            Client client = ClientBuilder.newClient();
            WebTarget target = client.target("http://example.com/base/uri");
            ResteasyWebTarget rtarget = (ResteasyWebTarget)target;

            SimpleClient simple = rtarget.proxy(SimpleClient.class);
            simple.putBasic("hello world");
        

Alternatively you can use the RESTEasy client extension interfaces directly:

            ResteasyClient client = (ResteasyClient)ClientBuilder.newClient();
            ResteasyWebTarget target = client.target("http://example.com/base/uri");

            SimpleClient simple = target.proxy(SimpleClient.class);
            simple.putBasic("hello world");
        

@CookieParam works the mirror opposite of its server-side counterpart and creates a cookie header to send to the server. You do not need to use @CookieParam if you allocate your own javax.ws.rs.core.Cookie object and pass it as a parameter to a client proxy method. The client framework understands that you are passing a cookie to the server so no extra metadata is needed.

The framework also supports the JAX-RS locator pattern, but on the client side. So, if you have a method annotated only with @Path, that proxy method will return a new proxy of the interface returned by that method.

A further extension implemented by the RESTEasy client proxy framework is the "response proxy facility", where a client proxy method returns an interface that represents the information contained in a javax.ws.rs.core.Response. Such an interface must be annotated with @ResponseObject from package org.jboss.resteasy.annotations, and its methods may be further annotated with @Body, @LinkHeaderParam, and @Status from the same package, as well as javax.ws.rs.HeaderParam. Consider the following example.

   @ResponseObject
   public interface TestResponseObject {
      
      @Status
      int status();

      @Body
      String body();

      @HeaderParam("Content-Type")
      String contentType();
      
      ClientResponse response();
   }

   @Path("test")
   public interface TestClient {
   
      @GET
      TestResponseObject get();
   }

   @Path("test")
   public static class TestResource {

      @GET
      @Produces("text/plain")
      public String get() {
         return "ABC";
      }
   }
        

Here, TestClient will define the client side proxy for TestResource. Note that TestResource.get() returns a String but the proxy based on TestClient will return a TestResponseObject on a call to get():

      Client client = ClientBuilder.newClient();
      TestClient ClientInterface = ProxyBuilder.builder(TestClient.class, client.target("http://localhost:8081")).build();
      TestResponseObject tro = ClientInterface.get();
        

The methods of TestResponseObject provide access to various pieces of information about the response received from TestResponse.get(). This is where the annotations on those methods come into play. status() is annotated with @Status, and a call to status() returns the HTTP status. Similarly, body() returns the returned entity, and contentType() returns the value of the response header Content-Type:

      System.out.println("status: " + tro.status());
      System.out.println("entity: " + tro.body());
      System.out.println("Content-Type: " + tro.contentType());
        

will yield

status: 200
entity: ABC
Content-Type: text/plain;charset=UTF-8        
        

Note that there is one other method in TestResponseObject, response(), that has no annotation. When RESTEasy sees a method in an interface annotated with @ResponseObject that returns a javax.ws.rs.core.Response (or a subclass thereof), it will return a org.jboss.resteasy.client.jaxrs.internal.ClientResponse. For example,

      ClientResponse clientResponse =  tro.response();
      System.out.println("Content-Length: " + clientResponse.getLength());
        

Perhaps the most interesting piece of the response proxy facility is the treatment of methods annotated with @LinkHeaderParam. Its simplest use is to assist in accessing a javax.ws.rs.core.Link returned by a resource method. For example, let's add

      @GET
      @Path("/link-header")
      public Response getWithHeader(@Context UriInfo uri) {
         URI subUri = uri.getAbsolutePathBuilder().path("next-link").build();
         Link link = new LinkBuilderImpl().uri(subUri).rel("nextLink").build();
         return Response.noContent().header("Link", link.toString()).build();
      }
        

to TestResource, add

       @GET
       @Path("link-header")
       ResponseObjectInterface performGetBasedOnHeader();
        

to ClientInterface, and add

       @LinkHeaderParam(rel = "nextLink")
       URI nextLink();
        

to ResponseObjectInterface. Then calling

      ResponseObjectInterface obj = ClientInterface.performGetBasedOnHeader();
      System.out.println("nextLink(): " + obj.nextLink());
        

will access the LinkHeader returned by TestResource.getWithHeader():

nextlink: http://localhost:8081/test/link-header/next-link
        

Last but not least, let's add

      @GET
      @Produces("text/plain")
      @Path("/link-header/next-link")
      public String getHeaderForward() {
         return "forwarded";
      }
        

to TestResource and

       @GET
       @LinkHeaderParam(rel = "nextLink")
       String followNextLink();
        

to ResponseObjectInterface. Note that, unlike ResponseObjectInterface.nextLink(), followNextLink() is annotated with @GET; that is, it qualifies as (the client proxy to) a resource method. When executing followNextLink(), RESTEasy will retrieve the value of the Link returned by TestResource.getWithHeader() and then will make a GET invocation on the URL in that Link. Calling

      System.out.println("followNextLink(): " + obj.followNextLink());
        

causes RESTEasy to retrieve the URL http://localhost:8081/test/link-header/next-link from the call to TestResource.getWithHeader() and then perform a GET on it, invoking TestResource.getHeaderForward():

followNextLink(): forwarded
        

Note. This facility for extracting a URL and following it is a step toward supporting the Representation State Transfer principle of HATEOAS. For more information, see RESTful Java with JAX-RS 2.0, 2nd Edition by Bill Burke.

Network communication between the client and server is handled by default in RESTEasy. The interface between the RESTEasy Client Framework and the network is defined by RESTEasy's ClientHttpEngine interface. RESTEasy ships with multiple implementations of this interface.

The default implementation is ApacheHttpClient43Engine, which uses version 4.3 of the HttpClient from the Apache HttpComponents project.

ApacheHttpAsyncClient4Engine, instead, is built on top of HttpAsyncClient (still from the Apache HttpComponents project) with internally dispatches requests using a non-blocking IO model.

JettyClientEngine is built on top of Eclipse Jetty HTTP engine, which is possibly an interesting option for those already running on the Jetty server.

VertxClientHttpEngine is built on top of Eclipse Vert.x, which provides a non-blocking HTTP client based on Vert.x framework.

ReactorNettyClientHttpEngine is built on top of Reactor Netty, which provides a non-blocking HTTP client based on Netty framework.

Finally, InMemoryClientEngine is an implementation that dispatches requests to a server in the same JVM and URLConnectionEngine is an implementation that uses java.net.HttpURLConnection.


The RESTEasy Client Framework can also be customized. The user can provide their own implementations of ClientHttpEngine to the ResteasyClient.

ClientHttpEngine myEngine = new ClientHttpEngine() {
    protected SSLContext sslContext;
    protected HostnameVerifier hostnameVerifier;


    @Override
    public ClientResponse invoke(ClientInvocation request) {
        // implement your processing code and return a
        // org.jboss.resteasy.client.jaxrs.internal.ClientResponse
        // object.
    }

    @Override
    public SSLContext getSslContext() {
       return sslContext;
    }

    @Override
    public HostnameVerifier getHostnameVerifier() {
       return hostnameVerifier;
    }

    @Override
    public void close() {
       // do nothing
    }
};

ResteasyClient client = ((ResteasyClientBuilder)ClientBuilder.newBuilder()).httpEngine(myEngine).build();
       

RESTEasy and HttpClient make reasonable default decisions so that it is possible to use the client framework without ever referencing HttpClient. For some applications it may be necessary to drill down into the HttpClient details. ApacheHttpClient43Engine can be supplied with an instance of org.apache.http.client.HttpClient and an instance of org.apache.http.protocol.HttpContext, which can carry additional configuration details into the HttpClient layer.

HttpContextProvider is a RESTEasy provided interface through which a custom HttpContext is supplied to ApacheHttpClient43Engine.

package org.jboss.resteasy.client.jaxrs.engines;

import org.apache.http.protocol.HttpContext;

public interface HttpContextProvider {
   HttpContext getContext();
}
       

Here is an example of providing a custom HttpContext

DefaultHttpClient httpClient = new DefaultHttpClient();
ApacheHttpClient43Engine engine = new ApacheHttpClient43Engine(httpClient,
   new HttpContextProvider() {
           @Override
           public HttpContext getContext() {
              // Configure HttpClient to authenticate preemptively
              // by prepopulating the authentication data cache.
              // 1. Create AuthCache instance
              AuthCache authCache = new BasicAuthCache();
              // 2. Generate BASIC scheme object and add it to the local auth cache
              BasicScheme basicAuth = new BasicScheme();
              authCache.put(getHttpHost(url), basicAuth);
              // 3. Add AuthCache to the execution context
              BasicHttpContext localContext = new BasicHttpContext();
              localContext.setAttribute(ClientContext.AUTH_CACHE, authCache);
              return localContext;
           }
});
       

To enable SSL on client, a ClientHttpEngine containing a SSLContext can be created to build client as in the following example:

ClientHttpEngine myEngine = new ClientHttpEngine() {
   ...
   public void setSslContext(SSLContext sslContext) {
      this.sslContext = sslContext;
   }

   @Override
   public HostnameVerifier getHostnameVerifier() {
      return hostnameVerifier;
   }
};
myEngine.setSslContext(mySslContext)
ResteasyClient client = ((ResteasyClientBuilder)ClientBuilder.newBuilder()).httpEngine(myEngine).build();
            

An alternative is to set up a keystore and truststore and pass a custom SslContext to ClientBuilder:

Client sslClient = ClientBuilder.newBuilder().sslContext(mySslContext).build();
            

If you don't want to create a SSLContext, you can build client with a keystore and truststore. Note if both SSLContext and keystore/truststore are configured, the later will be ignored by Resteasy ClientBuilder.

Client sslClient = ClientBuilder.newBuilder().keystore(keystore,mypassword).
                      trustKeystore(trustStore).build();
            

During handshaking, a custom HostNameVerifier can be called to allow the connection if URL's hostname and the server's identification hostname match.

Client sslClient =  ((ResteasyClientBuilder)ClientBuilder.newBuilder()).sslContext(mysslContext)
                       .hostnameVerifier(myhostnameVerifier).build();
            

Resteasy provides another simple way to set up a HostnameVerifier. It allows configuring ResteasyClientBuilder with a HostnameVerificationPolicy without creating a custom HostNameVerifier:

Client sslClient =  ((ResteasyClientBuilder)ClientBuilder.newBuilder()).sslContext(mysslContext)
                       .hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY).build();
            

  • Setting HostnameVerificationPolicy.ANY will allow all connections without a check.
  • HostnameVerificationPolicy.WILDCARD only allows wildcards in subdomain names i.e. *.foo.com.
  • HostnameVerificationPolicy.STRICT checks if DNS names match the content of the Public Suffix List (https://publicsuffix.org/list/public_suffix_list.dat). Please note if this public suffix list isn't the check you want, you should create your own HostNameVerifier instead of this policy setting.

The RESTEasy Client framework automatically creates and properly configures the underlying Apache HTTP Client engine. When the ApacheHttpClient43Engine is manually created, though, the user can either let it build and use a default HttpClient instance or provide a custom one:

public ApacheHttpClient43Engine() {
   ...
}

public ApacheHttpClient43Engine(HttpClient httpClient) {
   ...
}

public ApacheHttpClient43Engine(HttpClient httpClient, boolean closeHttpClient) {
   ...
}
     

The closeHttpClient parameter on the last constructor above allows controlling whether the Apache HttpClient is to be closed upon engine finalization. The default value is true. When a custom HttpClient instance is not provided, the default instance will always be closed together with the engine.

For more information about HttpClient (4.x), see the documentation at http://hc.apache.org/httpcomponents-client-ga/tutorial/html/.

Note. It is important to understand the difference between "releasing" a connection and "closing" a connection. Releasing a connection makes it available for reuse. Closing a connection frees its resources and makes it unusable.

If an execution of a request or a call on a proxy returns a class other than Response, then RESTEasy will take care of releasing the connection. For example, in the fragments

WebTarget target = client.target("http://localhost:8081/customer/123");
String answer = target.request().get(String.class);
     

or

ResteasyWebTarget target = client.target("http://localhost:8081/customer/123");
RegistryStats stats = target.proxy(RegistryStats.class);
RegistryData data = stats.get();
     

RESTEasy will release the connection under the covers. The only counterexample is the case in which the response is an instance of InputStream, which must be closed explicitly.

On the other hand, if the result of an invocation is an instance of Response, then Response.close() method must be used to released the connection.

WebTarget target = client.target("http://localhost:8081/customer/123");
Response response = target.request().get();
System.out.println(response.getStatus());
response.close();
     

You should probably execute this in a try/finally block. Again, releasing a connection only makes it available for another use. It does not normally close the socket.

On the other hand, ApacheHttpClient43Engine.finalize() will close any open sockets, unless the user set closeHttpClient as false when building the engine, in which case he is responsible for closing the connections.

Note that if ApacheHttpClient43Engine has created its own instance of HttpClient, it is not necessary to wait for finalize() to close open sockets. The ClientHttpEngine interface has a close() method for this purpose.

If your javax.ws.rs.client.Client class has created the engine automatically for you, you should call Client.close() and this will clean up any socket connections.

Finally, given having explicit finalize() methods can badly affect performances, the org.jboss.resteasy.client.jaxrs.engines.ManualClosingApacheHttpClient43Engine flavour of org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient43Engine can be used. With that the user is always responsible for calling close() as no finalize() is there to do that before object garbage collection.

RESTEasy's default async engine implementation class is ApacheHttpAsyncClient4Engine. It can be set as the active engine by calling method useAsyncHttpEngine in ResteasyClientBuilder.

    Client asyncClient = ((ResteasyClientBuilder)ClientBuilder.newBuilder()).useAsyncHttpEngine()
                             .build();
    Future<Response> future = asyncClient
                             .target("http://locahost:8080/test").request()
                             .async().get();
    Response res = future.get();
    Assert.assertEquals(HttpResponseCodes.SC_OK, res.getStatus());
    String entity = res.readEntity(String.class);