Custom content negociation handling extension AND "Accept" header with Jersey - java

I have this resource (simplified):
#Path("/cars{extension:(\\.(xml|json))?}")
public class Cars {
#GET
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response searchCars(#PathParam("extension") String extension) {
System.out.println("extension: " + extension);
//...
return Response.status(200).entity(output).build();
}
}
And I want, for instance (but the business logic can change), to return an xml response if the extension is xml, or Json if the extension is json, whatever the header "Accept" is.
The thing is by default I want to use the Accept header but let's say some dummy guy wants to access my web service with Ajax and doesn't know much about headers, I want to make things easy for him by just adding the appropriate extension.
With that snippet I am able to get the extension (if there is one) but I don't know how to change the strategy accordingly.
Thanks!
EDIT:
So I found something, I can use .type() from Response.ResponseBuilder like:
Response.ResponseBuilder responseBuilder = Response.status(200).entity(output);
if ([some test about extention or header])
responseBuilder.type(MediaType.APPLICATION_XML);
// other tests
I don't know if this is the correct way to do, but that would mean I need to handle it for all the paths...

I would use a ContainerResponseFilter for this, so you don't have todo it for each path.
First check for extension - aka the MediaType the dummy guy loves to get.
Than check if requested MediaType is acceptable for your service. If not, I would say the dummy guy has hard luck ;)
Example code [jersey 2.x]:
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.List;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
#Provider
public class EntityResponseFilter implements ContainerResponseFilter {
#Override
public void filter( ContainerRequestContext reqc , ContainerResponseContext resc ) throws IOException {
MediaType mediaType = this.getMediaTypeFromExtentionOrHeader(); // TODO
List<MediaType> mediaTypes = reqc.getAcceptableMediaTypes();
if( mediaTypes.contains(mediaType) ) {
resc.setEntity( resc.getEntity(), new Annotation[0], mediaType );
}
// ...
}
}
Hope this was helpful somehow :)

Related

Spring Cloud Gateway - modify response body in global Post filter

