diff --git a/http/README.md b/http/README.md index 2940b01227..f7a7a3cf6e 100644 --- a/http/README.md +++ b/http/README.md @@ -387,61 +387,63 @@ The service can both be configured using OSGi environment properties and using C this service is `"org.apache.felix.http"`. If you use both methods, Configuration Admin takes precedence. The following properties can be used (some legacy property names still exist but are not documented here on purpose). As properties might change over time, the actual list of properties can be found [here for the Jetty 12 bundle](https://github.com/apache/felix-dev/blob/master/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java) and [here for the Jetty 11 bundle](https://github.com/apache/felix-dev/blob/master/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java). -| Property | Description | -|----------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `org.apache.felix.http.host` | Host name or IP Address of the interface to listen on. The default is `null` causing Jetty to listen on all interfaces. | -| `org.osgi.service.http.port` | The port used for servlets and resources available via HTTP. The default is `8080`. See [port settings below](#http-port-settings) for additional information. A negative port number has the same effect as setting `org.apache.felix.http.enable` to `false`. | -| `org.osgi.service.http.port.secure` | The port used for servlets and resources available via HTTPS. The default is `8443`. See [port settings below](#http-port-settings) for additional information. A negative port number has the same effect as setting `org.apache.felix.https.enable` to `false`. | -| `org.apache.felix.http.context_path` | The servlet Context Path to use for the Http Service. If this property is not configured it defaults to "/". This must be a valid path starting with a slash and not ending with a slash (unless it is the root context). | -| `org.apache.felix.http.timeout` | Connection timeout in milliseconds. The default is `60000` (60 seconds). | -| `org.apache.felix.http.session.timeout` | Allows for the specification of the Session life time as a number of minutes. This property serves the same purpose as the `session-timeout` element in a Web Application descriptor. The default is "0" (zero) for no timeout at all. | -| `org.apache.felix.http.enable` | Flag to enable the use of HTTP. The default is `true`. | -| `org.apache.felix.https.enable` | Flag to enable the user of HTTPS. The default is `false`. | -| `org.apache.felix.https.keystore` | The name of the file containing the keystore. | -| `org.apache.felix.https.keystore.password` | The password for the keystore. | -| `org.apache.felix.https.keystore.key.password` | The password for the key in the keystore. | -| `org.apache.felix.https.truststore` | The name of the file containing the truststore. | -| `org.apache.felix.https.truststore.type` | The type of truststore to use. The default is `JKS`. | -| `org.apache.felix.https.truststore.password` | The password for the truststore. | -| `org.apache.felix.https.jetty.ciphersuites.excluded` | Configures comma-separated list of SSL cipher suites to *exclude*. Default is `null`, meaning that no cipher suite is excluded. | -| `org.apache.felix.https.jetty.ciphersuites.included` | Configures comma-separated list of SSL cipher suites to *include*. Default is `null`, meaning that the default cipher suites are used. | -| `org.apache.felix.https.jetty.protocols.excluded` | Configures comma-separated list of SSL protocols (e.g. SSLv3, TLSv1.0, TLSv1.1, TLSv1.2) to *exclude*. Default is `null`, meaning that no protocol is excluded. | -| `org.apache.felix.https.jetty.protocols.included` | Configures comma-separated list of SSL protocols to *include*. Default is `null`, meaning that the default protocols are used. | -| `org.apache.felix.https.clientcertificate` | Flag to determine if the HTTPS protocol requires, wants or does not use client certificates. Legal values are `needs`, `wants` and `none`. The default is `none`. | -| `org.apache.felix.http.jetty.headerBufferSize` | Size of the buffer for request and response headers, in bytes. Default is 16 KB. | -| `org.apache.felix.http.jetty.requestBufferSize` | Size of the buffer for requests not fitting the header buffer, in bytes. Default is 8 KB. | -| `org.apache.felix.http.jetty.responseBufferSize` | Size of the buffer for responses, in bytes. Default is 24 KB. | -| `org.apache.felix.http.jetty.maxFormSize` | The maximum size accepted for a form post, in bytes. Defaults to 200 KB. | -| `org.apache.felix.http.mbeans` | If `true`, enables the MBean server functionality. The default is `false`. | -| `org.apache.felix.http.jetty.sendServerHeader` | If `false`, the `Server` HTTP header is no longer included in responses. The default is `false`. | -| `org.eclipse.jetty.servlet.SessionCookie` | Name of the cookie used to transport the Session ID. The default is `JSESSIONID`. | -| `org.eclipse.jetty.servlet.SessionURL` | Name of the request parameter to transport the Session ID. The default is `jsessionid`. | -| `org.eclipse.jetty.servlet.SessionDomain` | Domain to set on the session cookie. The default is `null`. | -| `org.eclipse.jetty.servlet.SessionPath` | The path to set on the session cookie. The default is the configured session context path ("/"). | -| `org.eclipse.jetty.servlet.MaxAge` | The maximum age value to set on the cookie. The default is "-1". | +| Property | Description | +|----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `org.apache.felix.http.host` | Host name or IP Address of the interface to listen on. The default is `null` causing Jetty to listen on all interfaces. | +| `org.osgi.service.http.port` | The port used for servlets and resources available via HTTP. The default is `8080`. See [port settings below](#http-port-settings) for additional information. A negative port number has the same effect as setting `org.apache.felix.http.enable` to `false`. | +| `org.osgi.service.http.port.secure` | The port used for servlets and resources available via HTTPS. The default is `8443`. See [port settings below](#http-port-settings) for additional information. A negative port number has the same effect as setting `org.apache.felix.https.enable` to `false`. | +| `org.apache.felix.http.context_path` | The servlet Context Path to use for the Http Service. If this property is not configured it defaults to "/". This must be a valid path starting with a slash and not ending with a slash (unless it is the root context). | +| `org.apache.felix.http.timeout` | Connection timeout in milliseconds. The default is `60000` (60 seconds). | +| `org.apache.felix.http.session.timeout` | Allows for the specification of the Session life time as a number of minutes. This property serves the same purpose as the `session-timeout` element in a Web Application descriptor. The default is "0" (zero) for no timeout at all. | +| `org.apache.felix.http.enable` | Flag to enable the use of HTTP. The default is `true`. | +| `org.apache.felix.https.enable` | Flag to enable the user of HTTPS. The default is `false`. | +| `org.apache.felix.https.keystore` | The name of the file containing the keystore. | +| `org.apache.felix.https.keystore.password` | The password for the keystore. | +| `org.apache.felix.https.keystore.key.password` | The password for the key in the keystore. | +| `org.apache.felix.https.truststore` | The name of the file containing the truststore. | +| `org.apache.felix.https.truststore.type` | The type of truststore to use. The default is `JKS`. | +| `org.apache.felix.https.truststore.password` | The password for the truststore. | +| `org.apache.felix.https.jetty.ciphersuites.excluded` | Configures comma-separated list of SSL cipher suites to *exclude*. Default is `null`, meaning that no cipher suite is excluded. | +| `org.apache.felix.https.jetty.ciphersuites.included` | Configures comma-separated list of SSL cipher suites to *include*. Default is `null`, meaning that the default cipher suites are used. | +| `org.apache.felix.https.jetty.protocols.excluded` | Configures comma-separated list of SSL protocols (e.g. SSLv3, TLSv1.0, TLSv1.1, TLSv1.2) to *exclude*. Default is `null`, meaning that no protocol is excluded. | +| `org.apache.felix.https.jetty.protocols.included` | Configures comma-separated list of SSL protocols to *include*. Default is `null`, meaning that the default protocols are used. | +| `org.apache.felix.https.clientcertificate` | Flag to determine if the HTTPS protocol requires, wants or does not use client certificates. Legal values are `needs`, `wants` and `none`. The default is `none`. | +| `org.apache.felix.http.jetty.headerBufferSize` | Size of the buffer for request and response headers, in bytes. Default is 16 KB. | +| `org.apache.felix.http.jetty.requestBufferSize` | Size of the buffer for requests not fitting the header buffer, in bytes. Default is 8 KB. | +| `org.apache.felix.http.jetty.responseBufferSize` | Size of the buffer for responses, in bytes. Default is 24 KB. | +| `org.apache.felix.http.jetty.maxFormSize` | The maximum size accepted for a form post, in bytes (ony applies to form parameters). Defaults to 200 KB. | +| `org.apache.felix.http.jetty.requestSizeLimit` | Maximum size of the request body in bytes. Default is unlimited. Added in Jetty12 1.0.30. | +| `org.apache.felix.http.jetty.responseSizeLimit` | Maximum size of the response body in bytes. Default is unlimited. Default is unlimited. Added in Jetty12 1.0.30. | +| `org.apache.felix.http.mbeans` | If `true`, enables the MBean server functionality. The default is `false`. | +| `org.apache.felix.http.jetty.sendServerHeader` | If `false`, the `Server` HTTP header is no longer included in responses. The default is `false`. | +| `org.eclipse.jetty.servlet.SessionCookie` | Name of the cookie used to transport the Session ID. The default is `JSESSIONID`. | +| `org.eclipse.jetty.servlet.SessionURL` | Name of the request parameter to transport the Session ID. The default is `jsessionid`. | +| `org.eclipse.jetty.servlet.SessionDomain` | Domain to set on the session cookie. The default is `null`. | +| `org.eclipse.jetty.servlet.SessionPath` | The path to set on the session cookie. The default is the configured session context path ("/"). | +| `org.eclipse.jetty.servlet.MaxAge` | The maximum age value to set on the cookie. The default is "-1". | | `org.eclipse.jetty.UriComplianceMode` | The URI compliance mode to set. The default is [DEFAULT](https://eclipse.dev/jetty/javadoc/jetty-12/org/eclipse/jetty/http/UriCompliance.html#DEFAULT). See [documentation](https://eclipse.dev/jetty/documentation/jetty-12/programming-guide/index.html#pg-server-compliance-uri.) and [possible modes](https://github.com/jetty/jetty.project/blob/jetty-12.0.x/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/UriCompliance.java#L186C107-L186C113). Use with caution, as it may have [security implications](https://github.com/apache/felix-dev/pull/308#issuecomment-2438913766). | -| `org.apache.felix.proxy.load.balancer.connection.enable` | Set this to `true` when running Felix HTTP behind a (offloading) proxy or load balancer which rewrites the requests. The default is `false`. | -| `org.apache.felix.http.runtime.init.` | Properties starting with this prefix are added as service registration properties to the HttpServiceRuntime service. The prefix is removed for the property name. | -| `org.apache.felix.jetty.gziphandler.enable` | Whether the server should use a server-wide gzip handler. Default is false. | -| `org.apache.felix.jetty.gzip.minGzipSize` | The minimum response size to trigger dynamic compression. Default is GzipHandler.DEFAULT_MIN_GZIP_SIZE. | -| `org.apache.felix.jetty.gzip.inflateBufferSize` | The size in bytes of the buffer to inflate compressed request, or <= 0 for no inflation. Default is -1. | -| `org.apache.felix.jetty.gzip.syncFlush` | True if Deflater#SYNC_FLUSH should be used, else Deflater#NO_FLUSH will be used. Default is false. | -| `org.apache.felix.jetty.gzip.includedMethods` | The additional http methods to include in compression. Default is none. | -| `org.apache.felix.jetty.gzip.excludedMethods` | The additional http methods to exclude in compression. Default is none. | -| `org.apache.felix.jetty.gzip.includedPaths` | The additional path specs to include. Inclusion takes precedence over exclusion. Default is none. | -| `org.apache.felix.jetty.gzip.excludedPaths` | The additional path specs to exclude. Inclusion takes precedence over exclusion. Default is none. | -| `org.apache.felix.jetty.gzip.includedMimeTypes` | The included mime types. Inclusion takes precedence over exclusion. Default is none. | -| `org.apache.felix.jetty.gzip.excludedMimeTypes` | The excluded mime types. Inclusion takes precedence over exclusion. Default is none. | -| `org.apache.felix.http2.enable` | Whether to enable HTTP/2. Default is false. | -| `org.apache.felix.jetty.http2.maxConcurrentStreams` | The max number of concurrent streams per connection. Default is 128. | -| `org.apache.felix.jetty.http2.initialStreamRecvWindow` | The initial stream receive window (client to server). Default is 524288. | -| `org.apache.felix.jetty.http2.initialSessionRecvWindow` | The initial session receive window (client to server). Default is 1048576. | -| `org.apache.felix.jetty.alpn.protocols` | The ALPN protocols to consider. Default is h2, http/1.1. | -| `org.apache.felix.jetty.alpn.defaultProtocol` | The default protocol when negotiation fails. Default is http/1.1. | -| `org.apache.felix.jakarta.websocket.enable` | Enables Jakarta websocket support. Default is false. | -| `org.apache.felix.jetty.websocket.enable` | Enables Jetty websocket support. Default is false. | -| `org.apache.felix.http.jetty.threadpool.max` | The maximum number of threads in the Jetty thread pool. Default is unlimited. Works for both platform threads and virtual threads (Jetty 12 only). | -| `org.apache.felix.http.jetty.virtualthreads.enable` | Enables using virtual threads in Jetty 12 (JDK 21 required). Default is false. When enabled, `org.apache.felix.http.jetty.threadpool.max` is used for a bounded virtual thread pool. | +| `org.apache.felix.proxy.load.balancer.connection.enable` | Set this to `true` when running Felix HTTP behind a (offloading) proxy or load balancer which rewrites the requests. The default is `false`. | +| `org.apache.felix.http.runtime.init.` | Properties starting with this prefix are added as service registration properties to the HttpServiceRuntime service. The prefix is removed for the property name. | +| `org.apache.felix.jetty.gziphandler.enable` | Whether the server should use a server-wide gzip handler. Default is false. | +| `org.apache.felix.jetty.gzip.minGzipSize` | The minimum response size to trigger dynamic compression. Default is GzipHandler.DEFAULT_MIN_GZIP_SIZE. | +| `org.apache.felix.jetty.gzip.inflateBufferSize` | The size in bytes of the buffer to inflate compressed request, or <= 0 for no inflation. Default is -1. | +| `org.apache.felix.jetty.gzip.syncFlush` | True if Deflater#SYNC_FLUSH should be used, else Deflater#NO_FLUSH will be used. Default is false. | +| `org.apache.felix.jetty.gzip.includedMethods` | The additional http methods to include in compression. Default is none. | +| `org.apache.felix.jetty.gzip.excludedMethods` | The additional http methods to exclude in compression. Default is none. | +| `org.apache.felix.jetty.gzip.includedPaths` | The additional path specs to include. Inclusion takes precedence over exclusion. Default is none. | +| `org.apache.felix.jetty.gzip.excludedPaths` | The additional path specs to exclude. Inclusion takes precedence over exclusion. Default is none. | +| `org.apache.felix.jetty.gzip.includedMimeTypes` | The included mime types. Inclusion takes precedence over exclusion. Default is none. | +| `org.apache.felix.jetty.gzip.excludedMimeTypes` | The excluded mime types. Inclusion takes precedence over exclusion. Default is none. | +| `org.apache.felix.http2.enable` | Whether to enable HTTP/2. Default is false. | +| `org.apache.felix.jetty.http2.maxConcurrentStreams` | The max number of concurrent streams per connection. Default is 128. | +| `org.apache.felix.jetty.http2.initialStreamRecvWindow` | The initial stream receive window (client to server). Default is 524288. | +| `org.apache.felix.jetty.http2.initialSessionRecvWindow` | The initial session receive window (client to server). Default is 1048576. | +| `org.apache.felix.jetty.alpn.protocols` | The ALPN protocols to consider. Default is h2, http/1.1. | +| `org.apache.felix.jetty.alpn.defaultProtocol` | The default protocol when negotiation fails. Default is http/1.1. | +| `org.apache.felix.jakarta.websocket.enable` | Enables Jakarta websocket support. Default is false. | +| `org.apache.felix.jetty.websocket.enable` | Enables Jetty websocket support. Default is false. | +| `org.apache.felix.http.jetty.threadpool.max` | The maximum number of threads in the Jetty thread pool. Default is unlimited. Works for both platform threads and virtual threads (Jetty 12 only). | +| `org.apache.felix.http.jetty.virtualthreads.enable` | Enables using virtual threads in Jetty 12 (JDK 21 required). Default is false. When enabled, `org.apache.felix.http.jetty.threadpool.max` is used for a bounded virtual thread pool. | ### Multiple Servers diff --git a/http/base/pom.xml b/http/base/pom.xml index db521cf823..55803c791d 100644 --- a/http/base/pom.xml +++ b/http/base/pom.xml @@ -144,7 +144,7 @@ 1.0.0 provided - + junit junit @@ -154,7 +154,7 @@ org.mockito mockito-core - 5.7.0 + 5.17.0 test diff --git a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java index 817ec97ad8..3c6b13eff0 100644 --- a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java +++ b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java @@ -203,11 +203,23 @@ public ObjectClassDefinition getObjectClassDefinition( String id, String locale bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_RESPONSE_BUFFER_SIZE))); adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_MAX_FORM_SIZE, - "Maximum Form Size", + "Maximum Form Size in bytes", "Size of Body for submitted form content. Default is 200KB.", 204800, bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_MAX_FORM_SIZE))); + adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_REQUEST_SIZE_LIMIT, + "Maximum request size in bytes", + "Maximum size of the request body in bytes. Default is unlimited.", + 204800, + bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_REQUEST_SIZE_LIMIT))); + + adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_RESPONSE_SIZE_LIMIT, + "Maximum response size in bytes", + "Maximum size of the response body in bytes. Default is unlimited.", + 204800, + bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_RESPONSE_SIZE_LIMIT))); + adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_HTTP_PATH_EXCLUSIONS, "Path Exclusions", "Contains a list of context path prefixes. If a Web Application Bundle is started with a context path matching any " + diff --git a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java index a04eb85a60..c2961631b7 100644 --- a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java +++ b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java @@ -106,9 +106,15 @@ public final class JettyConfig /** Felix specific property to configure the request buffer size. Default is 24KB */ public static final String FELIX_JETTY_RESPONSE_BUFFER_SIZE = "org.apache.felix.http.jetty.responseBufferSize"; - /** Felix specific property to configure the max form size. Default is 200KB */ + /** Felix specific property to configure the max form size. Default is 200KB. */ public static final String FELIX_JETTY_MAX_FORM_SIZE = "org.apache.felix.http.jetty.maxFormSize"; + /** Felix specific property to configure the request size limit. Default is unlimited. See https://jetty.org/docs/jetty/12/programming-guide/server/http.html#handler-use-size-limit */ + public static final String FELIX_JETTY_REQUEST_SIZE_LIMIT = "org.apache.felix.http.jetty.requestSizeLimit"; + + /** Felix specific property to configure the response size limit. Default is unlimited. See https://jetty.org/docs/jetty/12/programming-guide/server/http.html#handler-use-size-limit */ + public static final String FELIX_JETTY_RESPONSE_SIZE_LIMIT = "org.apache.felix.http.jetty.responseSizeLimit"; + /** Felix specific property to enable Jetty MBeans. Valid values are "true", "false". Default is false */ public static final String FELIX_HTTP_MBEANS = "org.apache.felix.http.mbeans"; @@ -493,6 +499,16 @@ public int getMaxFormSize() return getIntProperty(FELIX_JETTY_MAX_FORM_SIZE, 200 * 1024); } + public int getRequestSizeLimit() + { + return getIntProperty(FELIX_JETTY_REQUEST_SIZE_LIMIT, -1); + } + + public int getResponseSizeLimit() + { + return getIntProperty(FELIX_JETTY_RESPONSE_SIZE_LIMIT, -1); + } + /** * Returns the configured session timeout in minutes or zero if not * configured. diff --git a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java index efe4f8fb83..047f4594ec 100644 --- a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java +++ b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java @@ -56,6 +56,7 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.server.handler.SizeLimitHandler; import org.eclipse.jetty.server.handler.StatisticsHandler; import org.eclipse.jetty.server.handler.gzip.GzipHandler; import org.eclipse.jetty.session.HouseKeeper; @@ -324,6 +325,14 @@ private void initializeJetty() throws Exception context.addServlet(holder, "/*"); context.setMaxFormContentSize(this.config.getMaxFormSize()); + int requestSizeLimit = this.config.getRequestSizeLimit(); + int responseSizeLimit = this.config.getResponseSizeLimit(); + if (requestSizeLimit > -1 || responseSizeLimit > -1) { + // Use SizeLimitHandler to limit the size of the request body and response + // -1 is unlimited + context.setHandler(new SizeLimitHandler(requestSizeLimit, responseSizeLimit)); + } + if (this.config.isRegisterMBeans()) { this.mbeanServerTracker = new MBeanServerTracker(this.context, this.server); diff --git a/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettyMaxFormSizeIT.java b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettyMaxFormSizeIT.java index f04f74f822..a0e71667ff 100644 --- a/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettyMaxFormSizeIT.java +++ b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettyMaxFormSizeIT.java @@ -114,18 +114,18 @@ public void testFormSizeLimit() throws Exception { formFieldsLimitExceeded.add(new Fields.Field("key", "valueoverlimit")); // over limit of 10 bytes ContentResponse responseExceeded = httpClient.FORM(uri, formFieldsLimitExceeded); - // HTTP 500 thrown, because req.getParameter("key") throws an IOEx + // HTTP 500 thrown, because req.getParameter("key") throws an IOException assertEquals(500, responseExceeded.getStatus()); httpClient.close(); } - static final class HelloWorldServlet extends HttpServlet { - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - req.getParameter("key"); // this triggers the maxFormSize check - resp.setStatus(200); - resp.getWriter().write("OK"); - } + static final class HelloWorldServlet extends HttpServlet { + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + req.getParameter("key"); // this triggers the maxFormSize check + resp.setStatus(200); + resp.getWriter().write("OK"); + } } -} +} \ No newline at end of file diff --git a/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettySizeLimitHandlerIT.java b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettySizeLimitHandlerIT.java new file mode 100644 index 0000000000..179726bf9b --- /dev/null +++ b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettySizeLimitHandlerIT.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.felix.http.jetty.it; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.ops4j.pax.exam.CoreOptions.mavenBundle; +import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration; + +import java.io.IOException; +import java.net.URI; +import java.util.Hashtable; +import java.util.Map; + +import javax.inject.Inject; +import jakarta.servlet.Servlet; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.ContentResponse; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP; +import org.eclipse.jetty.util.Fields; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.Option; +import org.ops4j.pax.exam.junit.PaxExam; +import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy; +import org.ops4j.pax.exam.spi.reactors.PerClass; +import org.osgi.framework.BundleContext; +import org.osgi.service.http.HttpService; +import org.osgi.service.servlet.whiteboard.HttpWhiteboardConstants; + +@RunWith(PaxExam.class) +@ExamReactorStrategy(PerClass.class) +public class JettySizeLimitHandlerIT extends AbstractJettyTestSupport { + private static final int LIMIT_IN_BYTES = 10; + + @Inject + protected BundleContext bundleContext; + + @Override + protected Option[] additionalOptions() throws IOException { + String jettyVersion = System.getProperty("jetty.version", JETTY_VERSION); + return new Option[] { + spifly(), + + // bundles for the server side + mavenBundle().groupId("org.eclipse.jetty.ee10").artifactId("jetty-ee10-webapp").version(jettyVersion), + mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-ee").version(jettyVersion), + mavenBundle().groupId("org.eclipse.jetty.ee10").artifactId("jetty-ee10-servlet").version(jettyVersion), + mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-xml").version(jettyVersion), + + // additional bundles for the client side + mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-client").version(jettyVersion), + mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-client").version(jettyVersion) + }; + } + + @Override + protected Option felixHttpConfig(int httpPort) { + return newConfiguration("org.apache.felix.http") + .put("org.osgi.service.http.port", httpPort) + .put("org.apache.felix.http.jetty.requestSizeLimit", LIMIT_IN_BYTES) // 10 bytes limit for the request + .put("org.apache.felix.http.jetty.responseSizeLimit", LIMIT_IN_BYTES) // 10 bytes limit for the response + .asOption(); + } + + @Before + public void setup(){ + assertNotNull(bundleContext); + bundleContext.registerService(Servlet.class, new HelloWorldServletWithinLimit(), new Hashtable<>(Map.of( + HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/withinlimit/*" + ))); + bundleContext.registerService(Servlet.class, new HelloWorldServletExceedingLimit(), new Hashtable<>(Map.of( + HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/exceedinglimit/*" + ))); + } + + + @Test + public void testRequestResponseLimits() throws Exception { + HttpClientTransportOverHTTP transport = new HttpClientTransportOverHTTP(); + HttpClient httpClient = new HttpClient(transport); + httpClient.start(); + + Object value = bundleContext.getServiceReference(HttpService.class).getProperty("org.osgi.service.http.port"); + int httpPort = Integer.parseInt((String) value); + + Fields formFields = new Fields(); + formFields.add(new Fields.Field("key", "value")); // under 10 bytes + ContentResponse responseWithinLimit = httpClient.FORM(new URI(String.format("http://localhost:%d/withinlimit/a", httpPort)), formFields); + + // Request limit ok, response limit ok + assertEquals(200, responseWithinLimit.getStatus()); + assertEquals("OK", responseWithinLimit.getContentAsString()); + + // Request limit ok, response limit exceeded + // org.eclipse.jetty.http.HttpException$RuntimeException: 500: Response body is too large: 17>10 + ContentResponse responseExceedingLimit = httpClient.FORM(new URI(String.format("http://localhost:%d/exceedinglimit/a", httpPort)), formFields); + assertEquals(500, responseExceedingLimit.getStatus()); + + Fields formFieldsLimitExceeded = new Fields(); + formFieldsLimitExceeded.add(new Fields.Field("key", "valueoverlimit")); // over limit of 10 bytes + ContentResponse responseExceeded = httpClient.FORM(new URI(String.format("http://localhost:%d/withinlimit/a", httpPort)), formFieldsLimitExceeded); + + // Request limit exceeded, HTTP 413 directly from Jetty + assertEquals(413, responseExceeded.getStatus()); + + httpClient.close(); + } + + static final class HelloWorldServletWithinLimit extends HttpServlet { + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setStatus(200); + resp.getWriter().write("OK"); + } + } + + static final class HelloWorldServletExceedingLimit extends HttpServlet { + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setStatus(200); + resp.getWriter().write("responseoverlimit"); + } + } +}