NotAcceptableException (HTTP 406 Not Acceptable) but no apparent error from MessageBodyWriter - jax-rs

In a webapp implemented as a Jetty container, we have a custom javax.ws.rs.ext.MessageBodyWriter<T> annotated with
#Singleton
#Provider
#Produces( "application/rss+xml" )
We also have a resource which works just fine. The get() method is annotated with
#Produces( "application/vnd.api+json" )
Visiting that endpoint returns the expected json response.
Adding .rss to the endpoint causes a 406 response to be returned.
What could be the reason it is not finding the MessageBodyWriter for returning the RSS response?
The full stacktrace is:
javax.ws.rs.NotAcceptableException: HTTP 406 Not Acceptable
at org.glassfish.jersey.server.internal.routing.MethodSelectingRouter.getMethodRouter(MethodSelectingRouter.java:472)
at org.glassfish.jersey.server.internal.routing.MethodSelectingRouter.access$000(MethodSelectingRouter.java:73)
at org.glassfish.jersey.server.internal.routing.MethodSelectingRouter$4.apply(MethodSelectingRouter.java:674)
at org.glassfish.jersey.server.internal.routing.MethodSelectingRouter.apply(MethodSelectingRouter.java:305)
at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:86)
at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:89)
at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:89)
at org.glassfish.jersey.server.internal.routing.RoutingStage.apply(RoutingStage.java:69)
at org.glassfish.jersey.server.internal.routing.RoutingStage.apply(RoutingStage.java:38)
at org.glassfish.jersey.process.internal.Stages.process(Stages.java:173)
at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:247)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244)
at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
at org.glassfish.jersey.internal.Errors.process(Errors.java:244)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:265)
at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:234)
at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:684)
at <our package>.Jetty94HttpContainer.handle(Jetty94HttpContainer.java:167) // Jetty94HttpContainer extends AbstractHandler implements Container
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
at com.codahale.metrics.jetty9.InstrumentedHandler.handle(InstrumentedHandler.java:284)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
at org.eclipse.jetty.server.Server.handle(Server.java:516)
at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:487)
at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:732)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:479)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:277)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:338)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:315)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:173)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:131)
at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:409)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034)
at java.base/java.lang.Thread.run(Thread.java:829)

The resource methods annotated with a request method should declare the supported request media types equals to the entity providers that supply mapping services between representations and their associated Java types.
Adding:
#Produces( "application/vnd.api+json", "application/rss+xml" )
to the get() method should do the trick.

(Posted an answer from the question author in order to move it to the answer space).
Thanks to Lety I was able to find the problem. It's one of those which are super-obvious once you realise it, but which no amount of staring at the code alone would reveal!
This answer also helped
If you want to switch a resource output between different output types depending on the Accept request header, the resource method needs #Produces to cover all the desired output types, as well as having a MessageBodyWriter configured for that output.
In other words, I changed the get() method of the resource to read ...
#Produces( "application/vnd.api+json", "application/rss+xml" )
... and this is what made it work.

Related

FeignClient error: url values must be not be absolute