I have spent the last 2 days trying every possible way of modifying the response body of a request before it hits the client, and nothing seems to work for me. So far I have tried the implementations mentioned here, here, here, here, here and a few others that I can't find right now, but nothing has worked. It doesn't matter if I define the filter as pre, post, global, gateway or route-specific - the actual response modification doesn't seem to work for me.
My situation is the following:
I have a YAML-configured API gateway running and have configured one of its routes to lead to an ADF service in the background. The issue I have with this ADF application is that the response it returns to the client is in the form of an HTML template that is automatically generated by its backend. In this template, some of the URLs are hardcoded and point to the address of the application itself. To justify the use of an API Gateway in this case, I want to replace those ADF URLs with those of the API Gateway.
For simplicity's sake, let's say the IP address of my ADF service is 1.2.3.4:1234, and the IP address of my API Gateway is localhost:8080. When I hit the ADF route in my gateway, the response contains some auto-generated javascript inserts, such as this one:
AdfPage.PAGE.__initializeSessionTimeoutTimer(1800000, 120000, "http://1.2.3.4:1234/entry/dynamic/index.jspx");
As you can see, it contains a hardcoded URL. I want to access the response body and find all those hardcoded URLs and replace them with the gateway URL, so the above example becomes:
AdfPage.PAGE.__initializeSessionTimeoutTimer(1800000, 120000, "http://localhost:8080/entry/dynamic/index.jspx");
To do this, it seems sensible to me to have a global POST filter that kicks in only when the request matches the route for my ADF application, so that's what I've settled on doing.
Here is my post filter so far:
#Bean
public GlobalFilter globalADFUrlReplacementFilter() {
return (exchange, chain) -> chain.filter(exchange).then(Mono.just(exchange)).map(serverWebExchange -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
if (requestIsTowardsADF(request)) {
logger.info("EXECUTING GLOBAL POST FILTER FOR ADF TEMPLATE URL REPLACEMENT");
ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(response) {
#Override
#SuppressWarnings("unchecked")
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
logger.info("OVERRIDING writeWith METHOD TO MODIFY THE BODY");
Flux<? extends DataBuffer> flux = (Flux<? extends DataBuffer>) body;
return super.writeWith(flux.buffer().map(buffer -> {
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = dataBufferFactory.join(buffer);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
DataBufferUtils.release(join);
String bodyStr = new String(content, StandardCharsets.UTF_8);
bodyStr = bodyStr.replace(ADF_URL, API_GATEWAY_URL);
getDelegate().getHeaders().setContentLength(bodyStr.getBytes().length);
return bufferFactory().wrap(bodyStr.getBytes());
}));
}
};
logger.info("ADF URL REPLACEMENT FILTER DONE");
return chain.filter(serverWebExchange.mutate().request(request).response(responseDecorator).build());
}
return serverWebExchange;
})
.then();
}
And the config:
spring:
cloud:
gateway:
routes:
- id: adf-test-2
uri: http://1.2.3.4:1234
predicates:
- Path=/entry/**
You can see that I'm using a org.slf4j.Logger object to log messages in the console. When I run my API Gateway and hit the ADF route, I can see the following:
EXECUTING GLOBAL POST FILTER FOR ADF TEMPLATE URL REPLACEMENT
ADF URL REPLACEMENT FILTER DONE
And when I check the response I got back from the API Gateway, I can see that the response body is still identical and the ADF URLs have not been replaced at all. I tried debugging the application and as soon as it reaches ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(response) { it skips over the entire anonymous class implementation within those curly braces. A testament to that is the absence of the OVERRIDING writeWith METHOD TO MODIFY THE BODY log in the console - it never got executed!
It seems that for some reason the actual body modification doesn't get executed and I can't figure out why. I tried several different implementations of this filter, as mentioned in the above links, and neither of them worked.
Can someone please share with me a working POST filter that modifies the response body, or point out the flaw in my solution?
Thanks a bunch in advance!
Thanks for sharing this sample filter cdan. I provided the most straightforward solution to my issue using it as a template. Here's how it looks:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
#Component
public class TestFilter2 extends AbstractGatewayFilterFactory<TestFilter2.Config> {
public static final String ADF_URL = "1.2.3.4:1234";
public static final String AG_URL = "localhost:8080";
final Logger logger = LoggerFactory.getLogger(TestFilter2.class);
public static class Config {
private String param1;
public Config() {
}
public void setParam1(String param1) {
this.param1 = param1;
}
public String getParam1() {
return param1;
}
}
#Override
public List<String> shortcutFieldOrder() {
return List.of("param1");
}
private final ModifyResponseBodyGatewayFilterFactory modifyResponseBodyFilterFactory;
public TestFilter2() {
super(Config.class);
this.modifyResponseBodyFilterFactory = new ModifyResponseBodyGatewayFilterFactory(new ArrayList<>(), new HashSet<>(), new HashSet<>());
}
#Override
public GatewayFilter apply(Config config) {
final ModifyResponseBodyGatewayFilterFactory.Config modifyResponseBodyFilterFactoryConfig = new ModifyResponseBodyGatewayFilterFactory.Config();
modifyResponseBodyFilterFactoryConfig.setRewriteFunction(String.class, String.class, (exchange, bodyAsString) -> Mono.just(bodyAsString.replace(ADF_URL, AG_URL)));
return modifyResponseBodyFilterFactory.apply(modifyResponseBodyFilterFactoryConfig);
}
}
I have added this filter to my route definition like so:
spring:
cloud:
gateway:
httpclient:
wiretap: true
httpserver:
wiretap: true
routes:
- id: adf-test-2
uri: http://1.2.3.4:1234
predicates:
- Path=/entry/**
filters:
- TestFilter2
I'm simply trying to modify the response body and replace the ADF URL in it with the AG URL, but whenever I try to hit the ADF route I get the below exception:
2022-05-08 17:35:19.492 ERROR 87216 --- [ctor-http-nio-3] a.w.r.e.AbstractErrorWebExceptionHandler : [284b180d-1] 500 Server Error for HTTP GET "/entry/dynamic/index.jspx"
org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'text/html' not supported for bodyType=java.lang.String
at org.springframework.web.reactive.function.BodyExtractors.lambda$readWithMessageReaders$12(BodyExtractors.java:201) ~[spring-webflux-5.3.18.jar:5.3.18]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ? Body from UNKNOWN [DefaultClientResponse]
*__checkpoint ? org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
*__checkpoint ? HTTP GET "/entry/dynamic/index.jspx" [ExceptionHandlingWebHandler]
I searched the web for some time but wasn't able to find any clear answer on why this UnsupportedMediaTypeException: Content type 'text/html' not supported for bodyType=java.lang.String exception gets thrown when I try to work with the bodyAsString field that is supposed to contain the response body as String. Debugging the entire filter didn't work either, as the exception seems to be thrown immediately after I hit the route and I can't even get in the body of that class. Am I missing something obvious?
UPDATE (09.05.2022):
After looking into this further, I refactored the filter structure a bit by removing the unnecessary parameter in the config, and Autowiring the dependency towards ModifyResponseBodyGatewayFilterFactory, and now it seems the filter works properly and does the replacement I needed it to do. I will test it a bit longer to make sure it does indeed work as expected, and if it does, I'll mark this as the solution. Thanks for all of your input cdan!
Here's the entire filter:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
#Component
public class TestFilter2 extends AbstractGatewayFilterFactory<TestFilter2.Config> {
public static final String ADF_URL = "1.2.3.4:1234";
public static final String AG_URL = "localhost:8080";
#Autowired
private final ModifyResponseBodyGatewayFilterFactory modifyResponseBodyFilterFactory;
public static class Config {
public Config() {
}
}
public TestFilter2(ModifyResponseBodyGatewayFilterFactory modifyResponseBodyFilterFactory) {
super(Config.class);
this.modifyResponseBodyFilterFactory = modifyResponseBodyFilterFactory;
}
#Override
public GatewayFilter apply(Config config) {
final ModifyResponseBodyGatewayFilterFactory.Config modifyResponseBodyFilterFactoryConfig = new ModifyResponseBodyGatewayFilterFactory.Config();
modifyResponseBodyFilterFactoryConfig.setRewriteFunction(String.class, String.class, (exchange, bodyAsString) -> Mono.just(bodyAsString.replace(ADF_URL, AG_URL)));
return modifyResponseBodyFilterFactory.apply(modifyResponseBodyFilterFactoryConfig);
}
}
Try with the built-in ModifyResponseBody Filter with Java DSL. If you still need more advanced response processing, your next option is to extend the ModifyResponseBodyGatewayFilterFactory class.
(Update 2022-05-08)
For example, using the Delegation design pattern (wrapping the built-in ModifyResponseBodyFilter in a new custom filter taking one custom parameter):
package test;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.*;
#Component
public class MyFilterFactory extends AbstractGatewayFilterFactory<MyFilterFactory.Config>
{
public static class Config
{
private String param1;
// Add other parameters if necessary
public Config() {}
public void setParam1(String param1) {
this.param1 = param1;
}
public String getParam1() {
return param1;
}
// Add getters and setters for other parameters if any
}
#Override
public List<String> shortcutFieldOrder()
{
return Arrays.asList("param1" /*, other parameters */ );
}
private final ModifyResponseBodyGatewayFilterFactory modifyResponseBodyFilterFactory;
public MyFilterFactory()
{
super(Config.class);
this.modifyResponseBodyFilterFactory = new ModifyResponseBodyGatewayFilterFactory(new ArrayList<>(), new HashSet<>(), new HashSet<>());
}
#Override
public GatewayFilter apply(Config config)
{
final ModifyResponseBodyGatewayFilterFactory.Config modifyResponseBodyFilterFactoryConfig = new ModifyResponseBodyGatewayFilterFactory.Config();
modifyResponseBodyFilterFactoryConfig.setNewContentType(MediaType.TEXT_HTML_VALUE);
modifyResponseBodyFilterFactoryConfig.setRewriteFunction(String.class, String.class, (exchange, bodyAsString) -> {
final String output;
/*
Do whatever transformation of bodyAsString (response body as String) and assign the result to output...
*/
return Mono.just(output);
});
return modifyResponseBodyFilterFactory.apply(modifyResponseBodyFilterFactoryConfig);
}
}

