Bad Request Error (400) or Not Found Error (404) during extracting request parameters in jax-rs - jax-rs

In docs.oracle ( https://docs.oracle.com/javaee/7/tutorial/jaxrs002.htm ) about Extracting Request Parameters we have:
If the URI path template variable cannot be cast to the specified type, the JAX-RS runtime returns an HTTP 400 (“Bad Request”) error to the client.
If the #PathParam annotation cannot be cast to the specified type, the JAX-RS runtime returns an HTTP 404 (“Not Found”) error to the client.
Could someone explain the difference, maybe give an example?

I don't know why, but if you define method with path param and it fails on cast string to custom type, jax-rs (in my case jersey servlet) returns 404 error, and it's very strange and unexpected.
package com.example.jaxrs.api;
...
#GET
#Path("/{id}/info")
public Response info(#PathParam UUID id) { ... }
curl -X GET http://127.0.0.1:8080/api/123/info --> HTTP ERROR 404 Not Found
This behavior can be changed. Just create simple ExceptionMapper, catch the ParamException, and return correct "400 Bad Request" response.
package com.example.jaxrs.errors;
#Provider
public class PathParamExceptionMapper implements ExceptionMapper<ParamException> {
#Override
public Response toResponse(ParamException exception) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
}
Do not forgot annotation #Provider and add the package with error handler to servlet init param.
<servlet>
<servlet-name>jersey-serlvet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.example.jaxrs.api;com.example.jaxrs.errors</param-value>
</init-param>
</servlet>

Related

How to customise the response body when "401 Unauthorized" occurred?

Instead of an empty response body when "401 Unauthorized" occurs, I want to add the other information into the response (e.g timestamp, a message), how can I override it?
Expectation:
HTTP status: 401 Unauthorized
Response body:
{
"timestamp": 1234567890,
"message": "Your access token was expired"
}
I'm using the Helidon MP v2.5.5
To customize the response body when a "401 Unauthorized" error occurs in Helidon, you can handle the error in the error handling mechanism provided by Helidon. You can write your custom logic inside the error handling mechanism to return the desired response.
Here is an example code to demonstrate the same:
server.addErrorHandler(401, ex ->
ServerResponse
.status(401)
.header("Content-Type", "text/plain")
.send("Unauthorized Error Occurred!")
);
This code will return a response with a 401 status code and the response body "Unauthorized Error Occurred!" whenever a 401 error is encountered in the server.
Take a look at ExceptionMapper class in javax/jakarta. You can create a Provider that implements the ExceptionMapper using BadRequestException. Then override the toResponse method of ExceptionMapper. That way you can customize the response.
Some code doing this could look like this:
#Provider
#NoArgsConstructor
public class <YourExceptionHandler> implements ExceptionMapper<BadRequestException> {
#Override
public Response toResponse(BadRequestException exception){
<custom code>
return Response.status(<status>).entity(<custom code>).build()
}
}
In my case, for the custom code part I instance a class that handles the error's status, error message, etc...

Is it possible to possible to achieve with Jersey a polymorphic deserialisation of json payloads on POST resources

I'd like a endpoint to accept on a single verb and path a json payload that varies by a tiny bit and that can be mapped to different objects. The variation is usually on the field value like the grant_type in the OAuth world.
I scrapped stackoverflow and google on this, I found this among other
JAX-RS polymorphic POST request: how should I write my JSON?
Polymorphism in JSON, Jersey and Jackson
But those question don't seem to be relevant to me, jackson deserialisation alone works fine for this payloads, but Jersey always refuses to init the servlet with an error that let me thinks there's no way around it.
#Path("parent")
interface Resource {
#POST
#Path("test")
String test(Payload1 payload1);
#POST
#Path("test")
String test(Payload2 payload1);
#Data
#JsonTypeName("payload1")
class Payload1 extends BasePayload {
String a;
}
#Data
#JsonTypeName("payload2")
class Payload2 extends BasePayload {
String b;
}
// #JsonTypeInfo(use= Id.MINIMAL_CLASS, include=As.PROPERTY, property="#class")
#JsonTypeInfo(use= Id.NAME, include=As.PROPERTY, property="#payload")
#JsonSubTypes({
#JsonSubTypes.Type(value = Payload1.class),
#JsonSubTypes.Type(value = Payload2.class)
})
class BasePayload {
}
However I get this message in an exception upon servlet initialisation. (edited message for clarity)
</pre><p><b>root cause</b></p><pre>
org.glassfish.jersey.server.model.ModelValidationException:
Validation of the application resource model has failed during application initialization.
[[FATAL] A resource model has
ambiguous (sub-)resource method for HTTP method POST and input mime-types as defined by
"#Consumes" and "#Produces" annotations
at Java methods
public java.lang.String xxx.Service.test(xxx.Resource$Payload1)
and
public java.lang.String xxx.Service.test(xxx.Resource$Payload2)
at matching regular expression /test.
These two methods produces and consumes exactly the same mime-types and
therefore their invocation as a resource methods will always fail.;
source='org.glassfish.jersey.server.model.RuntimeResource#59b668bf']
Note however that having a endpoint with the parent class of the payload works, but you have to handle the dispatch yourself.
#POST
#Path("test")
String test(BasePayload payload);
I'm using Spring-Boot 1.4 / Jersey 2.23.2 / Jackson 2.8.5
The JAX-RS runtime uses the following for matching requests to resource methods:
URI: Defined in the #Path annotation.
Request method: Defined by a resource method designator such as #GET, #POST, etc).
Media type: Defined in Accept and Content-Type headers, that are matched with the values defined in the #Produces and #Consumes annotations, respectively.
The content of the payload is not taken into account, so it makes your first method definition ambiguous.
As you already figured out, the following approach is the way to go:
#POST
#Path("test")
String test(BasePayload payload);

Microsoft OData Client 6.x with OData service 4.x handle NULL return (404 Not Found) on the client

I can't seem to figure out how to handle NULL (404 Not Found) on the client when calling an OData function for a given Entity.
Ex> calling service like "Context.Objects.ByKey(1).SomeFunction().GetValue()"
I want to get "NULL" from the service but instead on the client it throws a 404 Not Found exception.
If I alter the service to return "NULL" then I will receive a serialization exception on the server and if I tell the server to return "OK(null)" I will also get a serialization exception.
Here is the server code for the controller
[HttpGet]
public IHttpActionResult SomeFunction([FromODataUri] int key)
{
string something = null;
// Do some check and adjust the variable "something"
if (string.IsNullOrWhiteSpace(something))
{
return NotFound();
}
else
{
return Ok(something);
}
}
And here is the WebApiConfig code
builder.EntityType<SomeObject>().Function("SomeFunction").Returns<string>();
I can't seem to find the "proper" way of handling null values from the odata service when using Microsoft OData client.
Maybe I can wire into the client "ReceivingResponse" event to handle the 404 Not Found some how? Any suggestions...
The default behavior of the OData client is to throw an exception when the OData service returns a 404 File Not Found.
To get around this there is a property on the OData Client generated code called "IgnoreResourceNotFoundException".
Set this property to true and it with not throw an exception.

How to use a filter in a Struts1 application?

I have a Struts1 application and am unsuccessfully trying to get a filter to work in order to add headers/etc to the response after the action has completed, but am not able to get it to work.
By the time the struts action is completed and control is returned to my filter, the response is already committed.
web.xml:
<filter>
<filter-name>workflow</filter-name>
<filter-class>webapp.session.WorkflowModifierFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>workflow</filter-name>
<servlet-name>action</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>2</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
My filter is the following:
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if( !servletResponse.isCommitted() )
log.debug("Not Committed! Can modify it!");
filterChain.doFilter(servletRequest, servletResponse);
// depending on the response, I'd like to add headers here
if( servletResponse.getStatus() == 200 )
servletResponse.addIntHeader( "x-success", 1 );
if( servletResponse.isCommitted() )
log.debug("ACCCK! Response Already Committed");
}
However, I noticed that my x-success header was never added. A little digging, and I noticed that my response was already returned/committed by the time the control returned to my filter chain.
What is the proper way to do this in Struts1? Does the filter execution not supposed to wrap the entire servlet? Why is my response being committed prior to my filter finishing? Where else can I add headers based on the response (post action processing) in Struts1?
When you call filterChain.doFilter you pass control from your filter to requested page (ie Struts), which is then free to commit the response if it chooses. Since you want to examine the result of the Struts servlet, you should create a response wrapper class (extends HttpServletRequestWrapper) and pass that to filterChain.doFilter rather than the response parameter that your filter is passed.
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
ServletResponse wrapper = new MyHttpServletRequestWrapper(httpResponse);
filter.doChain(servletRequest, wrapper);
The HttpServletRequestWrapper constructor accepts a HttpServletResponse as input and delegates all methods to the wrapped response, but you can override them in your class as necessary. If you want to prevent the response from being committed you'll want to override methods such as flushBuffer, getOutputSteam (the returned output stream can be flushed, committing the response), and getWriter.
But you may not need to do that - try overriding the setStatus method in the wrapper class to check the status code and add the header when it is called:
public class MyHttpServletResponseWrapper extends HttpServletResponseWrapper {
public MyHttpServletResponseWrapper(HttpServletResponse response) {
super(response);
}
#Override
public void setStatus(int sc) {
if(sc == 200) {
addIntHeader("x-success", 1);
}
super.setStatus(sc);
}
}
Although this question was asked in relation to Struts 1, it applies to any web framework; my answer is based on work I did for JSF filters.