I have an error when starting SpringBootApplication:
Unexpected exception during bean creation; nested exception is java.lang.IllegalArgumentException: url values must be not be absolute.
I'm a beginner is SpringCloud, but I worked with Openshift (On first look it's basically the same things).
I have a cluster with GatewayApplication and some business microservices in it, wrote on Kotlin. Inside cluster microservices communicate by FeignClient without authentification. In consumer-service it looks like it:
#FeignClient(name = "producer-service")
#Headers("Content-Type: application/json")
interface MarketServiceFeign {
#GetMapping("https://somehost.ru/{id}/orders")
fun getUserDevices(
#PathVariable id: String,
): ResponseEntity<List<UserOrder>>
}
I tried find same case, but couldn't.
I tried to:
use #RequestLine from feign-core, but it doesn't work with #FeignClient
use feign.#Param for argument instead of org.springframework.web.bind.annotation.#PathVariable
use url with http instead of https
The thing I didn't take into account is that FeignClient goes to services into cluster by name, not by host. So I fixed it like that:
#FeignClient(name = "producer-service")
#Headers("Content-Type: application/json")
interface MarketServiceFeign {
#GetMapping("/{id}/orders")
fun getUserDevices(
#PathVariable id: String
): ResponseEntity<List<UserDevice>>
}
How I understand, instead of "https://somehost.ru" FeignClient uses service name "producer-service". Result url for FeignClient is "producer-service/{id}/orders".
I hope this helps someone.
If you want to use an absolute URL instead of using load-balancing, you need to pass it via the url attribute in the #FeignClient annotation. It's going to be a URL per Feign client, so you cannot pass it per-request via #RequestMapping annotations. You can only use them to provide the path segments that follow the host url. If you do not pass the url in #FeignClient, the name will be used as serviceId to fetch all the instances of that service (for example, from a service registry) and load-balancing will be performed under the hood to select an instance to send the request to.

Ktor returns 415 from endpoints where receive() is used with ContentNegotiation

I have parameter classes with the #Searializable annotation:
#Serializable
data class ShowPostURL(
val date: String,
val titleSlug: String,
override val redirectTo: String? = null
)
and no matter what I do call.receive() won't work. I'm getting HTTP 415 errors and Ktor doesn't log anything. I've added the serialization support as well:
install(ContentNegotiation) {
json()
}
How do I fix this? This is how I'm trying to use it:
accept(ContentType.Any) {
get("/foo/{date}/{titleSlug}") {
val input = call.receive(ShowPostURL::class)
call.respondText("foo")
}
}
If I do a trace I can see that my route is matched, but it can't receive the parameters. Is this json() setup is supposed to work when I'm deserializing from url parameters like this?
Firstly, ContentNegotiation feature works only for receiving custom objects from the payload of POST, PUT and PATCH requests:
POST, PUT and PATCH requests have an associated request body (the payload). That payload is usually encoded.
In order to receive custom objects from the payload, you have to use the ContentNegotiation feature. This is useful for example to receive and send JSON payloads in REST APIs.
When receiving, the Content-Type of the request will be used to determine which ContentConverter will be used to process that request
Secondly, there are three out of the box ContentConverter available:
GsonConverter, JacksonConverter and SerializationConverter.
Each of these converters has its own configuration function: gson, jackson and serialization respectively. You use json configuration function which is most likely is not appropriate for the configuration of ContentNegotiation.
To solve your problem you can access URL parameters by referring them with call.parameters and manually create ShowPostURL object. Then serialize it with the kotlinx.serialization framework if needed.
Also, you can write your own ContentConverter to implement custom logic for receiving typed objects.

Where is a Response transformed into one of its subclasses?

I'm trying to write a downloader middleware that ignores responses that don't have a pre-defined element. However, I can't use the css method of the HtmlResponse class inside the middleware because, at that point, the response's type is just Response. When it reaches the spider it's an HtmlResponse, but then it's too late because I can't perform certain actions to the middleware state.
Where is the response's final type set?
Without seeing your code of the middleware it is hard to tell what the matter is.
However my middleware below gets an HtmlResponse object:
class FilterMiddleware(object):
def process_response(self, request, response, spider):
print response.__class__
print type(response)
return response**strong text**
Both print statements verify this:
<class 'scrapy.http.response.html.HtmlResponse'>
<class 'scrapy.http.response.html.HtmlResponse'>
And I can use the css method on the response without any exception. The order of the middleware in the settings.py does not matter either: with 10, 100 or 500 I get the same result as above.
However if I configure the middleware to 590 or above I get plain old Response object. And this is because the conversion happens in the HttpCompressionMiddleware class on line 35 in the current version.
To solve your issue order your middleware somewhere later on the pipeline (with a lower order number) or convert the response yourself (I would not do this however).