Return multiple responses in Java [duplicate]

I'm writing a REST web app (NetBeans 6.9, JAX-RS, TopLink Essentials) and trying to return JSON and HTTP status code. I have code ready and working that returns JSON when the HTTP GET method is called from the client. Essentially:
#Path("get/id")
#GET
#Produces("application/json")
public M_機械 getMachineToUpdate(#PathParam("id") String id) {
// some code to return JSON ...
return myJson;
}
But I also want to return an HTTP status code (500, 200, 204, etc.) along with the JSON data.
I tried to use HttpServletResponse:
response.sendError("error message", 500);
But this made the browser think it's a "real" 500 so the output web page was a regular HTTP 500 error page.
I want to return an HTTP status code so that my client-side JavaScript can handle some logic depending on it (to e.g. display the error code and message on an HTML page). Is this possible or should HTTP status codes not be used for such thing?
Here's an example:
#GET
#Path("retrieve/{uuid}")
public Response retrieveSomething(#PathParam("uuid") String uuid) {
if(uuid == null || uuid.trim().length() == 0) {
return Response.serverError().entity("UUID cannot be blank").build();
}
Entity entity = service.getById(uuid);
if(entity == null) {
return Response.status(Response.Status.NOT_FOUND).entity("Entity not found for UUID: " + uuid).build();
}
String json = //convert entity to json
return Response.ok(json, MediaType.APPLICATION_JSON).build();
}
Take a look at the Response class.
Note that you should always specify a content type, especially if you are passing multiple content types, but if every message will be represented as JSON, you can just annotate the method with #Produces("application/json")
There are several use cases for setting HTTP status codes in a REST web service, and at least one was not sufficiently documented in the existing answers (i.e. when you are using auto-magical JSON/XML serialization using JAXB, and you want to return an object to be serialized, but also a status code different than the default 200).
So let me try and enumerate the different use cases and the solutions for each one:
1. Error code (500, 404,...)
The most common use case when you want to return a status code different than 200 OK is when an error occurs.
For example:
an entity is requested but it doesn't exist (404)
the request is semantically incorrect (400)
the user is not authorized (401)
there is a problem with the database connection (500)
etc..
a) Throw an exception
In that case, I think that the cleanest way to handle the problem is to throw an exception. This exception will be handled by an ExceptionMapper, that will translate the exception into a response with the appropriate error code.
You can use the default ExceptionMapper that comes pre-configured with Jersey (and I guess it's the same with other implementations) and throw any of the existing sub-classes of javax.ws.rs.WebApplicationException. These are pre-defined exception types that are pre-mapped to different error codes, for example:
BadRequestException (400)
InternalServerErrorException (500)
NotFoundException (404)
Etc. You can find the list here: API
Alternatively, you can define your own custom exceptions and ExceptionMapper classes, and add these mappers to Jersey by the mean of the #Provider annotation (source of this example):
public class MyApplicationException extends Exception implements Serializable
{
private static final long serialVersionUID = 1L;
public MyApplicationException() {
super();
}
public MyApplicationException(String msg) {
super(msg);
}
public MyApplicationException(String msg, Exception e) {
super(msg, e);
}
}
Provider :
#Provider
public class MyApplicationExceptionHandler implements ExceptionMapper<MyApplicationException>
{
#Override
public Response toResponse(MyApplicationException exception)
{
return Response.status(Status.BAD_REQUEST).entity(exception.getMessage()).build();
}
}
Note: you can also write ExceptionMappers for existing exception types that you use.
b) Use the Response builder
Another way to set a status code is to use a Response builder to build a response with the intended code.
In that case, your method's return type must be javax.ws.rs.core.Response. This is described in various other responses such as hisdrewness' accepted answer and looks like this :
#GET
#Path("myresource({id}")
public Response retrieveSomething(#PathParam("id") String id) {
...
Entity entity = service.getById(uuid);
if(entity == null) {
return Response.status(Response.Status.NOT_FOUND).entity("Resource not found for ID: " + uuid).build();
}
...
}
2. Success, but not 200
Another case when you want to set the return status is when the operation was successful, but you want to return a success code different than 200, along with the content that you return in the body.
A frequent use case is when you create a new entity (POST request) and want to return info about this new entity or maybe the entity itself, together with a 201 Created status code.
One approach is to use the response object just like described above and set the body of the request yourself. However, by doing this you loose the ability to use the automatic serialization to XML or JSON provided by JAXB.
This is the original method returning an entity object that will be serialized to JSON by JAXB:
#Path("/")
#POST
#Consumes({ MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_JSON })
public User addUser(User user){
User newuser = ... do something like DB insert ...
return newuser;
}
This will return a JSON representation of the newly created user, but the return status will be 200, not 201.
Now the problem is if I want to use the Response builder to set the return code, I have to return a Response object in my method. How do I still return the User object to be serialized?
a) Set the code on the servlet response
One approach to solve this is to obtain a servlet request object and set the response code manually ourselves, like demonstrated in Garett Wilson's answer :
#Path("/")
#POST
#Consumes({ MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_JSON })
public User addUser(User user, #Context final HttpServletResponse response){
User newUser = ...
//set HTTP code to "201 Created"
response.setStatus(HttpServletResponse.SC_CREATED);
try {
response.flushBuffer();
}catch(Exception e){}
return newUser;
}
The method still returns an entity object and the status code will be 201.
Note that to make it work, I had to flush the response. This is an unpleasant resurgence of low-level Servlet API code in our nice JAX_RS resource, and much worse, it causes the headers to be unmodifiable after this because they were already sent on the wire.
b) Use the response object with the entity
The best solution, in that case, is to use the Response object and set the entity to be serialized on this response object. It would be nice to make the Response object generic to indicate the type of the payload entity in that case, but is not the currently the case.
#Path("/")
#POST
#Consumes({ MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_JSON })
public Response addUser(User user){
User newUser = ...
return Response.created(hateoas.buildLinkUri(newUser, "entity")).entity(restResponse).build();
}
In that case, we use the created method of the Response builder class in order to set the status code to 201. We pass the entity object (user) to the response via the entity() method.
The result is that the HTTP code is 401 as we wanted, and the body of the response is the exact same JSON as we had before when we just returned the User object. It also adds a location header.
The Response class has a number of builder method for different statuses (stati ?) such as :
Response.accepted()
Response.ok()
Response.noContent()
Response.notAcceptable()
NB: the hateoas object is a helper class that I developed to help generate resources URIs. You will need to come up with your own mechanism here ;)
That's about it.
I hope this lengthy response helps somebody :)
The answer by hisdrewness will work, but it modifies the whole approach to letting a provider such as Jackson+JAXB automatically convert your returned object to some output format such as JSON. Inspired by an Apache CXF post (which uses a CXF-specific class) I've found one way to set the response code that should work in any JAX-RS implementation: inject an HttpServletResponse context and manually set the response code. For example, here is how to set the response code to CREATED when appropriate.
#Path("/foos/{fooId}")
#PUT
#Consumes("application/json")
#Produces("application/json")
public Foo setFoo(#PathParam("fooID") final String fooID, final Foo foo, #Context final HttpServletResponse response)
{
//TODO store foo in persistent storage
if(itemDidNotExistBefore) //return 201 only if new object; TODO app-specific logic
{
response.setStatus(Response.Status.CREATED.getStatusCode());
}
return foo; //TODO get latest foo from storage if needed
}
Improvement: After finding another related answer, I learned that one can inject the HttpServletResponse as a member variable, even for singleton service class (at least in RESTEasy)!! This is a much better approach than polluting the API with implementation details. It would look like this:
#Context //injected response proxy supporting multiple threads
private HttpServletResponse response;
#Path("/foos/{fooId}")
#PUT
#Consumes("application/json")
#Produces("application/json")
public Foo setFoo(#PathParam("fooID") final String fooID, final Foo foo)
{
//TODO store foo in persistent storage
if(itemDidNotExistBefore) //return 201 only if new object; TODO app-specific logic
{
response.setStatus(Response.Status.CREATED.getStatusCode());
}
return foo; //TODO get latest foo from storage if needed
}
If you like to keep your resource layer clean of Response objects, then I recommend you use #NameBinding and binding to implementations of ContainerResponseFilter.
Here's the meat of the annotation:
package my.webservice.annotations.status;
import javax.ws.rs.NameBinding;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
public #interface Status {
int CREATED = 201;
int value();
}
Here's the meat of the filter:
package my.webservice.interceptors.status;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
#Provider
public class StatusFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
if (containerResponseContext.getStatus() == 200) {
for (Annotation annotation : containerResponseContext.getEntityAnnotations()) {
if(annotation instanceof Status){
containerResponseContext.setStatus(((Status) annotation).value());
break;
}
}
}
}
}
And then the implementation on your resource simply becomes:
package my.webservice.resources;
import my.webservice.annotations.status.StatusCreated;
import javax.ws.rs.*;
#Path("/my-resource-path")
public class MyResource{
#POST
#Status(Status.CREATED)
public boolean create(){
return true;
}
}
I found it very useful to build also a json message with repeated code, like this:
#POST
#Consumes("application/json")
#Produces("application/json")
public Response authUser(JsonObject authData) {
String email = authData.getString("email");
String password = authData.getString("password");
JSONObject json = new JSONObject();
if (email.equalsIgnoreCase(user.getEmail()) && password.equalsIgnoreCase(user.getPassword())) {
json.put("status", "success");
json.put("code", Response.Status.OK.getStatusCode());
json.put("message", "User " + authData.getString("email") + " authenticated.");
return Response.ok(json.toString()).build();
} else {
json.put("status", "error");
json.put("code", Response.Status.NOT_FOUND.getStatusCode());
json.put("message", "User " + authData.getString("email") + " not found.");
return Response.status(Response.Status.NOT_FOUND).entity(json.toString()).build();
}
}
In case you want to change the status code because of an exception, with JAX-RS 2.0 you can implement an ExceptionMapper like this. This handles this kind of exception for the whole app.
#Provider
public class UnauthorizedExceptionMapper implements ExceptionMapper<EJBAccessException> {
#Override
public Response toResponse(EJBAccessException exception) {
return Response.status(Response.Status.UNAUTHORIZED.getStatusCode()).build();
}
}
If your WS-RS needs raise an error why not just use the WebApplicationException?
#GET
#Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
#Path("{id}")
public MyEntity getFoo(#PathParam("id") long id, #QueryParam("lang")long idLanguage) {
if (idLanguage== 0){
// No URL parameter idLanguage was sent
ResponseBuilder builder = Response.status(Response.Status.BAD_REQUEST);
builder.entity("Missing idLanguage parameter on request");
Response response = builder.build();
throw new WebApplicationException(response);
}
... //other stuff to return my entity
return myEntity;
}
JAX-RS has support for standard/custom HTTP codes. See ResponseBuilder and ResponseStatus, for example:
http://jackson.codehaus.org/javadoc/jax-rs/1.0/javax/ws/rs/core/Response.ResponseBuilder.html#status%28javax.ws.rs.core.Response.Status%29
Keep in mind that JSON information is more about the data associated with the resource/application. The HTTP codes are more about the status of the CRUD operation being requested. (at least that is how it's supposed to be in REST-ful systems)
Please look at the example here, it best illustrates the problem and how it is solved in the latest (2.3.1) version of Jersey.
https://jersey.java.net/documentation/latest/representations.html#d0e3586
It basically involves defining a custom Exception and keeping the return type as the entity. When there is an error, the exception is thrown, otherwise, you return the POJO.
I'm not using JAX-RS, but I've got a similar scenario where I use:
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
Also, notice that by default Jersey will override the response body in case of an http code 400 or more.
In order to get your specified entity as the response body, try to add the following init-param to your Jersey in your web.xml configuration file :
<init-param>
<!-- used to overwrite default 4xx state pages -->
<param-name>jersey.config.server.response.setStatusOverSendError</param-name>
<param-value>true</param-value>
</init-param>
The following code worked for me. Injecting the messageContext via annotated setter and setting the status code in my "add" method.
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import org.apache.cxf.jaxrs.ext.MessageContext;
public class FlightReservationService {
MessageContext messageContext;
private final Map<Long, FlightReservation> flightReservations = new HashMap<>();
#Context
public void setMessageContext(MessageContext messageContext) {
this.messageContext = messageContext;
}
#Override
public Collection<FlightReservation> list() {
return flightReservations.values();
}
#Path("/{id}")
#Produces("application/json")
#GET
public FlightReservation get(Long id) {
return flightReservations.get(id);
}
#Path("/")
#Consumes("application/json")
#Produces("application/json")
#POST
public void add(FlightReservation booking) {
messageContext.getHttpServletResponse().setStatus(Response.Status.CREATED.getStatusCode());
flightReservations.put(booking.getId(), booking);
}
#Path("/")
#Consumes("application/json")
#PUT
public void update(FlightReservation booking) {
flightReservations.remove(booking.getId());
flightReservations.put(booking.getId(), booking);
}
#Path("/{id}")
#DELETE
public void remove(Long id) {
flightReservations.remove(id);
}
}
Expanding on the answer of Nthalk with Microprofile OpenAPI you can align the return code with your documentation using #APIResponse annotation.
This allows tagging a JAX-RS method like
#GET
#APIResponse(responseCode = "204")
public Resource getResource(ResourceRequest request)
You can parse this standardized annotation with a ContainerResponseFilter
#Provider
public class StatusFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
if (responseContext.getStatus() == 200) {
for (final var annotation : responseContext.getEntityAnnotations()) {
if (annotation instanceof APIResponse response) {
final var rawCode = response.responseCode();
final var statusCode = Integer.parseInt(rawCode);
responseContext.setStatus(statusCode);
}
}
}
}
}
A caveat occurs when you put multiple annotations on your method like
#APIResponse(responseCode = "201", description = "first use case")
#APIResponse(responseCode = "204", description = "because you can")
public Resource getResource(ResourceRequest request)
I'm using jersey 2.0 with message body readers and writers. I had my method return type as a specific entity which was also used in the implementation of the message body writer and i was returning the same pojo, a SkuListDTO.
#GET
#Consumes({"application/xml", "application/json"})
#Produces({"application/xml", "application/json"})
#Path("/skuResync")
public SkuResultListDTO getSkuData()
....
return SkuResultListDTO;
all i changed was this, I left the writer implementation alone and it still worked.
public Response getSkuData()
...
return Response.status(Response.Status.FORBIDDEN).entity(dfCoreResultListDTO).build();