Content-Type case sensitivity in RESTEasy 3

I am developing a RestEasy client to connect to a 3rd party REST service which has defined its own custom media types. A made up example is
application/vnd.abc.thirdPartyThing-v1+json
Note the uppercase P in thirdParty.
I am using RESTEasy 3.0.11 for my client implementation. At the point where I make a POST call to the service my code looks like
Response response = target.request()
.post(Entity.<ThirdPartyThing>entity(
thing,
"application/vnd.abc.thirdPartyThing-v1+json"));
but RESTEasy sends to the server
Content-Type: application/vnd.abc.thirdpartything-v1+json
This is due to RESTEasy's MediaTypeHeaderDelegate class's toString() method, which lowercases the type and subtype MediaTypeHeaderDelegate. This should be correct, or at least unimportant, as RFC-1341 states that Content-Type values are case-insensitive - RFC-1341
Unfortunately the 3rd party service is checking the Content-Type in a case sensitive manner and so returning a 415 UNSUPPORTED MEDIA TYPE error. I've tested using curl which doesn't alter the content-type value and confirmed that it's a case issue. application/vnd.abc.thirdPartyThing-v1+json works, application/vnd.abc.thirdpartything-v1+json does not.
I'm in the process of raising a ticket, but in the meantime is there any way to override RESTEasy's default behaviour and send Content-Type headers without lowercasing the value?
Thanks for reading.
I could reproduce this behavior with RESTeasy 3.0.6.Final and would not expect it. Maybe you could check their JIRA if this has already been discussed or open an issue. I once had problems on the server side because a 2.x version of RESTeasy was checking the charset attribute of the Content-Type header case-sensitive. This was also changed.
You could solve this problem by a really ugly workaround: Overwrite the header again in a ClientRequestFilter.
public class ContentTypeFilter implements ClientRequestFilter {
private Map<String, String> contentTypes;
public ContentTypeFilter() {
contentTypes = new HashMap<>();
contentTypes.put("text/foo", "text/Foo");
}
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
String contentType = requestContext.getHeaderString("Content-Type");
if (contentTypes.containsKey(contentType)) {
requestContext.getHeaders().putSingle("Content-Type", contentTypes.get(contentType));
}
}
}
Don't forget to register this Filter:
Client client = ClientBuilder.newClient().register(ContentTypeFilter.class);