Best practise when making post request in JAX-RS - java

I have seen some different conventions regarding the response status code, when making a post request using JAX-RS.
I have seen this:
Response.ok(//content or object).build();
My initial thought would be that this is wrong, since status code 200, just means ok, which is a broad term, but since we have 'Posted', I think 201 would be better since it also returns the URI of the element inside the header
Response.created(//content or object).build();
here is want i want to achieve:
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response createUser(String content) throws CityNotFoundException
{
ReceivedCreateUser receivedUser = gson.fromJson(content, ReceivedCreateUser.class);
User createdUser = userFacade.createUser(receivedUser.name,
receivedUser.email,
receivedUser.password,
receivedUser.city,
receivedUser.gender,
receivedUser.dateOfBirth);
if(createdUser == null){
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return Response.ok(gson.toJson(UserDTO.basic(createdUser))).build();
}
i want to add the entity to the response, but created only accepts the uri not entities.

Whether or not to return a 201 on a POST request depends greatly on the functionality behind it. If your end-point creates a new entity in the system then 201 would be the way to go, but POST is also often used to perform search request with large option sets. In this case a 200 or 204 would be desirable in case of results or no results.
But to return an entity with a POST and response 201 you could do something similar to this:
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response createUser(String content) throws CityNotFoundException
{
ReceivedCreateUser receivedUser = gson.fromJson(content, ReceivedCreateUser.class);
User createdUser = userFacade.createUser(receivedUser.name,
receivedUser.email,
receivedUser.password,
receivedUser.city,
receivedUser.gender,
receivedUser.dateOfBirth);
if(createdUser == null){
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return Response
.created(new Uri("http://example.org"))
.entity(gson.toJson(UserDTO.basic(createdUser)))
.build();
}

Response.ok() is not Ok all the time, it might show that the request was done but actually no resource was created. You might want to use Response.created or Response.accepted. As in this link , you can see that it all depends on the business logic of your backend.

Response type is not the best return to clients and automatic documentation like Swagger, because it doesn't have too much information. I prefer particularly to return the entity directly how to bellow:
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public UserDTO createUser(String content) throws CityNotFoundException
{
ReceivedCreateUser receivedUser = gson.fromJson(content, ReceivedCreateUser.class);
User createdUser = userFacade.createUser(receivedUser.name,
receivedUser.email,
receivedUser.password,
receivedUser.city,
receivedUser.gender,
receivedUser.dateOfBirth);
if(createdUser == null){
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return UserDTO.basic(createdUser);
}
This will return a Status Code = 200, but have many ways to change to 201. My favorite is overriding the javax.ws.rs.container.ContainerResponseFilter.filter(...) as bellow:
import static org.springframework.http.HttpMethod.POST;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK;
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
#Provider
public class LocalContainerResponseFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
if (POST.name().equals(requestContext.getMethod()) && OK.value() == responseContext.getStatus()) {
responseContext.setStatus(CREATED.value());
}
}
}
This will change JAX-RS behavior returning 201 for all POST ok.
More 2 tips:
you can change a parameter (String content) to (ReceivedCreateUser content); and
you can put the code "throw new WebApplicationException" inside userFacade.createUser(...)

Related

Jetty and Dropwizard: how to return always 200 instead of 404, 500, etc

I'm using Dropwizard 0.8.0 that comes with Jetty-Jersey-Jackson stack.
For security reasons, I want to add a filter that makes every request that passes through a particular route defined with Jersey returns always 200, even in case of error (4xx, 5xx, etc.).
Is this possibile with Jetty/Servlet filters? Can I intercept the request after it passed through Jersey resource (the controller), but before it is returned to client, in order to modify the http status code?
UPDATE:
I'm trying to do this with a ServletFilter, but it seems that the response is sent to client before my code is executed.
I've written the filter this way:
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
if (response instanceof HttpServletResponse) {
/* No pre-processing */
chain.doFilter(request, response);
/* Post-processing: */
HttpServletResponse modifiedResponse = (HttpServletResponse) response;
if (modifiedResponse.getStatus() != 200) {
modifiedResponse.setStatus(200);
}
}
}
With this, registered in Dropwizard with:
environment.servlets().addFilter("MyCustomFilter", new MyCustomFilter())
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/mypath/*");
The filter is executed, and in the access.log I see all the requests with status code 200; but the client always get the "real" status code (ex, a 404, or a "Method not allowed").
It seems that the response is sent to client before the last part of filter is executed. Moreover, I can't modify the response body. I tried also with a HttpServletResponseWrapper, but with no luck.
You could create your custom javax.ws.rs.ext.ExceptionMapper<RuntimeException>. In that case, every exception that you get from your server, you can resolve it to 200.
Check this guy out. It should be easy to integrate with what you need.
Instead of what the tutorial shows you, like this example:
if (webAppException.getResponse().getStatus() == 401) {
return Response
.status(Response.Status.UNAUTHORIZED)
.entity(new PublicFreemarkerView("error/401.ftl"))
.build();
}
You code will be
if (webAppException.getResponse().getStatus() == 401) {
return Response
.status(Response.Status.OK)
.build();
}
I'm updating the question with my solution that finally works: I've used a Jersey filter instead of a Jetty http filtering, in order to manage directly the Response object from Jersey.
Obviously, this works only if you're using Jetty+Jersey, not with Jetty stand-alone.
Here's the filter I'm using:
/**
* When active, this filter transforms all responses for specified basePath to 200, even in case of error.
*/
#Provider
public class DiscardErrors implements ContainerResponseFilter
{
private String basePath;
public DiscardErrors(String basePath)
{
this.basePath = basePath;
}
#Override
public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException
{
if containerRequestContext.getUriInfo().getPath().startsWith(this.basePath) {
if (containerResponseContext.getStatus() != 200) { // Check if response code is different from 200
containerResponseContext.setStatus(200); // Force 200 status code
containerResponseContext.setEntity(""); // Empty body
}
}
}
}
And register it in Dropwizard:
environment.jersey().register(new DiscardErrors("/"));

Java: Get JSON from POST with HttpServletRequest?

I'm trying to get the body of a POST request by using HttpServletRequest or UriInfo. Given a class like this one (reduced for this question):
#Path("/nodes")
#Produces({ MediaType.APPLICATION_JSON })
#Consumes({ MediaType.APPLICATION_JSON })
public class Nodes {
public NodeResource() {
//initial stuff goes here
}
/**
* gives an empty response. For testing only!
*/
#POST
#Consumes("application/json")
#Path("{id}/test-db-requests")
public Response giveNodes(#PathParam("id") final String id, #Context HttpServletRequest request, #Context UriInfo uriInfo){
//String readReq = request.getQueryString(); //would work for GET
MultivaluedMap<String,String> readParams = uriInfo.getQueryParameters();
LOG.debug("what is readParams?", readParams); //goes, but shows nothing
if (readParams != null) {
LOG.debug("null or not?"); //goes, too
for (Map.Entry<String,List<String>> entry: readParams.entrySet()) {
List<String> values = entry.getValue();
LOG.debug("params POST key: {}", entry.getKey()); // goes not
for (String val: values) {
LOG.debug("params POST values: {}", val);
}
LOG.debug("params POST next entry:::");
}
}
List<?> results = null; //currentDBRequest(id);
List<?> content = new ArrayList<>();
if (results != null) {
content = results;
}
return Response.ok(content).build();
}
}
Instead of using
MultivaluedMap<String,String> readParams = uriInfo.getQueryParameters();
//not possible at all - for GET only!? See first comment.
I also tried to use
Map<String,String[]> readParams = request.getParameterMap();
//what is about this one?
with different following code of course. But that did not work, either.
So when I fire a simple request like /nodes/546c9abc975a54c398167306/test-db-requests with the following body
{
"hi":"hello",
"green":"tree"
}
(using an JSON Array does not change anything)
and stuff in the HEADER (some informations):
Content-Type: application/json; charset=UTF-8
Accept: application/json, text/plain, */*
Connection: keep-alive
the result is disappointing, readParams is not null, but does not contain any data. Before I start to play with getReader I wanted to ask: what am I doing wrong? Is there a problem in my POST, in my Java code or in the used HttpServletRequest method(s)? Thanks!
Related questions (where I found some possible solutions), among others:
How can I grab all query parameters in Jersey JaxRS?
How to access parameters in a RESTful POST method
Alright, Jackson would actually do this for me. Just use the argument of the method, which you want to use. (See examples below.)
But you would probably not use a POST in combination with an id parameter. POST is usually used for saving fresh resources, which do not have an id (in the DB, a primary key). Moreover the path /api/{resource_name}/{id}/{some_view} would be useful for GET. Just api/{resource_name}/{id} for a GET (single entry) or a PUT (update an existing entry).
Assume you are in a resource for Pet.class. You want to catch the POSTs for this class in order to do something special with them, based on the view test-db-requests. Then do:
#POST
#Consumes("application/json")
#Path("{id}/test-db-requests")
public Response giveNodes(final String pet, #PathParam("id") final String id){
//do stuff for POST with a strigified JSON here
}
or
#POST
#Path("{id}/test-db-requests")
public Response giveNodes(final Pet pet, #PathParam("id") final String id){
//do stuff for POST with an instance of pet here (useful for non
//polymorphic resources
}

Response authorization with Jersey

I have a Jersey 2 application containing resources that consume and produce json. My requirement is to add a signature to an Authorization response header generated from a combination of various piece of response data (similar to the Amazon Webservices request signature). One of these pieces of data is the response body but I cant see that there are any filter or interception points that will allow me access to the json content. I imagine this is mainly because the response outputstream is for writing not reading.
Any ideas as to how I can read the response body - or alternative approaches ?
Thank you.
My understanding is that when your application is responding to a request, you want to modify the Authorization header by adding a signature to it's value.
If that's the case, you want to implement a ContainerResponseFilter:
public class MyContainerResponseFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
// You can get the body of the response from the ContainerResponseContext
Object entity = containerResponseContext.getEntity();
// You'll need to know what kind of Object the entity is in order to do something useful though
// You can get some data using these functions
Class<?> entityClass = containerResponseContext.getEntityClass();
Type entityType = containerResponseContext.getEntityType();
// And/or by looking at the ContainerRequestContext and knowing what the response entity will be
String method = containerRequestContext.getMethod();
UriInfo uriInfo = containerRequestContext.getUriInfo();
// Then you can modify your Authorization header in some way
String authorizationHeaderValue = containerResponseContext.getHeaderString(HttpHeaders.AUTHORIZATION);
authorizationHeaderValue = authorizationHeaderValue + " a signature you calculated";
containerResponseContext.getHeaders().putSingle(HttpHeaders.AUTHORIZATION, authorizationHeaderValue);
}
}
Be warned that the filter function will be called for all requests to your application, even when Jersey couldn't find a matching resource for the request path, so you may have to do some extra checking.
You can implement ContainerRequestFilter in order to access the content, and once you are finished with your interception logic, forward it to the request. E.g.
import java.io.*;
import com.sun.jersey.api.container.ContainerException;
import com.sun.jersey.core.util.ReaderWriter;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
public class ExampleFilter implements ContainerRequestFilter {
#Override
public ContainerRequest filter(ContainerRequest req) {
try(InputStream in = req.getEntityInputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
if (in.available() > 0) {
StringBuilder content = new StringBuilder();
ReaderWriter.writeTo(in, out);
byte[] entity = out.toByteArray();
if (entity.length > 0) {
content.append(new String(entity)).append("\n");
System.out.println(content);
}
req.setEntityInputStream(new ByteArrayInputStream(entity));
}
} catch (IOException ex) {
//handle exception
}
return req;
}
}

How to add custom response and abort request in jersey 1.11 filters

I am trying to implement user authentication for rest calls in jersey 1.11 filters.
This is what i have tried
package com.ilrn.session.webservices.rest.filter;
import com.ilrn.entity.User;;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
public class CustomFilter implements ContainerRequestFilter{
#Override
public ContainerRequest filter(ContainerRequest request) {
User user = Helper.getCurrentUser();
if(user == null){
//Need to add custom response and abort request
}
return request;
}
}
Does anyone know any method or something to achieve the same?
In case of error, if you want to send a custom response then you need to throw a WebApplicationException. Create a Response object and send it back using the following exception constructor:
WebApplicationException(Response response)
Construct a new instance using the supplied response
Try this:
#Override
public ContainerRequest filter(ContainerRequest request) {
User user = Helper.getCurrentUser();
if(user == null){
ResponseBuilder builder = null;
String response = "Custom message";
builder = Response.status(Response.Status.UNAUTHORIZED).entity(response);
throw new WebApplicationException(builder.build());
}
return request;
}

Redirecting to a page using restful methods?

I've created a page which asks user to fill some form fields and when he submits, the form is sent to a Restful method which you can see below:
#POST
#Path("addUser")
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void addUser(#FormParam("username") String username,
#FormParam("password") String password,
#FormParam("id") String id,
#FormParam("group_name") String groupName,
#FormParam("authority_name") String authorityName,
#FormParam("authority_id") String authorityId
)
{
//Something will be done here
}
How can I redirect the user at the end of this function to (let's say) index.jsp?
change your code like this, the addUser() should return a Response Object
public Response addUser(#FormParam("username") String username,
#FormParam("password") String password,
#FormParam("id") String id,
#FormParam("group_name") String groupName,
#FormParam("authority_name") String authorityName,
#FormParam("authority_id") String authorityId
)
{
//Something will be done here
java.net.URI location = new java.net.URI("../index.jsp?msg=A_User_Added");
return Response.temporaryRedirect(location).build()
}
Create a URI using javax.ws.rs.core.UriBuilder that maps the parameters and other data you want to preserve. Then use Response.temporaryRedirect to return a redirect to the client and pass it the URI you’ve built.
Finally I come to this conclusion that there are no other way than what I did:
So here is my solution:
try {
java.net.URI location = new java.net.URI("../index.jsp?msg=A_User_Added");
throw new WebApplicationException(Response.temporaryRedirect(location).build());
} catch (URISyntaxException e) {
e.printStackTrace();
}
By adding this block to my code, I got what I needed. Hope it helps you as well.
See below the usage of redirecting in web services:
public class LoginWebService {
#POST
#Path("/check")
public Response checkDetails(#FormParam("name") String name,#FormParam("pass") String pass ) throws URISyntaxException {
URI uri = new URI("/login/success");
URI uri2= new URI("http://localhost:9090/NewWebServiceproject/new/login/failure");
if(name.equals("admin") && pass.equals("pass"))
//#Path("http://localhost:8010/NewWebServiceproject/new/login/success");
{
return Response.temporaryRedirect(uri).build();
//Response.seeOther(uri);
//return Response.status(200).entity("user successfully login").build();
}
else
{
return Response.temporaryRedirect(uri2).build();
//Response.seeOther(uri2);
//return Response.status(200).entity("user logon failed").build();
}
}
#POST
#Path("/success")
public Response successpage()
{
return Response.status(200).entity("user successfully login").build();
}
#POST
#Path("/failure")
public Response failurepage()
{
return Response.status(200).entity("user logon failed").build();
}
}
It is not good idea to use the "WebApplicationException" in order to redirect the request. in Jersey (2.4.1) you should be able to redirect the request via the normal servlet way, (request.getServletContext().getRequestDispatcher().forward() or just response.sendRedirect())
The following is how Jersey process the request
org.glassfish.jersey.servlet.ServletContainer.service(HttpServletRequest request, HttpServletResponse response)
requestScope.runInScope
final ContainerResponse response = endpoint.apply(data)
methodHandler.invoke(resource, method, args);
Responder.process(ContainerResponse);
That methodHandler is your REST service class, method is the function in that service class.
The step to redirect page become straitforward
Get the (request, response) through Jersey injection (#Context HttpServletRequest request, #Context HttpServletResponse response) in class field or function parameter
Call request.getServletContext().getRequestDispatcher() to get the dispatcher for "forward"
or use Response.sendRedirect(url)
Once you application is returned (just null), Jersey will try to process the result in the "Responder.process(ContainerResponse)". In this step, it will use response to set status (204 no contents for your null return).
So the key point here is you must finalize/close response object before return from your service function. Otherwise, Jersey may overwrite your output.
Small tips on why "WebApplicationException" can overwrite Jersey repsponse. It is because org.glassfish.jersey.server.ServerRuntime.mapException() will use the "webApplicationException.getResponse()" as the return response result.

Categories