Skip to content

Commit

Permalink
Added allowRequestsToOriginServer option to Bootstrap to allow handli…
Browse files Browse the repository at this point in the history
…ng origin-form requests
  • Loading branch information
jekh committed Feb 7, 2017
1 parent dfdb375 commit 4ab9829
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 8 deletions.
10 changes: 10 additions & 0 deletions src/main/java/org/littleshoot/proxy/HttpProxyServerBootstrap.java
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,16 @@ HttpProxyServerBootstrap withConnectTimeout(

HttpProxyServerBootstrap withMaxChunkSize(int maxChunkSize);

/**
* When true, the proxy will accept requests that appear to be directed at an origin server (i.e. the URI in the HTTP
* request will contain an origin-form, rather than an absolute-form, as specified in RFC 7230, section 5.3).
* This is useful when the proxy is acting as a gateway/reverse proxy. <b>Note:</b> This feature should not be
* enabled when running as a forward proxy; doing so may cause an infinite loop if the client requests the URI of the proxy.
*
* @param allowRequestToOriginServer when true, the proxy will accept origin-form HTTP requests
*/
HttpProxyServerBootstrap withAllowRequestToOriginServer(boolean allowRequestToOriginServer);

/**
* Sets the alias to use when adding Via headers to incoming and outgoing HTTP messages. The alias may be any
* pseudonym, or if not specified, defaults to the hostname of the local machine. See RFC 7230, section 5.7.1.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,9 @@ private ConnectionState doReadHTTPInitial(HttpRequest httpRequest) {
}
}

// short-circuit requests that treat the proxy as the "origin" server, to avoid infinite loops
if (isRequestToOriginServer(httpRequest)) {
// if origin-form requests are not explicitly enabled, short-circuit requests that treat the proxy as the
// origin server, to avoid infinite loops
if (!proxyServer.isAllowRequestsToOriginServer() && isRequestToOriginServer(httpRequest)) {
boolean keepAlive = writeBadRequest(httpRequest);
if (keepAlive) {
return AWAITING_INITIAL;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ public class DefaultHttpProxyServer implements HttpProxyServer {
private final int maxInitialLineLength;
private final int maxHeaderSize;
private final int maxChunkSize;
private final boolean allowRequestsToOriginServer;

/**
* The alias or pseudonym for this proxy, used when adding the Via header.
Expand Down Expand Up @@ -227,6 +228,8 @@ public static HttpProxyServerBootstrap bootstrapFromFile(String path) {
* @param maxInitialLineLength
* @param maxHeaderSize
* @param maxChunkSize
* @param allowRequestsToOriginServer
* when true, allow the proxy to handle requests that contain an origin-form URI, as defined in RFC 7230 5.3.1
*/
private DefaultHttpProxyServer(ServerGroup serverGroup,
TransportProtocol transportProtocol,
Expand All @@ -248,7 +251,8 @@ private DefaultHttpProxyServer(ServerGroup serverGroup,
String proxyAlias,
int maxInitialLineLength,
int maxHeaderSize,
int maxChunkSize) {
int maxChunkSize,
boolean allowRequestsToOriginServer) {
this.serverGroup = serverGroup;
this.transportProtocol = transportProtocol;
this.requestedAddress = requestedAddress;
Expand Down Expand Up @@ -284,8 +288,9 @@ private DefaultHttpProxyServer(ServerGroup serverGroup,
this.proxyAlias = proxyAlias;
}
this.maxInitialLineLength = maxInitialLineLength;
this.maxHeaderSize = maxHeaderSize;
this.maxChunkSize = maxChunkSize;
this.maxHeaderSize = maxHeaderSize;
this.maxChunkSize = maxChunkSize;
this.allowRequestsToOriginServer = allowRequestsToOriginServer;
}

/**
Expand Down Expand Up @@ -375,6 +380,10 @@ public int getMaxChunkSize() {
return maxChunkSize;
}

public boolean isAllowRequestsToOriginServer() {
return allowRequestsToOriginServer;
}

@Override
public HttpProxyServerBootstrap clone() {
return new DefaultHttpProxyServerBootstrap(serverGroup,
Expand All @@ -398,7 +407,8 @@ public HttpProxyServerBootstrap clone() {
proxyAlias,
maxInitialLineLength,
maxHeaderSize,
maxChunkSize);
maxChunkSize,
allowRequestsToOriginServer);
}

@Override
Expand Down Expand Up @@ -613,6 +623,7 @@ private static class DefaultHttpProxyServerBootstrap implements HttpProxyServerB
private int maxInitialLineLength = MAX_INITIAL_LINE_LENGTH_DEFAULT;
private int maxHeaderSize = MAX_HEADER_SIZE_DEFAULT;
private int maxChunkSize = MAX_CHUNK_SIZE_DEFAULT;
private boolean allowRequestToOriginServer = false;

private DefaultHttpProxyServerBootstrap() {
}
Expand All @@ -636,7 +647,8 @@ private DefaultHttpProxyServerBootstrap(
String proxyAlias,
int maxInitialLineLength,
int maxHeaderSize,
int maxChunkSize) {
int maxChunkSize,
boolean allowRequestToOriginServer) {
this.serverGroup = serverGroup;
this.transportProtocol = transportProtocol;
this.requestedAddress = requestedAddress;
Expand All @@ -661,6 +673,7 @@ private DefaultHttpProxyServerBootstrap(
this.maxInitialLineLength = maxInitialLineLength;
this.maxHeaderSize = maxHeaderSize;
this.maxChunkSize = maxChunkSize;
this.allowRequestToOriginServer = allowRequestToOriginServer;
}

private DefaultHttpProxyServerBootstrap(Properties props) {
Expand Down Expand Up @@ -854,6 +867,12 @@ public HttpProxyServerBootstrap withMaxChunkSize(int maxChunkSize){
return this;
}

@Override
public HttpProxyServerBootstrap withAllowRequestToOriginServer(boolean allowRequestToOriginServer) {
this.allowRequestToOriginServer = allowRequestToOriginServer;
return this;
}

@Override
public HttpProxyServer start() {
return build().start();
Expand Down Expand Up @@ -884,7 +903,8 @@ transportProtocol, determineListenAddress(),
filtersSource, transparent,
idleConnectionTimeout, activityTrackers, connectTimeout,
serverResolver, readThrottleBytesPerSecond, writeThrottleBytesPerSecond,
localAddress, proxyAlias, maxInitialLineLength, maxHeaderSize, maxChunkSize);
localAddress, proxyAlias, maxInitialLineLength, maxHeaderSize, maxChunkSize,
allowRequestToOriginServer);
}

private InetSocketAddress determineListenAddress() {
Expand Down
45 changes: 45 additions & 0 deletions src/test/java/org/littleshoot/proxy/DirectRequestTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.littleshoot.proxy;

import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
Expand All @@ -13,10 +14,12 @@
import org.littleshoot.proxy.test.HttpClientUtil;

import javax.net.ssl.SSLException;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;

/**
* This class tests direct requests to the proxy server, which causes endless
Expand Down Expand Up @@ -97,6 +100,48 @@ public void testHttpsShouldCancelConnection() {
}
}

@Test(timeout = 5000)
public void testAllowRequestToOriginServerWithOverride() {
// verify that the filter is hit twice: first, on the request from the client, without a Via header; and second, when the proxy
// forwards the request to itself
final AtomicBoolean receivedRequestWithoutVia = new AtomicBoolean();

proxyServer = DefaultHttpProxyServer.bootstrap()
.withPort(0)
.withAllowRequestToOriginServer(true)
.withProxyAlias("testAllowRequestToOriginServerWithOverride")
.withFiltersSource(new HttpFiltersSourceAdapter() {
@Override
public HttpFilters filterRequest(HttpRequest originalRequest) {
return new HttpFiltersAdapter(originalRequest) {
@Override
public HttpResponse clientToProxyRequest(HttpObject httpObject) {
if (httpObject instanceof HttpRequest) {
HttpRequest request = (HttpRequest) httpObject;
String viaHeader = request.headers().get(HttpHeaders.Names.VIA);
if (viaHeader != null && viaHeader.contains("testAllowRequestToOriginServerWithOverride")) {
return new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NO_CONTENT);
} else {
receivedRequestWithoutVia.set(true);
}
}
return null;
}
};
}
})
.start();

int proxyPort = proxyServer.getListenAddress().getPort();

org.apache.http.HttpResponse response = HttpClientUtil.performHttpGet("http://localhost:" + proxyPort + "/originrequest", proxyServer);
int statusCode = response.getStatusLine().getStatusCode();

assertEquals("Expected to receive a 204 response from the filter", 204, statusCode);

assertTrue("Expected to receive a request from the client without a Via header", receivedRequestWithoutVia.get());
}

private void startProxyServer() {
proxyServer = DefaultHttpProxyServer.bootstrap()
.withPort(0)
Expand Down

0 comments on commit 4ab9829

Please sign in to comment.