Skip to content

Commit ad2d70f

Browse files
jansupolsenivam
authored andcommitted
Missing Content-Type header should be application/octet-stream optionally (#4911)
Signed-off-by: jansupol <[email protected]>
1 parent 5b54138 commit ad2d70f

File tree

46 files changed

+164
-287
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+164
-287
lines changed

bundles/jaxrs-ri/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@
314314
<plugin>
315315
<groupId>org.apache.maven.plugins</groupId>
316316
<artifactId>maven-shade-plugin</artifactId>
317-
<version>3.1.0</version>
317+
<version>3.2.4</version>
318318
<executions>
319319
<execution>
320320
<phase>package</phase>

core-server/src/main/java/org/glassfish/jersey/server/ServerProperties.java

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -743,35 +743,20 @@ public final class ServerProperties {
743743

744744
/**
745745
* <p>
746-
* When an HTTP request does not contain a media type, the media type should default to {@code application/octet-stream}.
747-
* In the past, Jersey used to match any {@code @Consumes} when the HTTP {@code Content-Type} header was not set.
748-
* This property is to preserve the behaviour. Such behaviour is potentially dangerous, though.
749-
* The default behaviour is to set the request media type to {@code application/octet-stream} when none set.
750-
* This is a Jakarta REST requirement.
751-
* </p>
752-
* <p>
753-
* This change can be can be eminent especially for HTTP resource methods which do not expect an entity, such as HTTP GET:
754-
* <pre>
755-
* {@code
756-
* @Consumes(MediaType.TEXT_PLAIN)
757-
* @Produces(MediaType.TEXT_PLAIN)
758-
* @Path("/")
759-
* public class Resource {
760-
* @GET
761-
* public String get() {
762-
* return ...
763-
* }
764-
* }
765-
* }
766-
* </pre>
767-
* The client request needs to contain the {@code Content-Type} HTTP header, for instance:
768-
* {@code
769-
* webTarget.request().header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN).get()
770-
* }
771-
* </p>
772-
* <p>
773-
* Set this property to true, if the empty request media type is to match any {@code @Consumes}. The default is {@code false}.
774-
* The name of the configuration property is <tt>{@value}</tt>.
746+
* Jakarta RESTful WebServices provides {@code @Consumes} annotation to accept only HTTP requests with compatible
747+
* {@code Content-Type} header. However, when the header is missing a wildcard media type is used to
748+
* match the {@code @Consumes} annotation.
749+
* </p>
750+
* <p><a href="https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.1.5">HTTP/1.1 RFC</a>recommends that missing
751+
* {@code Content-Type} header MAY default to {@code application/octet-stream}. This property makes Jersey consider the
752+
* missing HTTP {@code Content-Type} header to be {@code application/octet-stream} rather than a wildcard
753+
* media type. However, for a resource method without an entity argument, such as for HTTP GET, a wildcard media type
754+
* is still considered to accept the HTTP request for the missing HTTP {@code Content-Type} header.
755+
* </p>
756+
* <p>
757+
* Set this property to false, if the empty request media type should not to match applied {@code @Consumes} annotation
758+
* on a resource method with an entity argument. The default is {@code true}. The name of the configuration property is
759+
* <tt>{@value}</tt>.
775760
* </p>
776761
* @since 3.1.0
777762
*/

core-server/src/main/java/org/glassfish/jersey/server/internal/routing/AbstractMethodSelectingRouter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959

6060
abstract class AbstractMethodSelectingRouter extends ContentTypeDeterminer implements Router {
6161

62-
private static final Logger LOGGER = Logger.getLogger(MethodSelectingRouter.class.getName());
62+
private static final Logger LOGGER = Logger.getLogger(AbstractMethodSelectingRouter.class.getName());
6363

6464
private static final Comparator<ConsumesProducesAcceptor> CONSUMES_PRODUCES_ACCEPTOR_COMPARATOR =
6565
new Comparator<ConsumesProducesAcceptor>() {

core-server/src/main/java/org/glassfish/jersey/server/internal/routing/MethodSelectingRouter.java renamed to core-server/src/main/java/org/glassfish/jersey/server/internal/routing/OctetStreamMethodSelectingRouter.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.glassfish.jersey.internal.routing.CombinedMediaType;
2222
import org.glassfish.jersey.message.MessageBodyWorkers;
2323
import org.glassfish.jersey.server.ContainerRequest;
24+
import org.glassfish.jersey.server.model.ResourceMethod;
2425

2526
/**
2627
* A single router responsible for selecting a single method from all the methods
@@ -29,11 +30,8 @@
2930
* The method selection algorithm selects the handling method based on the HTTP request
3031
* method name, requested media type as well as defined resource method media type
3132
* capabilities.
32-
*
33-
* @author Jakub Podlesak
34-
* @author Marek Potociar
3533
*/
36-
final class MethodSelectingRouter extends AbstractMethodSelectingRouter implements Router {
34+
final class OctetStreamMethodSelectingRouter extends AbstractMethodSelectingRouter implements Router {
3735

3836
/**
3937
* Create a new {@code MethodSelectingRouter} for all the methods on the same path.
@@ -44,7 +42,7 @@ final class MethodSelectingRouter extends AbstractMethodSelectingRouter implemen
4442
* @param workers message body workers.
4543
* @param methodRoutings [method model, method methodAcceptorPair] pairs.
4644
*/
47-
MethodSelectingRouter(MessageBodyWorkers workers, List<MethodRouting> methodRoutings) {
45+
OctetStreamMethodSelectingRouter(MessageBodyWorkers workers, List<MethodRouting> methodRoutings) {
4846
super(workers, methodRoutings);
4947
}
5048

@@ -72,8 +70,11 @@ private ConsumesProducesAcceptor(
7270
*/
7371
boolean isConsumable(ContainerRequest requestContext) {
7472
MediaType contentType = requestContext.getMediaType();
75-
contentType = contentType == null ? MediaType.APPLICATION_OCTET_STREAM_TYPE : contentType;
76-
return consumes.getMediaType().isCompatible(contentType);
73+
if (contentType == null && methodRouting.method.getType() != ResourceMethod.JaxrsType.SUB_RESOURCE_LOCATOR
74+
&& methodRouting.method.getInvocable().requiresEntity()) {
75+
contentType = MediaType.APPLICATION_OCTET_STREAM_TYPE;
76+
}
77+
return contentType == null || consumes.getMediaType().isCompatible(contentType);
7778
}
7879
}
7980
}

core-server/src/main/java/org/glassfish/jersey/server/internal/routing/RuntimeModelBuilder.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.util.Collection;
2121
import java.util.Collections;
2222
import java.util.List;
23-
import java.util.Properties;
2423
import java.util.function.Function;
2524

2625
import jakarta.ws.rs.core.Configuration;
@@ -57,7 +56,7 @@ final class RuntimeModelBuilder {
5756

5857
// SubResourceLocator Model Builder.
5958
private final Value<RuntimeLocatorModelBuilder> locatorBuilder;
60-
private final boolean is2xMethodSelectingRouter;
59+
private final boolean isWildcardMethodSelectingRouter;
6160

6261
/**
6362
* Create a new instance of the runtime model builder.
@@ -86,8 +85,8 @@ public RuntimeModelBuilder(
8685
this.locatorBuilder = Values.lazy((Value<RuntimeLocatorModelBuilder>)
8786
() -> new RuntimeLocatorModelBuilder(config, messageBodyWorkers, valueSuppliers, resourceContext,
8887
RuntimeModelBuilder.this, modelProcessors, createServiceFunction));
89-
this.is2xMethodSelectingRouter = ServerProperties.getValue(config.getProperties(),
90-
ServerProperties.EMPTY_REQUEST_MEDIA_TYPE_MATCHES_ANY_CONSUMES, false);
88+
this.isWildcardMethodSelectingRouter = ServerProperties.getValue(config.getProperties(),
89+
ServerProperties.EMPTY_REQUEST_MEDIA_TYPE_MATCHES_ANY_CONSUMES, true);
9190
}
9291

9392
private Router createMethodRouter(final ResourceMethod resourceMethod) {
@@ -154,9 +153,9 @@ public Router buildModel(final RuntimeResourceModel resourceModel, final boolean
154153
// resource methods
155154
if (!resource.getResourceMethods().isEmpty()) {
156155
final List<MethodRouting> methodRoutings = createResourceMethodRouters(resource, subResourceMode);
157-
final Router methodSelectingRouter = is2xMethodSelectingRouter
158-
? new MethodSelectingRouter2x(messageBodyWorkers, methodRoutings)
159-
: new MethodSelectingRouter(messageBodyWorkers, methodRoutings);
156+
final Router methodSelectingRouter = isWildcardMethodSelectingRouter
157+
? new WildcardMethodSelectingRouter(messageBodyWorkers, methodRoutings)
158+
: new OctetStreamMethodSelectingRouter(messageBodyWorkers, methodRoutings);
160159
if (subResourceMode) {
161160
currentRouterBuilder = startNextRoute(currentRouterBuilder, PathPattern.END_OF_PATH_PATTERN)
162161
.to(resourcePushingRouter)
@@ -185,9 +184,9 @@ public Router buildModel(final RuntimeResourceModel resourceModel, final boolean
185184
srRoutedBuilder = startNextRoute(srRoutedBuilder, childClosedPattern)
186185
.to(uriPushingRouter)
187186
.to(childResourcePushingRouter)
188-
.to(is2xMethodSelectingRouter
189-
? new MethodSelectingRouter2x(messageBodyWorkers, childMethodRoutings)
190-
: new MethodSelectingRouter(messageBodyWorkers, childMethodRoutings));
187+
.to(isWildcardMethodSelectingRouter
188+
? new WildcardMethodSelectingRouter(messageBodyWorkers, childMethodRoutings)
189+
: new OctetStreamMethodSelectingRouter(messageBodyWorkers, childMethodRoutings));
191190
}
192191

193192
// sub resource locator
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
* @author Jakub Podlesak
3434
* @author Marek Potociar
3535
*/
36-
final class MethodSelectingRouter2x extends AbstractMethodSelectingRouter implements Router {
36+
final class WildcardMethodSelectingRouter extends AbstractMethodSelectingRouter implements Router {
3737

3838
/**
3939
* Create a new {@code MethodSelectingRouter} for all the methods on the same path.
@@ -44,7 +44,7 @@ final class MethodSelectingRouter2x extends AbstractMethodSelectingRouter implem
4444
* @param workers message body workers.
4545
* @param methodRoutings [method model, method methodAcceptorPair] pairs.
4646
*/
47-
MethodSelectingRouter2x(MessageBodyWorkers workers, List<MethodRouting> methodRoutings) {
47+
WildcardMethodSelectingRouter(MessageBodyWorkers workers, List<MethodRouting> methodRoutings) {
4848
super(workers, methodRoutings);
4949
}
5050

core-server/src/test/java/org/glassfish/jersey/server/model/ConsumeProduceSimpleTest.java

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ private ApplicationHandler createApplication(Class<?>... classes) {
4747
return new ApplicationHandler(new ResourceConfig(classes));
4848
}
4949

50-
private ApplicationHandler create2xApplication(Class<?>... classes) {
50+
private ApplicationHandler createAppOctetStreamApplication(Class<?>... classes) {
5151
return new ApplicationHandler(
52-
new ResourceConfig(classes).property(ServerProperties.EMPTY_REQUEST_MEDIA_TYPE_MATCHES_ANY_CONSUMES, true)
52+
new ResourceConfig(classes).property(ServerProperties.EMPTY_REQUEST_MEDIA_TYPE_MATCHES_ANY_CONSUMES, false)
5353
);
5454
}
5555

@@ -154,7 +154,7 @@ public void testProduceSimpleBean() throws Exception {
154154
assertEquals("XHTML",
155155
app.apply(RequestContextBuilder.from("/a/b", "GET").accept("text/xhtml").build()).get().getEntity());
156156

157-
app = create2xApplication(ProduceSimpleBean.class);
157+
app = createAppOctetStreamApplication(ProduceSimpleBean.class);
158158

159159
assertEquals("HTML", app.apply(RequestContextBuilder.from("/a/b", "GET").accept("text/html").build()).get().getEntity());
160160
assertEquals("XHTML",
@@ -172,28 +172,38 @@ public void testConsumeProduceSimpleBean() throws Exception {
172172
app.apply(RequestContextBuilder.from("/a/b", "POST").entity("").type("text/xhtml").accept("text/xhtml").build())
173173
.get().getEntity());
174174

175-
assertEquals(415,
176-
app.apply(RequestContextBuilder.from("/a/b", "GET").accept("text/html").build())
177-
.get().getStatus()
175+
assertEquals("HTML",
176+
app.apply(RequestContextBuilder.from("/a/b", "GET").accept("text/html").build()).get().getEntity()
178177
);
179178
assertEquals("HTML",
180179
app.apply(RequestContextBuilder.from("/a/b", "GET").type("text/html").accept("text/html").build())
181180
.get().getEntity()
182181
);
183182

184-
assertEquals(415,
185-
app.apply(RequestContextBuilder.from("/a/b", "GET").accept("text/xhtml").build())
186-
.get().getStatus()
183+
assertEquals("XHTML",
184+
app.apply(RequestContextBuilder.from("/a/b", "GET").accept("text/xhtml").build()).get().getEntity()
187185
);
188186
assertEquals("XHTML",
189187
app.apply(RequestContextBuilder.from("/a/b", "GET").type("text/xhtml").accept("text/xhtml").build())
190188
.get().getEntity()
191189
);
192190

193-
app = create2xApplication(ConsumeProduceSimpleBean.class);
191+
app = createAppOctetStreamApplication(ConsumeProduceSimpleBean.class);
194192
assertEquals("HTML", app.apply(RequestContextBuilder.from("/a/b", "GET").accept("text/html").build()).get().getEntity());
195193
assertEquals("XHTML",
196194
app.apply(RequestContextBuilder.from("/a/b", "GET").accept("text/xhtml").build()).get().getEntity());
195+
196+
assertEquals(415,
197+
app.apply(RequestContextBuilder.from("/a/b", "POST").entity("").accept("text/html").build()).get().getStatus());
198+
assertEquals(415,
199+
app.apply(RequestContextBuilder.from("/a/b", "POST").entity("").accept("text/xhtml").build()).get().getStatus());
200+
201+
assertEquals("HTML",
202+
app.apply(RequestContextBuilder.from("/a/b", "POST").entity("").type("text/html").accept("text/html").build())
203+
.get().getEntity());
204+
assertEquals("XHTML",
205+
app.apply(RequestContextBuilder.from("/a/b", "POST").entity("").type("text/xhtml").accept("text/xhtml").build())
206+
.get().getEntity());
197207
}
198208

199209
@Path("/")

docs/src/main/docbook/appendix-properties.xml

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -277,16 +277,22 @@
277277
<entry><literal>jersey.config.server.empty.request.media.matches.any.consumes</literal></entry>
278278
<entry>
279279
<para>
280-
When an HTTP request does not contain a media type, the media type should default to
281-
<literal>application/octet-stream</literal>. In the past, Jersey used to match any
282-
<literal>@Consumes</literal> when the HTTP <literal>Content-Type</literal> header was not set.
283-
This property is to preserve the behaviour. Such behaviour is potentially dangerous, though.
284-
The default behaviour is to set the request media type to
285-
<literal>application/octet-stream</literal> when none set. This is a Jakarta REST requirement.
280+
Jakarta RESTful WebServices provides <literal>@Consumes</literal> annotation to accept only HTTP
281+
requests with compatible HTTP <literal>Content-Type</literal> header. However, when the header
282+
is missing a wildcard media type is used to match the <literal>@Consumes</literal> annotation.
286283
</para>
287284
<para>
288-
Set this property to <literal>true</literal>, if the empty request media type is to match any
289-
<literal>@Consumes</literal>. The default value is <literal>false</literal>.
285+
HTTP/1.1 RFC recommends that missing HTTP <literal>Content-Type</literal> header MAY default to
286+
<literal>application/octet-stream</literal>. This property makes Jersey consider the missing HTTP
287+
<literal>Content-Type</literal> header to be <literal>application/octet-stream</literal> rather
288+
than a wildcard media type. However, for a resource method without an entity argument, such as
289+
for HTTP GET, a wildcard media type is still considered to accept the HTTP request for
290+
the missing HTTP <literal>Content-Type</literal> header.
291+
</para>
292+
<para>
293+
Set this property to <literal>false</literal>, if the empty request media type should not to match
294+
applied <literal>@Consumes</literal> annotation on a resource method with an entity argument. The
295+
default is <literal>true</literal>.
290296
</para>
291297
</entry>
292298
</row>

docs/src/main/docbook/migration.xml

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -96,22 +96,6 @@
9696
supports JDK 8.
9797
</para>
9898
</listitem>
99-
<listitem>
100-
<para>
101-
Since Jersey 3.1.0 when incoming the HTTP request does not contain content type, the
102-
<literal>application/octet-stream</literal> is considered, as required by the Jakarta REST
103-
Specification and HTTP/1.1 RFC 2616. As a consequence, a resource method with non-wildcard
104-
<literal>@Consumes</literal> annotation is not matched with the request without the
105-
Content-Type HTTP header. This is typically the case of HTTP requests without an entity,
106-
such as HTTP GET requests, where the Content-Type header is not set.
107-
</para>
108-
<para>
109-
When using the <literal>@Consumes</literal> annotation, the Content-Type HTTP header must be set.
110-
The previous behaviour when the missing Content-Type HTTP header matched any resource method
111-
with <literal>@Consumes</literal> annotation, the
112-
&jersey.server.ServerProperties.EMPTY_REQUEST_MEDIA_TYPE_MATCHES_ANY_CONSUMES; property can be set.
113-
</para>
114-
</listitem>
11599
<listitem>
116100
<para>
117101
&jersey.server.ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE; is by default

etc/jenkins/jenkins_build.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22

33
export DEBUG=true
44

5-
mvn -V -U -B -e clean install glassfish-copyright:check -Dcopyright.quiet=false
5+
mvn -V -U -B -e clean install glassfish-copyright:check -Dcopyright.quiet=false
6+

0 commit comments

Comments
 (0)