Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update lib versions #402

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ sudo: false

language: java
jdk:
- oraclejdk7
- oraclejdk8

cache:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[![Build Status](https://travis-ci.org/adamfisk/LittleProxy.png?branch=master)](https://travis-ci.org/adamfisk/LittleProxy)
[![Build Status](https://travis-ci.org/michalsvec/LittleProxy.svg?branch=master)](https://travis-ci.org/michalsvec/LittleProxy)

LittleProxy is a high performance HTTP proxy written in Java atop Trustin Lee's excellent [Netty](http://netty.io) event-based networking library. It's quite stable, performs well, and is easy to integrate into your projects.

One option is to clone LittleProxy and run it from the command line. This is as simple as:

```
$ git clone git://github.com/adamfisk/LittleProxy.git
$ git clone git://github.com/michalsvec/LittleProxy.git
$ cd LittleProxy
$ ./run.bash
```
Expand Down
18 changes: 9 additions & 9 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<github.global.server>github</github.global.server>
<netty.version>4.0.44.Final</netty.version>
<slf4j.version>1.7.24</slf4j.version>
<java.version>1.7</java.version>
<netty.version>4.0.54.Final</netty.version>
<slf4j.version>1.7.25</slf4j.version>
<java.version>1.8</java.version>
</properties>

<organization>
Expand Down Expand Up @@ -177,7 +177,7 @@
<profile>
<id>netty-4.1</id>
<properties>
<netty.version>4.1.8.Final</netty.version>
<netty.version>4.1.19.Final</netty.version>
</properties>
</profile>
</profiles>
Expand All @@ -186,21 +186,21 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
<version>23.6-jre</version>
</dependency>

<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.3.1</version>
<version>1.4</version>
<!-- Only required when running as a standalone jar -->
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
<version>3.7</version>
</dependency>

<dependency>
Expand Down Expand Up @@ -234,7 +234,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.7.12</version>
<version>2.13.0</version>
<scope>test</scope>
</dependency>

Expand Down Expand Up @@ -274,7 +274,7 @@
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
<version>4.5.4</version>
<scope>test</scope>
</dependency>

Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/littleshoot/proxy/HttpFilters.java
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,8 @@ void proxyToServerResolutionSucceeded(String serverHostAndPort,
*/
void proxyToServerConnectionSucceeded(ChannelHandlerContext serverCtx);

/**
* Informs filter that server disconnected while request was still in flight
*/
void proxyToServerDisconnected();
}
4 changes: 4 additions & 0 deletions src/main/java/org/littleshoot/proxy/HttpFiltersAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,8 @@ public void proxyToServerConnectionFailed() {
@Override
public void proxyToServerConnectionSucceeded(ChannelHandlerContext serverCtx) {
}

@Override
public void proxyToServerDisconnected() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,12 @@ public class ClientToProxyConnection extends ProxyConnection<HttpRequest> {
*/
private volatile boolean mitming = false;

private AtomicBoolean authenticated = new AtomicBoolean();
/**
* Tracks if client is expecting data from proxy.
*/
private volatile boolean isRequestInFlight = false;

private final AtomicBoolean authenticated = new AtomicBoolean();

private final GlobalTrafficShapingHandler globalTrafficShapingHandler;

Expand Down Expand Up @@ -221,9 +226,6 @@ protected ConnectionState readHTTPInitial(HttpRequest httpRequest) {
* Note - the "server" could be a chained proxy, not the final endpoint for
* the request.
* </p>
*
* @param httpRequest
* @return
*/
private ConnectionState doReadHTTPInitial(HttpRequest httpRequest) {
// Make a copy of the original request
Expand All @@ -237,6 +239,7 @@ private ConnectionState doReadHTTPInitial(HttpRequest httpRequest) {
} else {
currentFilters = HttpFiltersAdapter.NOOP_FILTER;
}
isRequestInFlight = true;

// Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required
HttpResponse clientToProxyFilterResponse = currentFilters.clientToProxyRequest(httpRequest);
Expand Down Expand Up @@ -285,9 +288,21 @@ private ConnectionState doReadHTTPInitial(HttpRequest httpRequest) {

boolean newConnectionRequired = false;
if (ProxyUtils.isCONNECT(httpRequest)) {
LOG.debug(
"Not reusing existing ProxyToServerConnection because request is a CONNECT for: {}",
serverHostAndPort);
// Connect request on a previously used connection may break the connection later
// when another HTTP server (which had connected before on this connection before it was upgraded to HTTPS) disconnects.
// That may cause disconnection of client2proxy in serverDisconnected()
if(isConnectionUsed()) {
LOG.error(
"Dropping request to {} as it's a CONNECT request on a previously used connection.",
serverHostAndPort
);
writeBadGateway(httpRequest);
return DISCONNECT_REQUESTED;
}

LOG.debug("Not reusing existing ProxyToServerConnection because request is a CONNECT for: {}",
serverHostAndPort
);
newConnectionRequired = true;
} else if (currentServerConnection == null) {
LOG.debug("Didn't find existing ProxyToServerConnection for: {}",
Expand Down Expand Up @@ -360,6 +375,11 @@ private ConnectionState doReadHTTPInitial(HttpRequest httpRequest) {
}
}

private boolean isConnectionUsed()
{
return currentServerConnection != null || !serverConnectionsByHostAndPort.isEmpty();
}

/**
* Returns true if the specified request is a request to an origin server, rather than to a proxy server. If this
* request is being MITM'd, this method always returns false. The format of requests to a proxy server are defined
Expand Down Expand Up @@ -659,11 +679,23 @@ private void resumeReadingIfNecessary() {
protected void serverDisconnected(ProxyToServerConnection serverConnection) {
numberOfCurrentlyConnectedServers.decrementAndGet();

// if server sends content-length larger than content, then client hangs because it waits for next chunk,
// but the proxy2server connection was already closed. In this case it's necessary
// to close client2proxy connection because client would have timed out otherwise
if(isRequestInFlight() && currentServerConnection == serverConnection) {
LOG.warn(String.format("Server disconnected unexpectedly: %s", serverConnection.getServerHostAndPort()), new Exception("Server disconnected unexpectedly"));
writeEmptyBuffer();
disconnect();

currentFilters.proxyToServerDisconnected();
}

// for non-SSL connections, do not disconnect the client from the proxy, even if this was the last server connection.
// this allows clients to continue to use the open connection to the proxy to make future requests. for SSL
// connections, whether we are tunneling or MITMing, we need to disconnect the client because there is always
// exactly one ClientToProxyConnection per ProxyToServerConnection, and vice versa.
if (isTunneling() || isMitming()) {
if (isMitming() || isTunneling()) {
LOG.warn(String.format("Server %s disconnected. Closing client connection", serverConnection.getServerHostAndPort()), new Exception("Server disconnected"));
disconnect();
}
}
Expand All @@ -673,7 +705,7 @@ protected void serverDisconnected(ProxyToServerConnection serverConnection) {
* associated ProxyToServerConnections.
*/
@Override
synchronized protected void becameSaturated() {
protected synchronized void becameSaturated() {
super.becameSaturated();
for (ProxyToServerConnection serverConnection : serverConnectionsByHostAndPort
.values()) {
Expand All @@ -690,7 +722,7 @@ synchronized protected void becameSaturated() {
* associated ProxyToServerConnections.
*/
@Override
synchronized protected void becameWritable() {
protected synchronized void becameWritable() {
super.becameWritable();
for (ProxyToServerConnection serverConnection : serverConnectionsByHostAndPort
.values()) {
Expand All @@ -707,7 +739,7 @@ synchronized protected void becameWritable() {
*
* @param serverConnection
*/
synchronized protected void serverBecameSaturated(
protected synchronized void serverBecameSaturated(
ProxyToServerConnection serverConnection) {
if (serverConnection.isSaturated()) {
LOG.info("Connection to server became saturated, stopping reading");
Expand All @@ -721,7 +753,7 @@ synchronized protected void serverBecameSaturated(
*
* @param serverConnection
*/
synchronized protected void serverBecameWriteable(
protected synchronized void serverBecameWriteable(
ProxyToServerConnection serverConnection) {
boolean anyServersSaturated = false;
for (ProxyToServerConnection otherServerConnection : serverConnectionsByHostAndPort
Expand Down Expand Up @@ -1114,6 +1146,7 @@ private void modifyResponseHeadersToReflectProxying(
HttpResponse httpResponse) {
if (!proxyServer.isTransparent()) {
HttpHeaders headers = httpResponse.headers();
boolean isKeepAlive = HttpHeaders.isKeepAlive(httpResponse);

stripConnectionTokens(headers);
stripHopByHopHeaders(headers);
Expand All @@ -1129,6 +1162,9 @@ private void modifyResponseHeadersToReflectProxying(
if (!headers.contains(HttpHeaders.Names.DATE)) {
HttpHeaders.setDate(httpResponse, new Date());
}
if (isMitming()) {
HttpHeaders.setKeepAlive(httpResponse, isKeepAlive);
}
}
}

Expand Down Expand Up @@ -1267,7 +1303,8 @@ private boolean writeGatewayTimeout(HttpRequest httpRequest) {
*/
private boolean respondWithShortCircuitResponse(HttpResponse httpResponse) {
// we are sending a response to the client, so we are done handling this request
this.currentRequest = null;
currentRequest = null;
isRequestInFlight = false;

HttpResponse filteredResponse = (HttpResponse) currentFilters.proxyToClientResponse(httpResponse);
if (filteredResponse == null) {
Expand Down Expand Up @@ -1338,6 +1375,7 @@ private String identifyHostAndPort(HttpRequest httpRequest) {
*/
private void writeEmptyBuffer() {
write(Unpooled.EMPTY_BUFFER);
isRequestInFlight = false;
}

public boolean isMitming() {
Expand Down Expand Up @@ -1452,4 +1490,11 @@ private FlowContext flowContext() {
}
}

/**
* @return true if client is expecting data from proxy
*/
public boolean isRequestInFlight()
{
return isRequestInFlight;
}
}
10 changes: 9 additions & 1 deletion src/test/java/org/littleshoot/proxy/HttpFilterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ public void proxyToServerRequestSent() {
proxyToServerRequestSentNanos.set(requestCount.get(), now());
}

@Override
public HttpObject serverToProxyResponse(
HttpObject httpObject) {
if (originalRequest.getUri().contains("testing3")) {
Expand Down Expand Up @@ -223,7 +224,8 @@ public void serverToProxyResponseReceived() {
serverToProxyResponseReceivedNanos.set(requestCount.get(), now());
}

public HttpObject proxyToClientResponse(
@Override
public HttpObject proxyToClientResponse(
HttpObject httpObject) {
if (originalRequest.getUri().contains("testing4")) {
return new DefaultFullHttpResponse(
Expand Down Expand Up @@ -827,6 +829,7 @@ private static class HttpFiltersMethodInvokedAdapter implements HttpFilters {
private final AtomicBoolean proxyToServerResolutionFailed = new AtomicBoolean(false);
private final AtomicBoolean proxyToServerResolutionSucceeded = new AtomicBoolean(false);
private final AtomicBoolean proxyToServerConnectionSSLHandshakeStarted = new AtomicBoolean(false);
private final AtomicBoolean proxyToServerDisconnected = new AtomicBoolean(false);
private final AtomicBoolean serverToProxyResponseTimedOut = new AtomicBoolean(false);

public boolean isProxyToServerConnectionFailedInvoked() {
Expand Down Expand Up @@ -907,6 +910,11 @@ public void proxyToServerConnectionSucceeded(ChannelHandlerContext serverCtx) {
proxyToServerConnectionSucceeded.set(true);
}

@Override
public void proxyToServerDisconnected() {
proxyToServerDisconnected.set(true);
}

@Override
public HttpResponse clientToProxyRequest(HttpObject httpObject) {
clientToProxyRequest.set(true);
Expand Down