Add max http response header size configuration for tomcat and jetty

See gh-33553
This commit is contained in:
Michael Weidmann
2022-12-20 16:51:33 +01:00
committed by Moritz Halbritter
parent ace31cd5b2
commit 93d46d11e9
15 changed files with 406 additions and 73 deletions

View File

@@ -0,0 +1,26 @@
package smoketest.jetty.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import smoketest.jetty.util.RandomStringUtil;
@Component
public class HttpHeaderService {
@Value("${server.jetty.max-http-response-header-size}")
private int maxHttpResponseHeaderSize;
/**
* generate a random byte array that
* <ol>
* <li>is longer than configured
* <code>server.jetty.max-http-response-header-size</code></li>
* <li>is url encoded by base 64 encode the random value</li>
* </ol>
* @return a base64 encoded string of random bytes
*/
public String getHeaderValue() {
return RandomStringUtil.getRandomBase64EncodedString(maxHttpResponseHeaderSize + 1);
}
}

View File

@@ -0,0 +1,17 @@
package smoketest.jetty.util;
import java.util.Base64;
import java.util.Random;
public class RandomStringUtil {
private RandomStringUtil() {
}
public static String getRandomBase64EncodedString(int length) {
byte[] responseHeader = new byte[length];
new Random().nextBytes(responseHeader);
return Base64.getEncoder().encodeToString(responseHeader);
}
}

View File

@@ -16,7 +16,9 @@
package smoketest.jetty.web;
import jakarta.servlet.http.HttpServletResponse;
import smoketest.jetty.service.HelloWorldService;
import smoketest.jetty.service.HttpHeaderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@@ -29,10 +31,21 @@ public class SampleController {
@Autowired
private HelloWorldService helloWorldService;
@Autowired
private HttpHeaderService httpHeaderService;
@GetMapping("/")
@ResponseBody
public String helloWorld() {
return this.helloWorldService.getHelloMessage();
}
@GetMapping("/max-http-response-header")
@ResponseBody
public String maxHttpResponseHeader(HttpServletResponse response) {
String headerValue = httpHeaderService.getHeaderValue();
response.addHeader("x-max-header", headerValue);
return this.helloWorldService.getHelloMessage();
}
}

View File

@@ -1,3 +1,5 @@
server.compression.enabled: true
server.compression.min-response-size: 1
server.max-http-request-header-size=1000
server.jetty.threads.acceptors=2
server.jetty.max-http-response-header-size=1000

View File

@@ -22,9 +22,13 @@ import java.util.zip.GZIPInputStream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
@@ -32,6 +36,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StreamUtils;
import smoketest.jetty.util.RandomStringUtil;
import static org.assertj.core.api.Assertions.assertThat;
@@ -40,13 +45,19 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Dave Syer
* @author Andy Wilkinson
* @author Florian Storz
* @author Michael Weidmann
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@ExtendWith(OutputCaptureExtension.class)
class SampleJettyApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
@Value("${server.max-http-request-header-size}")
private int maxHttpRequestHeaderSize;
@Test
void testHome() {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/", String.class);
@@ -66,4 +77,22 @@ class SampleJettyApplicationTests {
}
}
@Test
void testMaxHttpResponseHeaderSize(CapturedOutput output) {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/max-http-response-header", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
assertThat(output).contains(
"org.eclipse.jetty.server.HttpChannel : handleException /max-http-response-header org.eclipse.jetty.http.BadMessageException: 500: Response header too large");
}
@Test
void testMaxHttpRequestHeaderSize() {
String headerValue = RandomStringUtil.getRandomBase64EncodedString(maxHttpRequestHeaderSize + 1);
HttpHeaders headers = new HttpHeaders();
headers.add("x-max-request-header", headerValue);
HttpEntity<?> httpEntity = new HttpEntity<>(headers);
ResponseEntity<String> entity = this.restTemplate.exchange("/", HttpMethod.GET, httpEntity, String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE);
}
}

View File

