Hi everyone,
When building RESTful services one very common concern is content negotiation.
This feature allows a client (REST client), to specify its preferred response formats. The idea is “Dear server I’m sending you an application/x-www-form-urlencoded content but I’d rather get application/json results.”
It also allows a server to specify its preferred request formats.
- Client content negotiation
An elegant way to specify preferred format at client side is to send an Accept header value with a comma separated content types. Example :
Accept: text/*, text/html, text/html;level=1, */*
If the server cannot generate any of the formats it should send an HTTP 406 response code.
If it can generate all of them it will chose the most specific and precise value. For further precision you can read this section
A less elegant but quite popular way is to use url extension.
One should know it is not an official supported method but it got popular because of browser usage.
We’ll stick to the first method.
- Server content negotiation
At server side it’s simpler. The server specifies a list of supported media type for a request.
If the Content-Type header sent by client doesn’t match the server’s list the server should send a 415 Unsupported Media Type response.
Also if the server could not marshall the body to the Media Type specified by the client, same punishment.
All this content-negotiation stuff is quite a story. Let’s test those behaviors with jbehave (You might want to read this article about jbehave if you don’t know the tool).
First, the story. The power of such a specification is that anyone can understand what’s the test purpose.
Content Negotiation story Meta: @wip Narrative: In order to provide content negotiation capabilities to my service As a client I want to specify a content type and get that content Scenario: response content negotiation should succeed Given I receive <responseContentType> data When I send a search request Then I should get a successful response Examples: |responseContentType| |application/xml| |application/json| Scenario: response content negotiation should fail Given I receive <responseContentType> data When I send a search request Then I should get an unsuccessful response And the response code should be 406 Examples: |responseContentType| |application/octet-stream| Scenario: request content negotiation should succeed Given I send <requestContentType> data When I send a create request Then I should get a successful response And I should get my newly created resource Examples: |requestContentType| |application/json| Scenario: request content negotiation should fail Given I send <requestContentType> data When I send a create request Then I should get an unsuccessful response And the response code should be 415 Examples: |requestContentType| |application/xml|
Then the implementation
Annotate your implementation if you use spring
@Component public class ContentNegotiation {...}
For each scenario implement its steps
@When("I send a search request") public void sendSearchRequest() { final StringBuilder queryBuilder = new StringBuilder(); final String path = "/advert/find"; final String query = queryBuilder.toString(); final URI uri = URI.create(baseEndPoint + path); final String requestContentType = "application/x-www-form-urlencoded"; final DefaultClientConfig config = new DefaultApacheHttpClient4Config(); config.getClasses().add(JacksonJsonProvider.class); final Client jerseyClient = ApacheHttpClient4.create(config); jerseyClient.addFilter(new LoggingFilter()); final ClientResponse response = jerseyClient.resource(uri).accept(MediaType.valueOf(responseContentType)) .header("Content-Type", requestContentType).post(ClientResponse.class, query); responseStatus = response.getStatus(); }
In the above step, jersey sends a search request to the server. I use its client API to describe the uri I want to reach.
Then I value the Accept header. That header allows the client to specify its preferred response MIME type so that it will be able to unmarshall the response body.
I also value the Content-Type. The server must know what kind of content the client sent. Specially if it supports many ones.
Finally I specify the target format : ClientResponse. This object holds the raw response : body, status and headers.
Depending on what you test you can ask for that object or a domain object. But keep in mind that you’re in REST environment : you should not be afraid of HTTP status codes or headers. These are very natural concepts in HTTP ecosystem. Actually they are fundamental. Embrace them and you’ll start to feel the power of REST. You’ll get surprised of how far they anticipated issues you’re likely to encounter.
I really found that jersey does a terrific job at client side with its DSL. It’s full of little gems like its logging feature added by this piece of code
jerseyClient.addFilter(new LoggingFilter());
It’s very pleasant to use and also very well documented compared to spring-mvc which is not always HTTP compliant and masks everything to the user. It may be a choice for a supposed easiness but the MVC/RestTemplate couple is not even close to catch up with jersey.
Wire story description with its implementation
Extend JunitStories (junit runner).
public class StoriesRunner extends JUnitStories {
Configure your runner to load stories by pattern
@Override protected List<String> storyPaths() { return new StoryFinder().findPaths(CodeLocations.codeLocationFromClass(this.getClass()).getFile(), Arrays.asList("**/*.story"), null); }
Configure your runner to load the implementations via your preferred factory (pico, spring, guice are all supported)
private ApplicationContext createContext() { return new SpringApplicationContextFactory(this.getClass().getClassLoader(), "jbehave-context.xml", "stories-context.xml").createApplicationContext(); }
Launch that runner via maven (all the maven power is available : executions, profiles, dependencyManagement)
<executions> <execution> <id>run-stories-as-embeddables</id> <phase>integration-test</phase> <configuration> <scope>test</scope> <includes> <include>${jbehave.embeddables}</include> </includes> <excludes /> <skip>${jbehave.skip}</skip> <batch>false</batch> <threads>${jbehave.threads}</threads> <storyTimeoutInSecs>${jbehave.storyTimeoutInSecs}</storyTimeoutInSecs> <generateViewAfterStories>true</generateViewAfterStories> <ignoreFailureInStories>${jbehave.ignoreFailureInStories}</ignoreFailureInStories> <ignoreFailureInView>false</ignoreFailureInView> <metaFilters> <metaFilter>${jbehave.meta.filters}</metaFilter> </metaFilters> </configuration> <goals> <goal>run-stories-as-embeddables</goal> <goal>unpack-view-resources</goal> </goals> </execution> </executions>
Run
mvn clean install -Pembedded
and note the failure.
Implement server side
The find method is a bit verbose because of the many parameters by which one can search. Providing a robust and elegant search method is not at all the purpose of the post. My concern is mainly to show you how pleasant jersey is.
@POST @Path(value = "find") @Consumes({ MediaType.APPLICATION_FORM_URLENCODED }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response find(final @FormParam("description") String description, final @FormParam("name") String name, final @FormParam("address.streetAddress") String streetAddress, final @FormParam("address.city") String city, final @FormParam("address.postalCode") String postalCode, final @FormParam("address.countryCode") String countryCode) throws Throwable { final Advert criteria = new Advert(); criteria.setName(name); criteria.setDescription(description); criteria.getAddress().setStreetAddress(streetAddress); criteria.getAddress().setCity(city); criteria.getAddress().setPostalCode(postalCode); criteria.getAddress().setCountryCode(countryCode); final List<Advert> results = facade.findAdvertsByCriteria(criteria); if (CollectionUtils.isEmpty(results)) { LOGGER.info("No results found"); } final GenericEntity<List<Advert>> entity = new GenericEntity<List<Advert>>(results) {}; return Response.ok(entity).build(); }
The above excerpt specifies many things :
– @POST : the supported method. any method other than post will result in a 405 Method Not Allowed response.
– the path which is relative to the class @Path if any specified. Read jersey documentation for more on this feature.
– @Comsumes : the supported request body content type. The server will confront that list with the one specified in the request Content-Type header. Failing to match will result in a 415 Unsupported Media Type response
– @Produces : the supported response body content type.The server will confront that list with the one specified in the request Accept header. Failing to match will result in a 406 Not Acceptable response
– @FormParam : binds a http request form parameter to the method argument. That part is weaker than spring conversion mechanism.
– final GenericEntity<List> entity = new GenericEntity<List>(results) {} : quoting this blog
Long story short: Generics provides compile time type safety and thus eliminating the need for casts. It is achieved through a compile time phenomenon called type erasure. The Generics FAQ explains everything in detail and it is the Java Generics Bible at least for me.
There are cases when we need to return parameterized types from a JAXRS resource method in the Response. Due to type erasure, it requires special handling in Jersey runtime to determine the generic type that is required to select a suitable MessageBodyWriter
– Response.ok(entity).build() : builds a response with HTTP 200 code and a list of results in the format specified by the Accept request header. Isn’t that powerful ? I find it really powerful. XML marshalling is supported out-of-the-box with jaxb2 but one can provide a different marshaller. The very important thing is to use the same marshaller/unmarshaller at client and server side !!! For JSON support I used jackson and got rid of some error by enabling “POJO Mapping feature” (com.sun.jersey.api.json.POJOMappingFeature in web.xml)
Creating a resource is as simple as specifying a path, a http method, a supported media type, calling a business, building the uri where the newly created resource can be found and returning a 201 (created) code. Jersey will add a Location header valued with the uri to the response.
@POST @Consumes({ MediaType.APPLICATION_JSON }) public Response create(final Advert advert) throws Throwable { final Long id = facade.createAdvert(advert); final URI uri = uriInfo.getAbsolutePathBuilder().path(String.valueOf(id)).build(); return Response.created(uri).build(); }
Deleting is even simpler : specify a path, a http method, calling a business, and returning a 204 (no content) code
@DELETE @Path("{id}") public Response delete(@PathParam(value = "id") final Long id) throws Throwable { facade.deleteAdvert(id); return Response.noContent().build(); }
Phewwwwww ! It was quite dense but I’m glad because content negotiation is really a nice feature in HTTP.
I hope I brought a better understanding to those who like me thought of content negotiation as a mystic feature.
It really was a pleasure to use jersey and jbehave. I hope you will adopt those 2 great frameworks if you’re in the situation of writing a REST application. They are really suited for the job.
I’ve always been a great fan of spring-mvc. But from what I tested (3.0.5) I can tell it’s really behind frameworks like jersey or resteasy.
My concern with jersey will now be testing its ability to generate hyper-text driven representations to conform the Ridchardson’s 3rd Level Maturity Model (aka HATEOAS).
My concern with jbehave is concurrency and steps state sharing … Still thinking about a way to isolate scenarii states … Never mind .. Thinking loud.
To do further we can :
– implement stories with groovy
– implement stories with rest-assured (an elegant rest testing framework by the company behind powermock)
Maybe in another post.
The full example code is on github
Just run it with
mvn clean install -Pembedded
Cheers.