how to see actual body sent from restassured

I have created a jax-rs rest api and tested it with rest-assured. All tests are green. Now I am trying to create an html/js front end for it.
My problem is I do not know how my json objects should look like to be accepted by my rest api. Thanks to restassured/jax-rs I never handled request strings. I fill in objects and I get objects, (un)marshaling (json) is invisible.
Is there any way I can see (debug) what strings are created by rest-assured/java and sent over the "wire"?
If you want to log the request body you can do:
given().log().body(). ..
Or if you want to log the response body you can do:
.. .then().log().body(). ..
See documentation on logging for more details.
I'm not a RestAssured used, so I can't answer your question directly, but here are some ideas that may help you out.
I don't know what serializer RestAssured uses under the hood, but Resteasy on Wildfly uses Jackson by default. I would get familiar with this library. For less trivial application, you may need to dig into its APIs directly to get your desired results. Here is it's documentation. For your particular case you can do something as simple as
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(yourObject);
System.out.println(jsonString);
This will print out the POJO in JSON format, based on your getters in the class. This is at the most basic level. If you don't already have Jackson as a dependency, you can add
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.4.0</version>
</dependency>
A really good friend (tool) to have is cURL. It's a command line tool that allows you to make REST/HTTP (other protocols also) requests. Though for this particular case it wouldn't help, you could send a GET request to one your resources that serves up the same type that you accepted in your POST. That way, you can see the resulting JSON. This may be a little much at this point, but I would definitely look into this tool if you're going to be doing a lot of REST development.
You might also want to check out a Browser tool like [Postman for Chrome]
You should really get familiar with JSON format. Once you get familiar with it, and you start working with JSON framework, you'll notice that at a basic level, they all work similarly.
Java Object == JSON Object ( {} )
Java Collection/Array == JSON Array ( [] )
Java fields/properties == JSON keys
Getters are used for they keys and their values (for serialization)
Setters are used for deserialization
So for example you have this class
public class Person {
String name;
List<Person> friends;
public String getName() { return name; }
public void setName(String name) { return name; }
// Getter and Setter for friends
}
An instance of Person would produce the following JSON
{
"name" : "Peeskillet",
"friends": [
{
"name": "Lebron James"
},
{
"name": "Steph Curry"
}
]
}
It's actually pretty simple once you get the hang of it.
Oh and another thing you can do is add a logging filter on the server side as mentioned here.
As far as working with Javascript, there is a JSON.stringify(javascriptObject) that will serialize your Javacript objects to JSON strings. So generally, you can model your Javascript object like your Java objects.
Hope this helped.
In RestAssured just use this:
String body = resp.asString();
System.out.println(body);
Try this for Request Body
RequestSpecification httpRequest = RestAssured.given().urlEncodingEnabled(true);
..
..
..
System.out.println(" Body ==> "+ httpRequest.log().body());
And for response body:
System.out.println("Res Body ===>"+ response.getBody().prettyPrint());

JAX-RS and #Produces ability to match on regex

I'm writing a Restful web service that requires versioning. The way I want to go about this is using the Media type in the header to do this.
Example of request:
Accept: application/vnd.test.books.v1+xml
I would have an endpoint of
#GET
#Path("/test")
#Produces("application/vnd.test.books.v1+xml")
public Response getA(){
...
}
I would also like to have regex matching all versions. So I want to match on anything with Accept header of application/vnd.test.books.*+xml . So any version in my case. I tried something like the following with no lucks.
#GET
#Path("/test")
#Produces("application/vnd.test.books*+xml")
public Response getB(){
...
}
What I want is the ability to down the line have endpoint for v1 specifically then anything above v2 I want a different end point.
You must explicitly list all the MIME types a method #Produces:
#Produces(value = {"application/vnd.test.books.v2+xml",
"application/vnd.test.books.v3+xml",
"application/vnd.test.books.v4+xml"})