Skip to content

Commit

Permalink
Added dynamic configuration property for maxHeaderSize (#370)
Browse files Browse the repository at this point in the history
* Added dynamic configuration property for maxHeaderSize

* Add test
  • Loading branch information
hamadodene authored Aug 22, 2022
1 parent c4f8f98 commit acc55f6
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ void reloadConfiguration(RuntimeServerConfiguration newConfiguration) throws Int
LOG.log(Level.INFO, "listener: {0} is to be shut down", key);
listenersToStop.add(key);
} else if (!newConfigurationForListener.equals(actualListenerConfig)
|| newConfiguration.getResponseCompressionThreshold() != currentConfiguration.getResponseCompressionThreshold()) {
|| newConfiguration.getResponseCompressionThreshold() != currentConfiguration.getResponseCompressionThreshold()
|| newConfiguration.getMaxHeaderSize() != currentConfiguration.getMaxHeaderSize()) {
LOG.log(Level.INFO, "listener: {0} is to be restarted", key);
listenersToRestart.add(key);
}
Expand Down Expand Up @@ -253,6 +254,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) {
CURRENT_CONNECTED_CLIENTS_GAUGE.inc();
conn.channel().closeFuture().addListener(e -> CURRENT_CONNECTED_CLIENTS_GAUGE.dec());
})
.httpRequestDecoder(option -> option.maxHeaderSize(currentConfiguration.getMaxHeaderSize()))
.handle((request, response) -> { // Custom request-response handling
if(CarapaceLogger.isLoggingDebugEnabled()) {
CarapaceLogger.debug("Receive request " + request.uri()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,11 @@ public class ProxyRequestsManager {
private final Map<String, HttpClient> forwardersPool = new ConcurrentHashMap<>(); // endpoint_connectionpool -> forwarder to use
private final ConnectionsManager connectionsManager = new ConnectionsManager();

private RuntimeServerConfiguration currentConfiguration;

public ProxyRequestsManager(HttpProxyServer parent) {
this.parent = parent;
this.currentConfiguration = parent.getCurrentConfiguration();
}

public EndpointStats getEndpointStats(EndpointKey key) {
Expand Down Expand Up @@ -339,6 +342,7 @@ public Publisher<Void> forward(ProxyRequest request, boolean cache) {
.option(Epoll.isAvailable()
? EpollChannelOption.TCP_KEEPCNT
: NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPCOUNT), connectionConfig.getKeepaliveCount())
.httpResponseDecoder(option -> option.maxHeaderSize(currentConfiguration.getMaxHeaderSize()))
.doOnRequest((req, conn) -> {
if (CarapaceLogger.isLoggingDebugEnabled()) {
CarapaceLogger.debug("Start sending request for "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
*/
package org.carapaceproxy.core;

import java.io.Console;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
Expand All @@ -28,6 +27,7 @@
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.carapaceproxy.configstore.ConfigurationStore;
import static org.carapaceproxy.server.certificates.DynamicCertificatesManager.DEFAULT_KEYPAIRS_SIZE;
import org.carapaceproxy.server.config.ConfigurationNotValidException;
Expand Down Expand Up @@ -102,6 +102,7 @@ public class RuntimeServerConfiguration {
private String sslTrustStoreFile;
private String sslTrustStorePassword;
private boolean ocspEnabled = false;
private int maxHeaderSize = 8_192; //bytes; default 8kb

public RuntimeServerConfiguration() {
defaultConnectionPool = new ConnectionPoolConfiguration(
Expand Down Expand Up @@ -223,6 +224,12 @@ public void configure(ConfigurationStore properties) throws ConfigurationNotVali

ocspEnabled = properties.getBoolean("ocsp.enabled", ocspEnabled);
LOG.log(Level.INFO, "ocsp.enabled={0}", ocspEnabled);

maxHeaderSize = properties.getInt("carapace.maxheadersize", maxHeaderSize);
if (this.maxHeaderSize <= 0) {
throw new ConfigurationNotValidException("Invalid value '" + this.maxHeaderSize + "' for carapace.maxheadersize");
}
LOG.log(Level.INFO, "carapace.maxheadersize={0}", maxHeaderSize);
}

private void configureCertificates(ConfigurationStore properties) throws ConfigurationNotValidException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package org.carapaceproxy.core;

import com.github.tomakehurst.wiremock.junit.WireMockRule;
import org.carapaceproxy.api.UseAdminServer;
import org.carapaceproxy.utils.TestUtils;
import org.junit.Rule;
import org.junit.Test;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Properties;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.junit.Assert.assertEquals;

public class MaxHeaderSizeTest extends UseAdminServer {

@Rule
public WireMockRule wireMockRule = new WireMockRule(0);

private Properties config;

@Test
public void test() throws Exception {

stubFor(get(urlEqualTo("/index.html"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "text/html")
.withHeader("Content-Length", "it <b>works</b> !!".length() + "")
.withBody("it <b>works</b> !!")));

config = new Properties(HTTP_ADMIN_SERVER_CONFIG);
startServer(config);

// Default certificate
String defaultCertificate = TestUtils.deployResource("ia.p12", tmpDir.getRoot());
config.put("certificate.1.hostname", "*");
config.put("certificate.1.file", defaultCertificate);
config.put("certificate.1.password", "changeit");

// Listeners
config.put("listener.1.host", "localhost");
config.put("listener.1.port", "8086");
config.put("listener.1.enabled", "true");
config.put("listener.1.defaultcertificate", "*");

// Backends
config.put("backend.1.id", "localhost");
config.put("backend.1.enabled", "true");
config.put("backend.1.host", "localhost");
config.put("backend.1.port", wireMockRule.port() + "");

config.put("backend.2.id", "localhost2");
config.put("backend.2.enabled", "true");
config.put("backend.2.host", "localhost2");
config.put("backend.2.port", wireMockRule.port() + "");

// Default director
config.put("director.1.id", "*");
config.put("director.1.backends", "localhost");
config.put("director.1.enabled", "true");

// Default route
config.put("route.100.id", "default");
config.put("route.100.enabled", "true");
config.put("route.100.match", "all");
config.put("route.100.action", "proxy-all");

changeDynamicConfiguration(config);


HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.build();

HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create("http://localhost:" + 8086 +"/index.html"))
.setHeader("custom-header", "test")
.setHeader("token", "eyJhbGciOiJIUzI1NiJ9.eyJSb")
.setHeader("token1", "eyJhbGciOiJIUzI1NiJ9.eyJSb")
.setHeader("token2", "eyJhbGciOiJIUzI1NiJ9.eyJSb")
.setHeader("token3", "eyJhbGciOiJIUzI1NiJ9.eyJSb")
.build();

HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

assertEquals(200, response.statusCode());

config.put("carapace.maxheadersize", "1");
changeDynamicConfiguration(config);

HttpClient httpClient2 = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.build();

HttpResponse<String> response2 = httpClient2.send(request, HttpResponse.BodyHandlers.ofString());

assertEquals(413, response2.statusCode());
}
}

0 comments on commit acc55f6

Please sign in to comment.