1. Introduction
The HTTP Requests library provides a clean and simplified interface to make HTTP requests.
The spirit of this library is to make it trivial to do easy things with HTTP requests. It can accomplish the most common use cases for an HTTP client, but does not aim to be a complete HTTP client implementation.
At its core, the library defines a framework for making requests. A built-in implementation of this framework based on Java’s built-in HTTP classes is included to make the library extremely lightweight, with its only external dependency being SLF4J. There are also several providers available that use popular HTTP client libraries, such as Jersey and Apache HTTP Components Client. When one of these providers is used, the library serves as a facade, making the use of these popular libraries much easier and your code more concise.
Indeed, the driving philosophy behind this project is to serve as a facade for HTTP requests, so that library authors may perform HTTP operations without dictating what HTTP library is required, much like SLF4J does for logging.
2. Installing
The library is split into several libraries, which can all be found on jCenter and Maven Central. The library contains a core module, which contains the interfaces and base implementations of the library. There are also libraries that provide support for popular HTTP client libraries, as well as various extensions that provide additional functionality. The table below lists out all of the libraries that exist for the HTTP Requests library and what their purposes are.
Module | Purpose |
---|---|
|
A bill-of-materials POM for all libraries. |
|
Contains the core interfaces of the library, in addition to a built-in HTTP
client implementation built on top of Java’s |
|
Adds support for Jersey 1.x. |
|
Adds support for Jersey 2.x. |
|
Adds support for Apache HttpComponents Client. |
|
Adds entity converters for |
|
Enables automatic configuration of the HTTP requests components as Spring beans. |
|
Adds Groovy DSLs to the library and Groovy-specific entity converters. |
|
Adds support for mocking HTTP requests in unit tests. |
All modules are deployed under the group com.budjb .
|
As an example, opting to add Jersey 2.x support and Jackson converters, Gradle might be configured like below:
apply plugin: "java"
repositories {
mavenCentral()
}
dependencies {
compile "com.budjb:http-requests-jersey2:2.0.7"
compile "com.budjb:http-requests-jackson:2.0.7"
}
Additionally, the bill of materials POM is available to keep dependencies in sync without the need to specify their version numbers.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "io.spring.gradle:dependency-management-plugin:1.0.6.RELEASE"
}
}
apply plugin: "java"
apply plugin: "io.spring.dependency-management"
repositories {
mavenCentral()
}
dependencyManagement {
imports { mavenBom "com.budjb:http-requests-bom:2.0.7" }
}
dependencies {
compile "com.budjb:http-requests-jersey2"
compile "com.budjb:http-requests-jackson"
}
3. Components
3.1. EntityConverterManager
The EntityConverterManager
serves as both a place to aggregate entity converters and as an entry point to object
marshalling.
EntityConverterManager converterManager = new EntityConverterManager();
converterManager.add(new ExampleWriter());
converterManager.add(new ExampleReader());
The converter manager may be used directly to marshal objects to and from InputStream
instances, but it is intended
to be a support component of HttpClient
. Therefore, the converter manager is a requirement to build an
HttpClientFactory
.
More information can be found in the section that covers entity converters. |
3.2. HttpClientFactory
An HttpClient
is used to make requests, but should not be created directly. HTTP client providers contain
implementations of HttpClient
specific to the HTTP client being wrapped. In order to ensure that authors do not need
to worry about what type of HttpClient
needs to be created, an HttpClientFactory
should be used to create the client
instances using the createHttpClient()
method.
The HttpClientFactory
interface is implemented by provider libraries and can be created directly. As an example,
the Jersey 1.x module’s factory can be used to create a Jersey-specific HttpClient
instance.
// Note that EntityConverterManager is required by HttpClientFactory implementations
HttpClientFactory factory = new JerseyHttpClientFactory(entityConverterManager);
HttpClient client = factory.createHttpClient();
The HttpClientFactory can be registered as a Spring bean when applications utilize Spring Framework so that it
may be injected as necessary. If Spring is not in use, the HttpClientFactoryHolder may be used as a singleton holder
for the HttpClientFactory instance.
|
Each provider library declares its own HttpClientFactory
implementation, which can be looked up in the following
table.
Provider | HttpClientFactory Class | |
---|---|---|
http-requests-core |
|
|
http-requests-jersey1 |
|
|
http-requests-jersey2 |
|
|
http-requests-httpcomponents-client |
|
3.3. HttpRequest
The HttpRequest
object is used to configure an HTTP request. The properties contained within HttpRequest
both define
a request and influence the behavior of the HTTP client.
Property | Type | Default Value | Description |
---|---|---|---|
|
|
The URI of the request, including the protocol, port, host, and path. |
|
|
|
Request headers. Individual header names can contain multiple values. |
|
|
|
Query parameters of the request. Individual query parameters can contain multiple values. |
|
|
|
0 |
The amount of time, in milliseconds, the request
should wait for the connection to time out. A value
|
|
|
0 |
The amount of time, in milliseconds, the request should wait to time out while waiting for content. A value`0` means there is no timeout. |
|
|
|
Whether SSL certification trust chains and host names should be validated. |
|
|
|
Whether the HTTP client should follow redirect responses received from the host. |
|
|
|
If |
|
Filters to apply to the request. |
3.3.1. Building an HttpRequest
The HttpRequest
supports a fluent syntax, where calls to setter methods may be chained.
Using the builder syntax, an HttpRequest
object may be created like the following:
HttpRequest request = new HttpRequest()
.setUri("http://example.com")
.setHeader("Accept", "application/json")
.setFollowRedirects(false);
3.3.2. Behavior Modification
As mentioned before, some of the HttpRequest
properties influence the behavior of the HTTP client. This section will
identify those properties and their behavior.
followRedirects
By default, the HTTP client will follow redirect responses sent by the server by repeating the request against the new
HTTP URI. If this behavior should be avoided, it can be disabled by setting followRedirects
to false.
When this happens, the HttpResponse object will likely not contain much information other than the HTTP status
code.
|
bufferResponseEntity
When this is enabled, the response entity (if there is one) is automatically read from the HTTP connection’s
InputStream
into a byte array stored in the HttpResponse
object, and the stream is closed. This allows the entity
to be retrieved multiple times. When disabled, the response InputStream
may typically only be read only once.
It is very important that the response is closed if this functionality is disabled, or the underlying system
resources may not be freed up. This is true even when an HTTP status exception is thrown via the
HttpStatusExceptionFilter , which includes the HttpResponse in the thrown exception.
|
sslValidated
Disabling this functionality makes HTTP requests inherently insecure and is not recommended in situations where host identity verification is important, such as production environments. |
By default, all SSL and TLS connections go through the standard validation steps, and may throw an IOException
if
SSL validation fails. Sometimes, authors may wish to bypass this functionality when they know SSL validation will fail
but they still trust the remote host. When this property is set to true, SSL validation will not occur and the request
should succeed unless there’s a more serious SSL issue encountered.
Disabling SSL validation is a great tool for quickly deploying development environments with self-signed certificates that are not stored in Java keystores. |
3.4. HttpEntity
An HttpEntity
is the container object for an entity to be used for a request or returned with a response. The
HttpEntity
class itself contains an InputStream
, which contains the actual content of the entity, and optionally
a content type and character set.
3.5. HttpClient
The HttpClient
contains many methods used to initiate requests. The HttpClient
should not be created directly, but
rather the use of the createHttpClient
method in HttpClientFactory
.
Methods in the client are named after the HTTP verb or method to be used for the request.
-
GET
-
POST
-
PUT
-
DELETE
-
TRACE
-
OPTIONS
-
HEAD
-
PATCH
3.5.1. Client Methods
Each HTTP verb has a corresponding method in HttpClient
. Each method takes either an HttpRequest
, or a String
for
simple requests. All methods in the client have the following methods.
/**
* Perform an HTTP request.
*
* @param request Request properties to use with the HTTP request.
* @return A {@link HttpResponse} object containing the properties of the server response.
* @throws IOException When an underlying IO exception occurs.
*/
HttpResponse method(HttpRequest request) throws IOException;
/**
* Perform an HTTP request.
*
* @param uri URI of the request.
* @return A {@link HttpResponse} object containing the properties of the server response.
* @throws IOException When an underlying IO exception occurs.
* @throws URISyntaxException When the syntax of the request is incorrect.
*/
HttpResponse method(String uri) throws IOException, URISyntaxException;
Some HTTP verbs support request entities (or bodies). Specifically, all HTTP verbs except GET and TRACE support request entities. Entities may be provided with a few different types.
Certain HTTP verbs, such as PATCH and DELETE , have limited support both in provider HTTP clients and in
HTTP servers.
|
Type | Description |
---|---|
|
Contains an object that has already been serialized into an |
|
Contains an object that has already been serialized, with no content type information. |
|
Contains an object that will be serialized with the |
|
An object that will be serialized with the |
HTTP verbs that support entities have additional methods defined in the HttpClient
, with the following structure.
/**
* Perform an HTTP request with the given request entity.
*
* @param request Request properties to use with the HTTP request.
* @param entity An HTTP entity.
* @return A {@link HttpResponse} object containing the properties of the server response.
* @throws IOException When an underlying IO exception occurs.
*/
HttpResponse method(HttpRequest request, HttpEntity entity) throws IOException;
/**
* Perform an HTTP request with the given request entity.
*
* @param uri URI of the request.
* @param entity An HTTP entity.
* @return A {@link HttpResponse} object containing the properties of the server response.
* @throws IOException When an underlying IO exception occurs.
* @throws URISyntaxException When the syntax of the request is incorrect.
*/
HttpResponse method(String uri, HttpEntity entity) throws IOException, URISyntaxException;
/**
* Perform an HTTP request with the given input stream.
*
* @param request Request properties to use with the HTTP request.
* @param inputStream An {@link InputStream} containing the response body.
* @return A {@link HttpResponse} object containing the properties of the server response.
* @throws IOException When an underlying IO exception occurs.
*/
HttpResponse method(HttpRequest request, InputStream inputStream) throws IOException;
/**
* Perform an HTTP request with the given input stream.
*
* @param uri URI of the request.
* @param inputStream An {@link InputStream} containing the response body.
* @return A {@link HttpResponse} object containing the properties of the server response.
* @throws IOException When an underlying IO exception occurs.
* @throws URISyntaxException When the syntax of the request is incorrect.
*/
HttpResponse method(String uri, InputStream inputStream) throws IOException, URISyntaxException;
/**
* Perform an HTTP request with the given entity.
*
* The entity will be converted if an appropriate {@link EntityWriter} can be found. If no
* writer can be found, an {@link UnsupportedConversionException} will be thrown.
*
* @param request Request properties to use with the HTTP request.
* @param entity Request entity.
* @return A {@link HttpResponse} object containing the properties of the server response.
* @throws IOException When an underlying IO exception occurs.
* @throws UnsupportedConversionException When an error in entity conversion occurs.
*/
HttpResponse method(HttpRequest request, ConvertingHttpEntity entity) throws IOException, UnsupportedConversionException;
/**
* Perform an HTTP request with the given entity.
*
* The entity will be converted if an appropriate {@link EntityWriter} can be found. If no
* writer can be found, an {@link UnsupportedConversionException} will be thrown.
*
* @param uri URI of the request.
* @param entity Request entity.
* @return A {@link HttpResponse} object containing the properties of the server response.
* @throws IOException When an underlying IO exception occurs.
* @throws URISyntaxException When the syntax of the request is incorrect.
* @throws UnsupportedConversionException When an error in entity conversion occurs.
*/
HttpResponse method(HttpRequest request, ConvertingHttpEntity entity) throws IOException, UnsupportedConversionException;
/**
* Perform an HTTP request with the given entity.
*
* The entity will be converted if an appropriate {@link EntityWriter} can be found. If no
* writer can be found, an {@link UnsupportedConversionException} will be thrown.
*
* @param request Request properties to use with the HTTP request.
* @param entity Request entity.
* @return A {@link HttpResponse} object containing the properties of the server response.
* @throws IOException When an underlying IO exception occurs.
* @throws UnsupportedConversionException When an error in entity conversion occurs.
*/
HttpResponse method(HttpRequest request, Object entity) throws IOException, UnsupportedConversionException;
/**
* Perform an HTTP request with the given entity.
*
* The entity will be converted if an appropriate {@link EntityWriter} can be found. If no
* writer can be found, an {@link UnsupportedConversionException} will be thrown.
*
* @param uri URI of the request.
* @param entity Request entity.
* @return A {@link HttpResponse} object containing the properties of the server response.
* @throws IOException When an underlying IO exception occurs.
* @throws URISyntaxException When the syntax of the request is incorrect.
* @throws UnsupportedConversionException When an error in entity conversion occurs.
*/
HttpResponse method(String uri, Object entity) throws IOException, URISyntaxException, UnsupportedConversionException;
HttpResponse response = client.get("http://example.com/foo/bar");
HttpResponse response = client.post("http://example.com/foo/bar", "content");
3.6. Handling Responses
HttpClient
request methods return an HttpResponse
object that contains information about the response.
3.6.1. HttpResponse Properties
Property | Type | Description |
---|---|---|
|
|
HTTP status code of the response. |
|
|
Response headers. |
|
|
Response entity (may be |
To ensure that all resources used during the request are released, it is critical that the response is
closed via the HttpResponse.close() method once the response is no longer needed.
|
3.6.2. Response Entities
The response entity can be retrieved by using the getEntity()
method of the HttpResponse
, which return an
HttpEntity
containing the entity. By default, the response entity, if available, is buffered and the response is
closed. Subsequent calls to retrieve the response entity will reproduce the entity an unlimited amount of times. If the
request properties disabled this functionality, the response’s entity can be read only once, but it does not require the memory space to buffer the entity in the HttpResponse
object. Additionally, it
is important to close the response when this functionality is disabled.
When bufferResponseEntity is false , it is very important that the HttpResponse is closed
when it is no longer needed. If it is not closed, underlying system resources may not be freed up. The close() method
can be called on the HttpResponse to properly clean up the request.
|
The HttpResponse
supports entity conversion using the getEntity(Class<?> type)
method. If an
entity reader is registered that supports the requested type, it will be converted and returned.
HttpResponse response = client.get("https://example.com/api/foo_resource");
try {
InputStream inputStream = response.getEntity();
// do some work with the input stream
}
finally {
response.close()
}
HttpResponse response = client.get("https://example.com/api/foo_resource");
try {
String inputStream = response.getEntity(String.class);
}
finally {
response.close();
}
The above example assumes that an entity converter that can read the entity into a String is registered with
the EntityConverterManager that the HttpClientFactory was built with.
|
4. Filters
Filters may be added to the HttpRequest
to modify or react to certain events in the lifecycle of an HTTP request.
There are various filter types available, each providing an injection point into the request lifecycle, as explained
below.
4.1. RequestFilter
Applications may need to intercept an HTTP request directly before the request is actually made to modify the request
in some manner. A great example of a use of this filter is the built-in BasicAuthFilter
// Adding a BasicAuthFilter to the request will automatically apply the required
// headers for Basic Authentication.
HttpRequest request = new HttpRequest("https://example.com");
request.addFilter(new BasicAuthFilter('username', 'password'));
4.2. OutputStreamFilter
This filter allows applications to modify the OutputStream
sent to the actual HTTP connection before it is sent.
An example of a built-in filter that uses this functionality is the GZIPFilter
, which wraps the output stream
in a GZIPOutputStream
.
4.3. HttpClientResponseFilter
The response filter is called after a response has been received from the HTTP request. This filter provides an
opportunity to modify the HttpResponse
before it is returned.
4.4. RetryFilter
The retry filter allows applications to determine if a request should be retried based on some criteria. If any one retry filter determines that the request should be retried, the request will be submitted again until no more retry filters request a retry.
A use case for this functionality is authentication schemes that provide an authentication token that can be reused until it expires. A retry filter might be created for this type of authentication scheme that inspects the response of a call to the service that requires the authentication token, and re-authenticates if the previous authentication token has expired. Once a new authentication token is obtained, the original request can be retried with the updated authentication token.
There is a built-in AuthenticationTokenHeaderFilter class that creates a framework for this type of
authentication scheme.
|
4.5. LifecycleFilter
This filter provides several integration points related to the lifecycle of the request. The interfaces includes
default, empty implementations of each available method so that only the methods a filter need only override the methods
it requires. Each of the methods in this interface as passed an HttpContext
, which contains the request, method,
request entity, the response, and a property repository. This allows filters to maintain some state during the course
of the request. The methods available are as follows.
-
onStart
is called before any processing of the request is started. -
onRequest
is called directly before the request is executed, and is the last chance to modify the request before it is sent over the wire. -
onResponse
is similar in purpose toonRequest
, but is called once a response has been received. -
onComplete
is called once the HTTP request has completed and no further retries have been requested. This is the final filter opportunity before the response is returned to the caller.
4.6. Built-In Filters
The library includes several built-in filters for common use cases.
4.6.1. BasicAuthFilter
The basic authentication filter provides simple basic auth functionality with a username and a password.
4.6.2. GZIPFilter
The GZIP filter wraps the output of the request with a GZIPOutputStream
so that the entity’s contents are compressed
with the GZIP algorithm. The appropriate Content-Encoding
header is set.
4.6.3. DeflateFilter
The deflate filter wraps the output of the request with a DeflateOutputStream
so that the entity’s contents are
compressed with the deflate algorithm. The appropriate Content-Encoding
header is set.
4.6.4. AuthenticationTokenHeaderFilter
This filter is abstract and provides a framework for HTTP requests that require token authentication and follow the flow:
-
If no authentication token is known, authenticate.
-
Make the HTTP request.
-
If HTTP status 401 is received, and no retries have been attempted, re-authenticate and request a retry.
The filter provides integration points so that authentication logic and authentication token header names can be provided by concrete implementations of the filter.
4.6.5. LoggingFilter
The logging filter captures the contents of the request and the response and logs it. The LoggingFilter
itself is an
abstract class, and there are a couple bundled implementations.
ConsoleLoggingFilter
The ConsoleLoggingFilter
is a concrete implementation of LoggingFilter
that prints the contents of the log to the
Java console.
Slf4jLoggingFilter
The Slf4jLoggingFilter
is a concrete implementation of LoggingFilter
that logs the contents of the log to an SLF4J
logger. The filter can be configured with a logger name to use, otherwise the fully-qualified class name of the logging
filter will be used. By default, the log is configured to log at the TRACE
level. The filter can be configured to log
at a different level if desired.
4.6.6. HttpStatusExceptionFilter
The HttpStatusExceptionFilter
filter modifies how the response status is treated. When an HTTP status that
is not considered successful (that is, not in the 200-299 range), an exception representing that status code is thrown.
For example, if an HTTP 404
is received, an HttpNotFoundException
will be thrown.
Use this functionality to create different error handling for expected error cases. |
If no appropriate exception exists for the HTTP status code that is received, an HttpStatusException
is thrown
instead. This exception is actually the superclass of all HTTP status specific exceptions.
These exceptions contain the following information about the response:
Property | Description |
---|---|
status |
The HTTP status code of the response, as an |
response |
The |
5. Entity Converters
Entity converters are responsible for marshalling request and response entities to and from a byte stream. This prevents applications from having to make separate calls to helper methods or classes to do the conversion.
Entity converters are registered with an EntityConverterManager
, which is a requirement to create an
HttpClientFactory
. This allows the HttpClient
to marshal objects to use as request entities. The manager is
subsequently passed to HttpResponse
objects so that it too may marshal response objects.
5.1. Entity Readers
Entity readers are responsible for marshalling the byte stream of a response entity into some other type of object. Applications can create a new entity reader for a custom object type that contains the logic to read relevant information from the byte stream and create the custom object from it, bound with the data from the response.
Entity readers should implement the EntityReader
interface.
5.2. Entity Writers
Entity writers are responsible for marshalling an object into a byte stream for use in a request. Much like entity readers, entity writers provide applications a method to convert custom objects for use in a request without having to handle that logic separately from the request call.
Entity writers should implement the EntityWriter
interface.
Entity writers have the option to provide a default content type if it was able to marshal the entity. The default content type is only used if one was not otherwise provided with the entity. |
5.3. Built-In Converters
The library contains several out of the box entity converters that may be used without including any other dependencies.
-
ByteArrayEntityReader
-
ByteArrayEntityWriter
-
FormDataEntityWriter
-
StringEntityReader
-
StringEntityWriter
5.4. Jackson Converters
The http-requests-jackson
library adds entity converters for List
and Map
objects using the
Jackson[https://github.com/FasterXML/jackson] library. The following entity converters are available:
-
JacksonListReader
-
JacksonMapReader
-
JacksonMapListWriter
5.5. Groovy Converters
The http-requests-groovy
library adds entity converters specific to Groovy class types. The following
entity converters are available:
-
GStringEntityWriter
-
JsonEntityWriter
-
JsonEntityReader
-
XmlSlurperEntityReader
If the Groovy JSON converters are in use, the Jackson library is redundant. The groovy variants utilize
JsonSlurper and JsonBuilder .
|
6. Spring Boot Integration
The http-requests-spring
is available to automatically configure the various library components to the application
context as Spring beans. It supports automatic entity converter registry for all entity converters that are registered
as beans, and it automatically configures the HttpClientFactory
instance as a bean depending on which provider is
present on the classpath.
Auto configuration must be enabled for the HTTP requests components to be automatically registered.
This can be enabled with the EnableAutoConfiguration annotation on the main application class or a Configuration
class. Alternatively, the HttpRequestsAutoConfiguration configuration class may be configured as a bean to accomplish
automatic configuration of the library.
|
With the Spring support added, other beans may inject the HttpClientFactory
(with the name httpClientFactory
) bean
as a dependency.
7. Examples
7.1. Using the Library
This example walks through creating the various components of the library and how they are used. The example is admittedly verbose, so that each component is elaborated upon. In real world applications, the initialization code and requests are much more likely to be separated.
// The entity converter manager is built and contains any entity converters
// needed by the application.
EntityConverterManager entityConverterManager = new EntityConverterManager();
entityConverterManager.add(new StringEntityReader());
// The HttpClientFactory is built, using the built-in Java client.
// The converter manager is passed in as a dependency.
HttpClientFactory httpClientFactory = ReferenceHttpClientFactory(entityConverterManager);
// The factory may be stored in the holder if other parts of the application need it.
//HttpClientFactoryHolder.setHttpClientFactory(httpClientFactory);
// An HttpClient is created from the factory.
HttpClient client = httpClientFactory.createHttpClient();
try {
// Make the HTTP request, and capture the response.
HttpResponse response = client.get("https://reqres.in/api/users");
// Marshal the response entity as a String (since the StringEntityReader was registered
// with the EntityConverterManager above).
String result = response.getEntity(String.class);
// Print the result.
System.out.println("HTTP response is:\n" + result);
}
catch (Exception e) {
System.out.println("unexpected exception occurred");
e.printStackTrace();
}
7.2. Using Spring Integration
Using the Spring Boot integration removes the need for applications to bootstrap the HttpClientFactory
, due to the
automatic configuration of the library’s beans. The above example is significantly simplified in the Spring Boot
example below. Notice that the code does not create an EntityConverterManager
, register entity converters with it, or
create the HttpClientFactory
. This is all handled via the Spring library.
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
/**
* The command line runner is a simple way to run some code just as the application
* has started up.
*
* For this example, the commandLineRunner bean simply takes the httpClientFactory
* bean as a dependency and makes a request.
*/
@Bean
CommandLineRunner commandLineRunner(HttpClientFactory httpClientFactory) {
return args -> {
// Create the client.
HttpClient client = httpClientFactory.createHttpClient();
// Make the request.
HttpResponse response = client.get("https://reqres.in/api/users");
// Marshal the response entity as a String.
String result = response.getEntity(String.class);
// Print the result.
System.out.println("HTTP response is:\n" + result);
};
}
}
7.3. Additional Samples
More samples can be found in the samples project on GitHub.
8. Testing
Since it is not ideal to make actual HTTP requests while performing unit tests, a mock library exists that allows tests to inject responses for expected HTTP requests made by the code being tested. Using the built-in request mocking functionality avoids the need to use dynamically generated mocking and stubbing libraries such as Mockito or Spock.
8.1. Request Mocks
A request mock contains the details about a potential HTTP request that should have a
mocked response. The RequestMock
object contains most of the same properties that
a typical HttpRequest
does, such as request URI, headers, and query parameters,
as well as some additional properties, such as the HTTP method that should match.
It also includes properties about the response, such as response status code, headers, and the response entity.
The minimal requirement for a mock to match a request is that the URI must match the request. If a mock has other details set, such as request headers, HTTP method, or query parameters, those also must match. If they are not specified in the mock, they are not considered as part of the match criteria. This allows tests to be as specific or not as they need to be.
Mocks that match requests will have their counters incremented each time a match occurs.
The RequestMock
object exposes these details via the called
and getCalledCount
methods.
If a no matching mock is found for an HTTP request, and UnmatchedRequestMockException
will be thrown.
|
8.2. MockHttpClientFactory
The MockHttpClientFactory
class may be used in tests and injected into those objects
being tested. This factory implementation exposes the same interface as the other provider
libraries, and adds some additional methods specific to testing. Request mocks are
created via the createMock
method, which returns and tracks an empty mock which may
be configured.
The factory exposes a method allMocksCalled
that will return whether all created mocks
have been called at least once.
8.3. Example
Below is a simple Java object that makes an API request and returns the payload,
which is expected to be a simple String
.
/**
* A test object that makes use of the HTTP requests library to fetch
* a string result from the API located at http://localhost/api.
*/
class TestObject {
private final HttpClientFactory httpClientFactory;
public TestObject(HttpClientFactory httpClientFactory) {
this.httpClientFactory = httpClientFactory;
}
public String makeApiCall() {
return httpClientFactory.createHttpClient.get("http://localhost/api").getEntity(String.class);
}
}
This class may be tested using a framework, such as Junit or Spock. A Spock example follows:
class FunctionalSpec extends Specification {
/**
* Mock HTTP client factory.
*/
MockHttpClientFactory httpClientFactory
/**
* Entity converter manager.
*/
EntityConverterManager entityConverterManager
/**
* Configure the environment before each test.
*/
def setup() {
// Create an entity converter manager with the Groovy JSON converters
entityConverterManager = new EntityConverterManager([
new JsonEntityWriter(),
new JsonEntityReader()
])
// Create the mock HTTP client factory with the entity converter manager above
httpClientFactory = new MockHttpClientFactory(entityConverterManager)
}
def 'A simple mocked GET request returns the expected response entity'() {
setup:
// Create a mock for a specific URL with a response payload
RequestMock mock = httpClientFactory
.createMock()
.setRequestUri('http://localhost/foo/bar')
.setResponseEntity([
foo: 'bar',
baz: 'boz'
])
TestObject object = new TestObject(httpClientFactory)
when:
// Make the API call through the test object
def response = object.makeApiCall()
then:
// The mock should have been called at least once
mock.called()
// Since the above mock is the only one that was created, all mocks
// should have been called from the client factory.
httpClientFactory.allMocksCalled()
// The response should have been returned and converted properly
response == [
foo: 'bar',
baz: 'boz'
]
}
}
9. Changelog
The changelog will be updated with release notes for each release of the library.
- 2.0.7
-
-
Ensure that a Content-Type specified in the request is preferred over the one provided in the HTTP entity. This ensures that any Content-Type provided by a converter will not take precedence over a user-specified one.
-
Add http-requests-mock library to enable easier testing of code using the http-requests libraries.
-
Fix
NullPointerException
when sending a request with a null entity (#14). -
Write a test to ensure that retrieving an entity when the response does not contain an entity does not result in a
NullPointerException
(#13). -
Throw a
URISyntaxException
instead ofNullPointerException
when a URI is not absolute (#12).
-
- 2.0.6
-
-
Make the build compatible with JDK 8.
-
Fix publishing for the jersey version 1 library.
-
- 2.0.5
-
-
Add handling for
null
values in multi-valued maps (which affects request headers, query parameters, and form fields).
-
- 2.0.4
-
-
Update Gradle version and build dependencies.
-
Update library dependencies to latest.
-
Make
EntityConverterManager
immutable. Converters need to be passed into its constructor going forward. -
Introduce entity converter ordering to ensure that certain converters have higherh or lower priority than others.
-
- 2.0.3
-
-
Fix a bug where the reference HTTP client threw an
IOException
when an error HTTP status code was returned with no response entity. -
Fix a bug where calling
HttpResponse.getEntity(Class)
threw aNullPointerException
when no response entity was returned.
-
- 2.0.2
-
-
Add
Closeable
support for filters. Any filter that implementsCloseable
are guaranteed to be closed. -
Fix bug where the
LoggingFilter
threw aNullPointerException
when no request entity is present. -
Do not apply a port if none was specified using the reference HTTP client.
-
- 2.0.1
-
-
Close the underlying Apache HTTP client when the
HttpResponse
is closed.
-
- 2.0.0
-
The version 2.0 upgrade involved refactoring of the components of the library, with the aim of simplifying the codebase and making the project more maintainable. Due to this, the change log for this version is relatively lengthy.
-
The library has been ported from Groovy to Java. This was done to remove a required dependency and widen the audience that might find value in the library. The Groovy functionality has been moved into its own library.
-
Changed the Groovy DSL to use a new function-based delegate, bringing it in line with how Groovy DSLs typically behave. Before, properties of the
HttpRequest
were assigned as properties. With the change, properties are exposed as methods. -
The
EntityConverterManager
is now a required dependency toHttpClientFactory
. The same manager instance is passed along to theHttpClient
andHttpResponse
objects to support object marshalling throughout the stack. This differs from version 1.0 , where the set of entity converters may be modified in both theHttpClientFactory
andHttpClient
separately. -
Filters are no longer registered with the
HttpClientFactory
orHttpClient
, but rather with anHttpRequest
. Since filters act on a request, it brings the registration of filters closer to to the context in which they are used. -
Properties that align to specific headers have been removed from the
HttpRequest
andHttpResponse
. Of course, these headers may be added to the request just as any other header would be. In particular, the following have been removed:-
contentType
-
accept
-
allow
-
-
Introduced the
http-requests-groovy
library, which contains the Groovy DSL extensions toHttpRequest
and Groovy specific entity converters. -
Introduced the
http-requests-spring
library, which adds automatic configuration for the various libraries contained in the http-requests project. -
Introduced the
http-requests-jackson
library, which addsMap
andList
entity converters using Jackson. -
Added
ConvertingHttpEntity
for objects that should be marshalled, so that a content type may also be provided. -
Added a samples project in GitHub with a collection of example applications demonstrating various aspects of the library.
-
Removed the Grails 3 plugin.
-
Add support for HTTP PATCH. Whether the verb works or not is dependent on the underlying HTTP client.
-
Add entity support to HTTP DELETE. Whether this works or not is dependent on the underlying HTTP client and the HTTP server the request is sent to.
-
All artifacts are now deployed to both jcenter and Maven Central.
-
Added query parameter support to the
LoggingFilter
(#10). -
Fixed
HttpRequest
clone issue where paths with special characters would cause a URISyntaxException (#7).
-
- 1.0.2
-
-
Remove default character set from the
HttpRequest
. -
Change default conversion character set from
UTF-8
toISO-8859-1
(per HTTP 1.1 spec).
-
- 1.0.1
-
-
Add missing
@DelegatesTo
annotation to a method taking a closure. -
If a retry filter is registered with the client, the request entity is automatically buffered so that it can be resent with subsequent requests.
-
Add a reference implementation of the client to avoid the need to include other provider implementation modules.
-
Made the Grails 3.x plugin compatible with JDK 1.7.
-
Modify Grails 3.x plugin so that the classpath scanner prefers the non-built-in HttpClientFactory if another provider is found.
-
- 1.0.0
-
-
Initial non-beta release of the library.
-