Handle Embedded Tomcat Exception in Spring Boot

We have an issue where embedded Tomcat is throwing IllegalArgumentException from the LegacyCookieProcessor. It throws a 500 HTTP response code.
We need to handle the exception and do something with it (specifically, send it as a 400 instead).
The typical #ExceptionHandler(IllegalArgumentException.class) doesn't seem to get triggered and Google only seems to give results for dealing with Spring Boot specific exceptions.
Example:
Here is an example to reproduce the behavior. You can execute the example by downloading the initial project including spring-web (https://start.spring.io/) in version 2.1.5.RELEASE. Then add the following two classes to your project.
DemoControllerAdvice.java
package com.example.demo;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
#RestControllerAdvice
public class DemoControllerAdvice {
#ExceptionHandler(IllegalArgumentException.class)
#ResponseStatus(HttpStatus.FORBIDDEN)
public Map<String, String> forbiddenHandler() {
Map<String, String> map = new HashMap<>();
map.put("error", "An error occurred.");
map.put("status", HttpStatus.FORBIDDEN.value() + " " + HttpStatus.FORBIDDEN.name());
return map;
}
}
DemoRestController.java
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class DemoRestController {
#GetMapping(value = "/working")
public void working() {
throw new java.lang.IllegalArgumentException();
}
#GetMapping(value = "/not-working")
public String notWorking(#RequestParam String demo) {
return "You need to pass e.g. the character ^ as a request param to test this.";
}
}
Then, start the server and request the following URLs in the browser:
http://localhost:8080/working An IllegalArgumentException is thrown manually in the controller. It is then caught by the ControllerAdvice and will therefore produce a JSON string containing the information defined in the DemoControllerAdvice
http://localhost:8080/not-working?demo=test^123 An IllegalArgumentException is thrown by the Tomcat, because the request param cannot be parsed (because of the invalid character ^). The exception however is not caught by the ControllerAdvice. It shows the default HTML page provided by Tomcat. It also provides a different error code than defined in the DemoControllerAdvice.
In the logs the following message is shown:
o.apache.coyote.http11.Http11Processor : Error parsing HTTP request header
Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level.
java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986
at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:467) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
This is a feature of Tomcat itself as mentioned in this answer.
However, you can do something like this by allowing the special characters that you are expecting as part of your request and handle them yourself.
First, you need to allow the special characters that you would need to handle by setting up the relaxedQueryChars like this.
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
#Component
public class TomcatCustomizer implements
WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers((connector) -> {
connector.setAttribute("relaxedQueryChars", "^");
});
}
}
and later handle the special characters in each of your requests or create an interceptor and handle it in a common place.
To handle it in the request individually you can do something like this.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class DemoRestController {
#GetMapping(value = "/working")
public void working() {
throw new java.lang.IllegalArgumentException();
}
#GetMapping(value = "/not-working")
public String notWorking(#RequestParam String demo) {
if (demo.contains("^")) {
throw new java.lang.IllegalArgumentException("^");
}
return "You need to pass e.g. the character ^ as a request param to test this.";
}
}
You might also want to refer this answer to decide if you really need this fix.
Try to catch the IllegalArgumentException in your filter, then call HttpServletResponse.sendError(int sc, String msg);. This may catch the IllegalArgumentExceptions that do not come from Tomcat though. But I suppose you already handle them properly.