@@ -0,0 +1,26 @@
package smoketest.tomcat.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import smoketest.tomcat.util.RandomStringUtil;
@Component
public class HttpHeaderService {
@Value("${server.tomcat.max-http-response-header-size}")
private int maxHttpResponseHeaderSize;
/**
* generate a random byte array that
* <ol>
* <li>is longer than configured
* <code>server.jetty.max-http-response-header-size</code></li>
* <li>is url encoded by base 64 encode the random value</li>
* </ol>
* @return a base64 encoded string of random bytes
*/
public String getHeaderValue() {
return RandomStringUtil.getRandomBase64EncodedString(maxHttpResponseHeaderSize + 1);
}
}

View File

@@ -0,0 +1,17 @@
package smoketest.tomcat.util;
import java.util.Base64;
import java.util.Random;
public class RandomStringUtil {
private RandomStringUtil() {
}
public static String getRandomBase64EncodedString(int length) {
byte[] responseHeader = new byte[length];
new Random().nextBytes(responseHeader);
return Base64.getEncoder().encodeToString(responseHeader);
}
}

View File

@@ -16,12 +16,14 @@
package smoketest.tomcat.web;
import jakarta.servlet.http.HttpServletResponse;
import smoketest.tomcat.service.HelloWorldService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import smoketest.tomcat.service.HttpHeaderService;
@Controller
public class SampleController {
@@ -29,10 +31,21 @@ public class SampleController {
@Autowired
private HelloWorldService helloWorldService;
@Autowired
private HttpHeaderService httpHeaderService;
@GetMapping("/")
@ResponseBody
public String helloWorld() {
return this.helloWorldService.getHelloMessage();
}
@GetMapping("/max-http-response-header")
@ResponseBody
public String maxHttpResponseHeader(HttpServletResponse response) {
String headerValue = httpHeaderService.getHeaderValue();
response.addHeader("x-max-header", headerValue);
return this.helloWorldService.getHelloMessage();
}
}

View File

@@ -1,3 +1,5 @@
server.compression.enabled: true
server.compression.min-response-size: 1
server.max-http-request-header-size=1000
server.tomcat.connection-timeout=5s
server.tomcat.max-http-response-header-size=1000

View File

@@ -24,9 +24,13 @@ import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
@@ -37,6 +41,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StreamUtils;
import smoketest.tomcat.util.RandomStringUtil;
import static org.assertj.core.api.Assertions.assertThat;
@@ -45,8 +50,11 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Dave Syer
* @author Andy Wilkinson
* @author Florian Storz
* @author Michael Weidmann
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@ExtendWith(OutputCaptureExtension.class)
class SampleTomcatApplicationTests {
@Autowired
@@ -55,6 +63,9 @@ class SampleTomcatApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Value("${server.max-http-request-header-size}")
private int maxHttpRequestHeaderSize;
@Test
void testHome() {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/", String.class);
@@ -83,4 +94,23 @@ class SampleTomcatApplicationTests {
assertThat(timeout).isEqualTo(5000);
}
@Test
void testMaxHttpResponseHeaderSize(CapturedOutput output) {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/max-http-response-header", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
assertThat(output).contains(
"threw exception [Request processing failed: org.apache.coyote.http11.HeadersTooLargeException: An attempt was made to write more data to the response headers than there was room available in the buffer. Increase maxHttpHeaderSize on the connector or write less data into the response headers.]");
}
@Test
void testMaxHttpRequestHeaderSize(CapturedOutput output) {
String headerValue = RandomStringUtil.getRandomBase64EncodedString(maxHttpRequestHeaderSize + 1);
HttpHeaders headers = new HttpHeaders();
headers.add("x-max-request-header", headerValue);
HttpEntity<?> httpEntity = new HttpEntity<>(headers);
ResponseEntity<String> entity = this.restTemplate.exchange("/", HttpMethod.GET, httpEntity, String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
assertThat(output).contains("java.lang.IllegalArgumentException: Request header is too large");
}
}