Lightweight REST client implementation for Java 1.7+.
For questions and support please contact services@mercadolibre.com
- Dependencies
- Building a REST Client
- Understanding RESTPool
- Basic Usage
- Advanced Usage
- Using Caches
- Async API
- Serializers
- Logging
- Mocks
You must define the repository resolver
<repository>
<id>java-restclient-mvn-repo</id>
<url>https://raw.github.com/mercadolibre/java-restclient/mvn-repo/</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
and the dependency itself
<dependency>
<groupId>com.mercadolibre.restclient</groupId>
<artifactId>restclient-default</artifactId>
<version>0.0.18</version>
</dependency>
A RestClient instance uses a collection of RESTPool
to route its requests.
So, first you have to build as many instances of RESTPool
as you need using its builder. Please take also a moment to read about RESTPool
concept.
RESTPool aPool = RESTPool.builder()
.withName("my_pool")
.build();
Once you have built all of your pool instances, you can build your RestClient
instance.
Every RestClient
instance also contains a default RESTPool
implementation, with typical parameters. It´ll be used when no pool is defined in a request.
RestClient restClient = RestClient.builder()
.withPool(aPool, bPool)
.build();
This instance is thread safe and it should be shared across resources in your application.
For typical uses, youd only need just one instance of RestClient
in your entire application.
A RESTPool
is a collection of HTTP resource definitions, it´s not just a connection pool.
You can think of it as a separate client, intended to handle some group of resources and that is part of a RestClient
, which instead is intended to bind many RESTPool
instances and abstract HTTP calls.
It groups together connection pool definitions (max connections, socket timeout, etc) with high level features such as proxy, basic authentication, interceptors and so on.
For every request you make you must explicitly specify which RESTPool
should be used, in case you don´t want your request to be handled by the provided default RESTPool
.
Some typical uses are
- Fast and slow resources. You could create a
RESTPool
instance for each one, the former with short timeouts and the latter with longer ones. - Internal and external resources. You could create an instance of
RESTPool
with no proxy for internal usage and another with a proxy for the rest. - Different throughput. If you expect large number of requests to a particular group of resources, you can create a
RESTPool
with a larger number of connections to handle them. - Poor SLA. If you know some resources are in some way unreliable (bad network, pool external implementation, etc) you may define a dedicated
RESTPool
with a more aggressiveRetryStrategy
.
You can use your RestClient
instance right away, by routing requests through its default pool.
Response response = restClient.get("http://yourdomain.com/resource");
In a similar way, you can add custom headers to your request
Headers headers = new Headers().add("Content-Type", "text/plain");
Response response = restClient.get("http://yourdomain.com/resource", headers);
In case you want to send a request with body, you should do it as a byte array.
byte[] body = "{\"text\":\"hello\"}".getBytes();
Response response = restClient.post("http://yourdomain.com/resource", body);
Headers are allowed in a similar way
Headers headers = new Headers().add("Content-Type", "text/plain");
byte[] body = "hello".getBytes();
Response response = restClient.post("http://yourdomain.com/resource", headers, body);
A Response
object provides methods to get HTTP response data
Response response = restClient.get("http://yourdomain.com/resource");
int status = response.getStatus();
int headers = response.getHeaders();
byte[] body = response.getBytes();
Also, if you have a Serializer
registered for current Content-Type, you can get its marshaled data as Object
or as another you provide.
Response response = restClient.get("http://yourdomain.com/get_item");
Object body = response.getData();
Item body = response.getData(Item.class);
In case you would like to route your requests only through the default pool, you could use the provided RestClient
default implementation.
Response response = RestClient.getDefault().get("http://yourdomain.com/resource");
When you use a default implementation the following features apply
- Pool parameters tailored for average usage
- application/json as Content-Type and Accept headers
- gzip/deflate handling for incoming data
It's possible to specify many request scoped features for sync and async calls. Many of then can also be specified poolwise.
Response response = restClient.withPool(aPool).get("http://yourdomain.com/resource");
Response response = restClient.withProxy("http://proxy",80).get("http://yourdomain.com/resource");
Response response = restClient.withAuthentication("http://auth", 80, "user", "pass").get("http://yourdomain.com/resource");
An interceptor applies over a request, just before sending it; or over a response, right after receiving it. In both cases, they are stored in a deque, so you can easily manage the order in which they are applied.
To intercept a request, just implement a RequestInterceptor
, or maybe one of the provided may help.
Response response = restClient.withInterceptorFirst(new YourRequestInterceptor()).get("http://yourdomain.com/resource");
Response response = restClient.withInterceptorLast(new YourRequestInterceptor()).get("http://yourdomain.com/resource");
As for response interceptors, provide your own by implementing ResponseInterceptor
.
Response response = restClient.withInterceptorFirst(new YourResponseInterceptor()).get("http://yourdomain.com/resource");
Response response = restClient.withInterceptorLast(new YourResponseInterceptor()).get("http://yourdomain.com/resource");
You can provide an OutputStream
where you want your data to be streamed. Notice that trying to also fetch data from associated response will be null.
Remember to close stream upon completion.
Response response = restClient.get("http://yourdomain.com/resource", yourOutputStream);
To upload multipart data, just add parts and make your request.
Response response = restClient
.withPart(aPart)
.withPart(bPart)
.post("http://yourdomain.com/resource");
You may define a retry strategy that specifies when a response is to be considered a failure, and an action thatll be run on each retry call.
We provide two basic strategies
Itll just retry for a fixed number of times and wait for a fixed interval between runs
Response response = restClient
.withRetryStrategy(new SimpleRetryStrategy(MAX_RETRIES, WAIT_MS))
.get("http://yourdomain.com/resource");
It´ll increase wait time between retries between a min and a max value, growing exponentially between runs
Response response = restClient
.withRetryStrategy(new ExponentialBackoffRetryStrategy(MIN_MS, MAX_MS))
.get("http://yourdomain.com/resource");
We provide local and memcached cache implementations, thatll cache requests based on their Cache-Control header info.
All cache implementations can be chained, to be made multilevel. Every constructor has a version with the next level as the last argument.
For more information about Cache-Control you can take a look here and here.
RESTCache cache = new RESTLocalCache("my_cache", MAX_ELEMENTS);
Response response = restClient
.withCache(cache)
.get("http://yourdomain.com/resource");
In this case, you must provide a wrapper over your Memcached client
RESTMemcachedClient client = new MyRESTMemcachedClient();
RESTCache cache = new RESTMemcachedCache("my_cache", client);
Response response = restClient
.withCache(cache)
.get("http://yourdomain.com/resource");
Asynchronous calls are handled similar to their synchronous counterpart, we just return a Future<Response>
as a promise of call completion.
A RestException
is raised if request could not be built up, but every other exception will be wrapped around Future´s ExecutionException
upon get.
Caching and retries are handled under the hood. Both of them, as well as call themselves, are handled in a non blocking way.
As an example, an async GET could be made as
Future<Response> response = restClient.asyncGet("http://yourdomain.com/resource");
int status = response.get().getStatus();
There's also the possibility to specify an instance of Callback<Response>
as a completion callback for current requst, instead of getting a Future
instance.
restClient.asyncGet("http://yourdomain.com/resource", myCallback);
When you obtain a Response
, you can get its raw data as a byte array by calling its getBytes()
method. Also you can parse its content according to received Content-Type header, if you previously had registered a serializer capable of handling it.
Serializers.register(ContentType.APPLICATION_JSON, mySerializer);
A Jackson based JSON parser is shipped with restclient-default
package, as well as parsers for usual contents. You can override them by registering a custom Serializer
as shown.
We use slf4j as a common interface for different logging implementations. If you want to collect Rest Client logs, first you must add the binding for your current logging implementation.
For example, if you use log4j 1.2.x, you should add
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
Then you should specify which level you want to apply to restClientLogger
logger.
- ERROR. For failed requests (not 5xx, just in case of an exception is thrown) and cache failures.
- WARN. Exceptions in maintenance components (i.e. staled connections evictor, etc).
- DEBUG. Component initialization and termination.
- TRACE. Request status and time. Retries.
We provide a mock server for testing, which is immediately available in test environment. You can specify which response should be given for a specific request, or if the client should fail instead.
You can forget about mock and cache cleanup after each test, if you make your tests extend from RestClientTestBase
.
<dependency>
<groupId>com.mercadolibre.restclient</groupId>
<artifactId>restclient-core</artifactId>
<version>0.0.18</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
In this simple example, we add a response with status code 200 and a plain text response body for any GET to "http://localhost/endpoint""
MockResponse.builder()
.withURL("http://localhost/endpoint")
.withMethod(GET)
.withStatusCode(200)
.withResponseHeader(ContentType.HEADER_NAME, ContentType.TEXT_PLAIN.toString())
.withResponseHeader("Cache-Control", "max-age=3600")
.withResponseBody("ok")
.build();
A simple in-memory cache is also available for testing purposes.