"Publish" object through http

I have an object with state and non-serializable fields, like threads, and I would to invoke functions on it like one would do it through RMI but through http. I don't want to scale and I am in an isolated network. I am currently using Jetty, like this:
public class ObjectHandler extends AbstractHandler {
MyStatefulObject obj;
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
String action = request.getParameter("action");
switch (action) {
case "method1":
obj.method1(request.getParameter("some-parameter"));
break;
case "method2":
obj.method2(request.getParameter("some-other-parameter"));
break;
}
baseRequest.setHandled(true);
}
}
which is kind of weird. I would like to use something like Servlets, and use the different methods to tell apart the action to do, or use JAX-RS to use the calling url to tell apart the action to do. But both of those methods are stateless, that is, I cannot pass an object to a servlet, and, at least with jersey, the construction was made with the class, not with and instance of it, so I could not control the construction of the MyStatefulObject object. So, is there a library for, let's say, annotate an object and pass it to a server instance and start listening to requests? I would like to make something like this:
#Path("/")
public class MyStatefulObject {
MyStatefulObject(Parameter param1, Param) {
//some building stuff
}
#POST
#Path("/path1")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8")
void method1(Parameter param) {}
#POST
#Path("/path2")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8")
Object method2(Parameter param) {
return new Object();
}
}
while outside I would have:
Server server = new Server(8081);
server.setHandler(new MyStatefulObject(param));
server.start();
server.join();
Is there a library that makes me able to do that? as I say before, I don't want to scale (this is running in a small network) and there is no security concerns. I just want to "publish" an object.
In the end, Jersey does allow stateful objects to be published, using the ResourceConfig class with an object (as opposed with a Class, which is the use I found more frequently). Funny cause in this question they want to do the exact opposite. We simply register an object in the ResourceConfig.
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.net.URI;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.glassfish.jersey.jetty.JettyHttpContainerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import javax.inject.Singleton;
#Path("calculator")
public class Calculator {
int i = -1;
public Calculator(int i) {
this.i = i;
}
#GET
#Path("increment")
#Produces(MediaType.APPLICATION_JSON)
public String increment() {
i = i + 1;
return "" + i;
}
public static void main(String[] args) throws Exception {
ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.register(new Calculator(10));
Server server = JettyHttpContainerFactory.createServer(new URI("http://localhost:8080"), resourceConfig);
server.start();
}
}

