eclipse-ee4j / eclipse-ee4j/jersey

Inconsistent media type negotitation

Open
#3,605 4 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
730
Forks
382
PR merge metrics
No merged PRs in 30d

Description

Hi,

Let's consider following resource running into a Jersey container:

```
@Path("echo")
public class EchoResource {

@XmlRootElement
public static class Message {

private String message;

public Message() {
}

public String getMessage() {
return this.message;
}

public void setMessage(String message) {
this.message = message;
}

}

@Path("/echo1")
@Produces({ "*/xml" })
@GET
public Response echo1(@QueryParam("msg") String msg) {
Message message = new Message();
message.setMessage(String.valueOf(msg));
return Response.ok(message).build();
}

@Path("/echo2")
@Produces({ "*/xml", MediaType.APPLICATION_OCTET_STREAM })
@GET
public Response echo2(@QueryParam("msg") String msg) {
Message message = new Message();
message.setMessage(String.valueOf(msg));
return Response.ok(message).build();
}

}
```

- If calling `echo1` method using any HTTP client with `Accept` header set to `application/*`, it will fail with:

>
> javax.ws.rs.InternalServerErrorException: HTTP 500 Internal Server Error
> at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:90)
> at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
> at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1130)
> at org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:711)
> at org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:444)
> at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:434)
> at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:329)
> at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
> at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
> at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
> at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
> at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
> at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
> at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305)
> at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154)
> at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:473)
> at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427)
> at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388)
> at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341)
> at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228)
> at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:812)
> at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:587)
> at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:221)
> at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
> at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
> at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
> at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
> at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
> at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
> at org.eclipse.jetty.server.Server.handle(Server.java:499)
> at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
> at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
> at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:544)
> at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
> at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
> at java.lang.Thread.run(Thread.java:745)
> Caused by: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=application/octet-stream, type=class org.nne.jaxrs.ext.oauth2.server.demo.rs.resource.EchoResource$Message, genericType=class org.nne.jaxrs.ext.oauth2.server.demo.rs.resource.EchoResource$Message.
> at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:247)
> at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
> at org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:106)
> at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
> at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:86)
> ... 35 more

- If calling `echo2` method in the same context it will success and the response media type will be application/xml.

I was expecting the first call to work as the second one, but it does not because of the fact that `echo1` method id annotated with `@Produces({ "*/xml" })` which contains only 1 media type.

The code to check seems to be `MethodSelectingRouter.usePreSelectedMediaType(...)` called by `MethodSelectingRouter.determineResponseMediaType(...)`.

Maybe this method should become:

```
private static boolean usePreSelectedMediaType(final RequestSpecificConsumesProducesAcceptor selectedMethod,
final List acceptableMediaTypes) {
// Resource method is annotated with @Produces and this annotation contains only one MediaType.
if (!selectedMethod.producesFromProviders
&& selectedMethod.methodRouting.method.getProducedTypes().size() == 1
&& !MediaTypes.isWildcard(selectedMethod.methodRouting.method.getProducedTypes().get(0)) {
return true;
}

// There is only one (non-wildcard) acceptable media type - at this point the pre-selected method has to be chosen so
// there are compatible writers (not necessarily writeable ones).
return acceptableMediaTypes.size() == 1 && !MediaTypes.isWildcard(acceptableMediaTypes.get(0));
}
```
What do you think ?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.