Invoke RESTful webservice with parameter

I have a simple RESTful web service that print "Hello World !"
I'm using NetBeans and the code looks like:
package resource;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.Consumes;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.GET;
import javax.ws.rs.Produces;
#Path("simple")
public class SimpleResource {
#Context
private UriInfo context;
/** Creates a new instance of SimpleResource */
public SimpleResource() {
}
#GET
#Produces("application/xml")
public String getXml() {
//TODO return proper representation object
return "<greeting>Hello World !</greeting>";
}
#PUT
#Consumes("application/xml")
public void putXml(String content) {
}
}
I call this web service from this URL : http://localhost:8080/WebService/resources/simple.
Now, I want to send a parameter to this web service, then print this parameter after the "Hello world" message.
How can we do that?
Thanks!
The two main ways of handling a parameter in REST are via parsing the path and via extracting the query part.
Path parameters
These handle this case — /foo/{fooID} — where {fooID} is a template that will be replaced by the parameter you want:
#GET
#Produces("text/plain")
#Path("/foo/{fooID}")
public String getFoo(#PathParam("fooID") String id) {
// ...
}
These are great for the case where you can consider the parameter to be describing a resource.
Query parameters
These handle this case — /?foo=ID — just like you'd get from doing traditional form processing:
#GET
#Produces("text/plain")
#Path("/")
public String getFoo(#QueryParam("foo") String id) {
// ...
}
These are great for the case where you consider the parameter to be describing an adjunct to the resource, and not the resource itself. The #FormParam annotation is extremely similar, except it is for handling a POSTed form instead of GET-style parameters
Other types of parameters
There are other types of parameter handling supported by the JAX-RS spec (matrix parameters, header parameters, cookie parameters) which all work in about the same way to the programmer, but are rarer or more specialized in use. A reasonable place to start exploring the details is the JAX-RS javadoc itself, as that has useful links.
The sample code for a web service which accepts parameters in URl will look like this:
#GET
#Path("/search")
public String getUserDetailsFromAddress(
#QueryParam("name") String name) {
return "Hello"+name;
}
and the URL will be like this:
http://localhost:8080/searchapp/mysearch/search?name=Tom
Try adding a Path annotation like this:
#javax.ws.rs.Path(“/bookstore/books/{bookId}”)

Categories