Merged from master.
This commit is contained in:
commit
5624a5721e
|
@ -5,7 +5,6 @@
|
|||
<version>8.1.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.eclipse.jetty.aggregate</groupId>
|
||||
<artifactId>jetty-all-server</artifactId>
|
||||
<name>Jetty :: Aggregate :: All Server</name>
|
||||
<properties>
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
<version>8.1.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.eclipse.jetty.aggregate</groupId>
|
||||
<artifactId>jetty-client</artifactId>
|
||||
<name>Jetty :: Aggregate :: HTTP Client</name>
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>jetty-project</artifactId>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
|
|
|
@ -237,6 +237,9 @@ public class SslConnection extends AbstractConnection implements AsyncConnection
|
|||
/* ------------------------------------------------------------ */
|
||||
public void onClose()
|
||||
{
|
||||
Connection connection = _sslEndPoint.getConnection();
|
||||
if (connection != null && connection != this)
|
||||
connection.onClose();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -408,7 +411,7 @@ public class SslConnection extends AbstractConnection implements AsyncConnection
|
|||
}
|
||||
|
||||
// If we are reading into the temp buffer and it has some content, then we should be dispatched.
|
||||
if (toFill==_unwrapBuf && _unwrapBuf.hasContent())
|
||||
if (toFill==_unwrapBuf && _unwrapBuf.hasContent() && !_connection.isSuspended())
|
||||
_aEndp.asyncDispatch();
|
||||
}
|
||||
finally
|
||||
|
@ -550,7 +553,7 @@ public class SslConnection extends AbstractConnection implements AsyncConnection
|
|||
break;
|
||||
|
||||
case BUFFER_OVERFLOW:
|
||||
_logger.debug("{} unwrap {} {}->{}",_session,result.getStatus(),_inbound.toDetailString(),buffer.toDetailString());
|
||||
if (_logger.isDebugEnabled()) _logger.debug("{} unwrap {} {}->{}",_session,result.getStatus(),_inbound.toDetailString(),buffer.toDetailString());
|
||||
break;
|
||||
|
||||
case OK:
|
||||
|
|
|
@ -24,12 +24,15 @@ import org.eclipse.jetty.server.Request;
|
|||
* Rewrite the URI by matching with a regular expression.
|
||||
* The replacement string may use $n" to replace the nth capture group.
|
||||
* If the replacement string contains ? character, then it is split into a path
|
||||
* and query string component. The returned target contains only the path.
|
||||
* and query string component. The replacement query string may also contain $Q, which
|
||||
* is replaced with the original query string.
|
||||
* The returned target contains only the path.
|
||||
*/
|
||||
public class RewriteRegexRule extends RegexRule implements Rule.ApplyURI
|
||||
{
|
||||
private String _replacement;
|
||||
private String _query;
|
||||
private boolean _queryGroup;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public RewriteRegexRule()
|
||||
|
@ -49,6 +52,7 @@ public class RewriteRegexRule extends RegexRule implements Rule.ApplyURI
|
|||
String[] split=replacement.split("\\?",2);
|
||||
_replacement = split[0];
|
||||
_query=split.length==2?split[1]:null;
|
||||
_queryGroup=_query!=null && _query.contains("$Q");
|
||||
}
|
||||
|
||||
|
||||
|
@ -73,7 +77,12 @@ public class RewriteRegexRule extends RegexRule implements Rule.ApplyURI
|
|||
}
|
||||
|
||||
if (query!=null)
|
||||
{
|
||||
if (_queryGroup)
|
||||
query=query.replace("$Q",request.getQueryString()==null?"":request.getQueryString());
|
||||
request.setAttribute("org.eclipse.jetty.rewrite.handler.RewriteRegexRule.Q",query);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
|
@ -84,7 +93,7 @@ public class RewriteRegexRule extends RegexRule implements Rule.ApplyURI
|
|||
if (_query!=null)
|
||||
{
|
||||
String query=(String)request.getAttribute("org.eclipse.jetty.rewrite.handler.RewriteRegexRule.Q");
|
||||
if (request.getQueryString()==null)
|
||||
if (_queryGroup||request.getQueryString()==null)
|
||||
request.setQueryString(query);
|
||||
else
|
||||
request.setQueryString(request.getQueryString()+"&"+query);
|
||||
|
|
|
@ -197,28 +197,25 @@ public class RuleContainer extends Rule
|
|||
if (applied!=null)
|
||||
{
|
||||
LOG.debug("applied {}",rule);
|
||||
if (!target.equals(applied))
|
||||
{
|
||||
LOG.debug("rewrote {} to {}",target,applied);
|
||||
if (!original_set)
|
||||
{
|
||||
original_set=true;
|
||||
request.setAttribute(_originalPathAttribute, target);
|
||||
}
|
||||
LOG.debug("rewrote {} to {}",target,applied);
|
||||
if (!original_set)
|
||||
{
|
||||
original_set=true;
|
||||
request.setAttribute(_originalPathAttribute, target);
|
||||
}
|
||||
|
||||
if (_rewriteRequestURI)
|
||||
{
|
||||
if (rule instanceof Rule.ApplyURI && !target.equals(request.getRequestURI()))
|
||||
((Rule.ApplyURI)rule).applyURI((Request)request, target, applied);
|
||||
else
|
||||
((Request)request).setRequestURI(applied);
|
||||
}
|
||||
|
||||
if (_rewritePathInfo)
|
||||
((Request)request).setPathInfo(applied);
|
||||
|
||||
target=applied;
|
||||
if (_rewriteRequestURI)
|
||||
{
|
||||
if (rule instanceof Rule.ApplyURI)
|
||||
((Rule.ApplyURI)rule).applyURI((Request)request, target, applied);
|
||||
else
|
||||
((Request)request).setRequestURI(applied);
|
||||
}
|
||||
|
||||
if (_rewritePathInfo)
|
||||
((Request)request).setPathInfo(applied);
|
||||
|
||||
target=applied;
|
||||
|
||||
if (rule.isHandling())
|
||||
{
|
||||
|
|
|
@ -23,13 +23,21 @@ public class RewriteRegexRuleTest extends AbstractRuleTestCase
|
|||
{
|
||||
private String[][] _tests=
|
||||
{
|
||||
{"/foo/bar",".*","/replace","/replace",null},
|
||||
{"/foo/bar","/xxx.*","/replace",null,null},
|
||||
{"/foo/bar","/(.*)/(.*)","/$2/$1/xxx","/bar/foo/xxx",null},
|
||||
{"/foo/bar","/(foo)/(.*)(bar)","/$3/$1/xxx$2","/bar/foo/xxx",null},
|
||||
{"/foo/$bar",".*","/$replace","/$replace",null},
|
||||
{"/foo/$bar","/foo/(.*)","/$1/replace","/$bar/replace",null},
|
||||
{"/foo/bar/info","/foo/(NotHere)?([^/]*)/(.*)","/$3/other?p1=$2","/info/other","p1=bar"},
|
||||
{"/foo/bar",null,".*","/replace","/replace",null},
|
||||
{"/foo/bar","n=v",".*","/replace","/replace","n=v"},
|
||||
{"/foo/bar",null,"/xxx.*","/replace",null,null},
|
||||
{"/foo/bar",null,"/(.*)/(.*)","/$2/$1/xxx","/bar/foo/xxx",null},
|
||||
{"/foo/bar",null,"/(.*)/(.*)","/test?p2=$2&p1=$1","/test","p2=bar&p1=foo"},
|
||||
{"/foo/bar","n=v","/(.*)/(.*)","/test?p2=$2&p1=$1","/test","n=v&p2=bar&p1=foo"},
|
||||
{"/foo/bar",null,"/(.*)/(.*)","/foo/bar?p2=$2&p1=$1","/foo/bar","p2=bar&p1=foo"},
|
||||
{"/foo/bar","n=v","/(.*)/(.*)","/foo/bar?p2=$2&p1=$1","/foo/bar","n=v&p2=bar&p1=foo"},
|
||||
{"/foo/bar",null,"/(foo)/(.*)(bar)","/$3/$1/xxx$2","/bar/foo/xxx",null},
|
||||
{"/foo/$bar",null,".*","/$replace","/$replace",null},
|
||||
{"/foo/$bar",null,"/foo/(.*)","/$1/replace","/$bar/replace",null},
|
||||
{"/foo/bar/info",null,"/foo/(NotHere)?([^/]*)/(.*)","/$3/other?p1=$2","/info/other","p1=bar"},
|
||||
{"/foo/bar/info",null,"/foo/(NotHere)?([^/]*)/(.*)","/$3/other?p1=$2&$Q","/info/other","p1=bar&"},
|
||||
{"/foo/bar/info","n=v","/foo/(NotHere)?([^/]*)/(.*)","/$3/other?p1=$2&$Q","/info/other","p1=bar&n=v"},
|
||||
{"/foo/bar/info","n=v","/foo/(NotHere)?([^/]*)/(.*)","/$3/other?p1=$2","/info/other","n=v&p1=bar"},
|
||||
};
|
||||
private RewriteRegexRule _rule;
|
||||
|
||||
|
@ -45,17 +53,43 @@ public class RewriteRegexRuleTest extends AbstractRuleTestCase
|
|||
{
|
||||
for (String[] test : _tests)
|
||||
{
|
||||
_rule.setRegex(test[1]);
|
||||
_rule.setReplacement(test[2]);
|
||||
String result = _rule.matchAndApply(test[0], _request, _response);
|
||||
assertEquals(test[1], test[3], result);
|
||||
|
||||
String t=test[0]+"?"+test[1]+">"+test[2]+"|"+test[3];
|
||||
_rule.setRegex(test[2]);
|
||||
_rule.setReplacement(test[3]);
|
||||
|
||||
_request.setRequestURI(test[0]);
|
||||
_request.setQueryString(null);
|
||||
_request.setQueryString(test[1]);
|
||||
_request.getAttributes().clearAttributes();
|
||||
|
||||
String result = _rule.matchAndApply(test[0], _request, _response);
|
||||
assertEquals(t, test[4], result);
|
||||
_rule.applyURI(_request,test[0],result);
|
||||
|
||||
assertEquals(test[3], _request.getRequestURI());
|
||||
assertEquals(test[4], _request.getQueryString());
|
||||
assertEquals(t,test[4], _request.getRequestURI());
|
||||
assertEquals(t,test[5], _request.getQueryString());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainedRequestUriEnabled() throws IOException
|
||||
{
|
||||
RuleContainer container = new RuleContainer();
|
||||
container.setRewriteRequestURI(true);
|
||||
container.addRule(_rule);
|
||||
for (String[] test : _tests)
|
||||
{
|
||||
String t=test[0]+"?"+test[1]+">"+test[2]+"|"+test[3];
|
||||
_rule.setRegex(test[2]);
|
||||
_rule.setReplacement(test[3]);
|
||||
|
||||
_request.setRequestURI(test[0]);
|
||||
_request.setQueryString(test[1]);
|
||||
_request.getAttributes().clearAttributes();
|
||||
|
||||
String result = container.apply(test[0],_request,_response);
|
||||
assertEquals(t,test[4]==null?test[0]:test[4], result);
|
||||
assertEquals(t,test[4]==null?test[0]:test[4], _request.getRequestURI());
|
||||
assertEquals(t,test[5], _request.getQueryString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,9 +39,11 @@ import org.eclipse.jetty.util.IO;
|
|||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.StdErrLog;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
import org.junit.matchers.JUnitMatchers;
|
||||
import org.mockito.internal.matchers.Contains;
|
||||
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -1280,4 +1282,51 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
response.setStatus(200);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSuspendedPipeline() throws Exception
|
||||
{
|
||||
SuspendHandler suspend = new SuspendHandler();
|
||||
suspend.setSuspendFor(30000);
|
||||
suspend.setResumeAfter(1000);
|
||||
configureServer(suspend);
|
||||
|
||||
long start=System.currentTimeMillis();
|
||||
Socket client=newSocket(HOST,_connector.getLocalPort());
|
||||
try
|
||||
{
|
||||
OutputStream os=client.getOutputStream();
|
||||
InputStream is=client.getInputStream();
|
||||
|
||||
// write an initial request
|
||||
os.write((
|
||||
"GET / HTTP/1.1\r\n"+
|
||||
"host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
|
||||
"\r\n"
|
||||
).getBytes());
|
||||
os.flush();
|
||||
|
||||
Thread.sleep(200);
|
||||
|
||||
// write an pipelined request
|
||||
os.write((
|
||||
"GET / HTTP/1.1\r\n"+
|
||||
"host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
|
||||
"connection: close\r\n"+
|
||||
"\r\n"
|
||||
).getBytes());
|
||||
os.flush();
|
||||
|
||||
String response=readResponse(client);
|
||||
assertThat(response,JUnitMatchers.containsString("RESUMEDHTTP/1.1 200 OK"));
|
||||
assertThat((System.currentTimeMillis()-start),greaterThanOrEqualTo(1999L));
|
||||
|
||||
// TODO This test should also check that that the CPU did not spin during the suspend.
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,13 @@ public class SelectChannelServerTest extends HttpServerTestBase
|
|||
{
|
||||
super.testCommittedError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testSuspendedPipeline() throws Exception
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
super.testSuspendedPipeline();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -144,5 +144,10 @@ public class SelectChannelServerSslTest extends HttpServerTestBase
|
|||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void testSuspendedPipeline() throws Exception
|
||||
{
|
||||
super.testSuspendedPipeline();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -590,7 +590,8 @@ public class ServletHandler extends ScopedHandler
|
|||
}
|
||||
finally
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
if (servlet_holder!=null)
|
||||
baseRequest.setHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1007,7 +1007,7 @@ public class DoSFilter implements Filter
|
|||
if (_rateTrackers != null && _trackerTimeoutQ != null)
|
||||
{
|
||||
long now = _trackerTimeoutQ.getNow();
|
||||
int latestIndex = _next == 0 ? 3 : (_next - 1 ) % _timestamps.length;
|
||||
int latestIndex = _next == 0 ? (_timestamps.length-1) : (_next - 1 );
|
||||
long last=_timestamps[latestIndex];
|
||||
boolean hasRecentRequest = last != 0 && (now-last)<1000L;
|
||||
|
||||
|
|
|
@ -80,7 +80,9 @@ public class Promise<T> implements Handler<T>, Future<T>
|
|||
@Override
|
||||
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
latch.await(timeout, unit);
|
||||
boolean elapsed = !latch.await(timeout, unit);
|
||||
if (elapsed)
|
||||
throw new TimeoutException();
|
||||
return result();
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,11 @@
|
|||
|
||||
package org.eclipse.jetty.spdy.http;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jetty.http.HttpSchemes;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.spdy.AsyncConnectionFactory;
|
||||
import org.eclipse.jetty.spdy.SPDYServerConnector;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
|
@ -52,4 +57,34 @@ public class HTTPSPDYServerConnector extends SPDYServerConnector
|
|||
{
|
||||
return defaultConnectionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(EndPoint endPoint, Request request) throws IOException
|
||||
{
|
||||
super.customize(endPoint, request);
|
||||
if (getSslContextFactory() != null)
|
||||
request.setScheme(HttpSchemes.HTTPS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfidential(Request request)
|
||||
{
|
||||
if (getSslContextFactory() != null)
|
||||
{
|
||||
int confidentialPort = getConfidentialPort();
|
||||
return confidentialPort == 0 || confidentialPort == request.getServerPort();
|
||||
}
|
||||
return super.isConfidential(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIntegral(Request request)
|
||||
{
|
||||
if (getSslContextFactory() != null)
|
||||
{
|
||||
int integralPort = getIntegralPort();
|
||||
return integralPort == 0 || integralPort == request.getServerPort();
|
||||
}
|
||||
return super.isIntegral(request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,8 +31,9 @@ import javax.servlet.ServletOutputStream;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.continuation.Continuation;
|
||||
import org.eclipse.jetty.continuation.ContinuationSupport;
|
||||
import org.eclipse.jetty.io.ByteArrayBuffer;
|
||||
import javax.servlet.AsyncContext;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.spdy.api.BytesDataInfo;
|
||||
|
@ -975,7 +976,10 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
|
|||
throws IOException, ServletException
|
||||
{
|
||||
request.setHandled(true);
|
||||
final AsyncContext async = request.startAsync();
|
||||
|
||||
final Continuation continuation = ContinuationSupport.getContinuation(request);
|
||||
continuation.suspend();
|
||||
|
||||
new Thread()
|
||||
{
|
||||
@Override
|
||||
|
@ -988,7 +992,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
|
|||
int read = 0;
|
||||
while (read < data.length)
|
||||
read += input.read(buffer);
|
||||
async.complete();
|
||||
continuation.complete();
|
||||
latch.countDown();
|
||||
}
|
||||
catch (IOException x)
|
||||
|
@ -1034,7 +1038,10 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
|
|||
throws IOException, ServletException
|
||||
{
|
||||
request.setHandled(true);
|
||||
final AsyncContext async = request.startAsync();
|
||||
|
||||
final Continuation continuation = ContinuationSupport.getContinuation(request);
|
||||
continuation.suspend();
|
||||
|
||||
new Thread()
|
||||
{
|
||||
@Override
|
||||
|
@ -1047,7 +1054,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
|
|||
int read = 0;
|
||||
while (read < 2 * data.length)
|
||||
read += input.read(buffer);
|
||||
async.complete();
|
||||
continuation.complete();
|
||||
latch.countDown();
|
||||
}
|
||||
catch (IOException x)
|
||||
|
@ -1094,14 +1101,17 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
|
|||
throws IOException, ServletException
|
||||
{
|
||||
request.setHandled(true);
|
||||
if (request.getAsyncContinuation().isInitial())
|
||||
|
||||
final Continuation continuation = ContinuationSupport.getContinuation(request);
|
||||
|
||||
if (continuation.isInitial())
|
||||
{
|
||||
InputStream input = request.getInputStream();
|
||||
byte[] buffer = new byte[256];
|
||||
int read = 0;
|
||||
while (read < data.length)
|
||||
read += input.read(buffer);
|
||||
final AsyncContext async = request.startAsync();
|
||||
continuation.suspend();
|
||||
new Thread()
|
||||
{
|
||||
@Override
|
||||
|
@ -1110,7 +1120,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
|
|||
try
|
||||
{
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
async.dispatch();
|
||||
continuation.resume();
|
||||
latch.countDown();
|
||||
}
|
||||
catch (InterruptedException x)
|
||||
|
|
|
@ -38,6 +38,7 @@ import java.util.concurrent.Executors;
|
|||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
|
@ -313,7 +314,7 @@ public class SPDYClient
|
|||
}
|
||||
|
||||
@Override
|
||||
public AsyncConnection newConnection(final SocketChannel channel, AsyncEndPoint endPoint, final Object attachment)
|
||||
public AsyncConnection newConnection(final SocketChannel channel, AsyncEndPoint endPoint, Object attachment)
|
||||
{
|
||||
SessionPromise sessionPromise = (SessionPromise)attachment;
|
||||
final SPDYClient client = sessionPromise.client;
|
||||
|
@ -322,11 +323,31 @@ public class SPDYClient
|
|||
{
|
||||
if (sslContextFactory != null)
|
||||
{
|
||||
final AtomicReference<AsyncEndPoint> sslEndPointRef = new AtomicReference<>();
|
||||
final AtomicReference<Object> attachmentRef = new AtomicReference<>(attachment);
|
||||
SSLEngine engine = client.newSSLEngine(sslContextFactory, channel);
|
||||
SslConnection sslConnection = new SslConnection(engine, endPoint);
|
||||
SslConnection sslConnection = new SslConnection(engine, endPoint)
|
||||
{
|
||||
@Override
|
||||
public void onClose()
|
||||
{
|
||||
sslEndPointRef.set(null);
|
||||
attachmentRef.set(null);
|
||||
super.onClose();
|
||||
}
|
||||
};
|
||||
endPoint.setConnection(sslConnection);
|
||||
final AsyncEndPoint sslEndPoint = sslConnection.getSslEndPoint();
|
||||
AsyncEndPoint sslEndPoint = sslConnection.getSslEndPoint();
|
||||
sslEndPointRef.set(sslEndPoint);
|
||||
|
||||
// Instances of the ClientProvider inner class strong reference the
|
||||
// SslEndPoint (via lexical scoping), which strong references the SSLEngine.
|
||||
// Since NextProtoNego stores in a WeakHashMap the SSLEngine as key
|
||||
// and this instance as value, we are in the situation where the value
|
||||
// of a WeakHashMap refers indirectly to the key, which is bad because
|
||||
// the entry will never be removed from the WeakHashMap.
|
||||
// We use AtomicReferences to be captured via lexical scoping,
|
||||
// and we null them out above when the connection is closed.
|
||||
NextProtoNego.put(engine, new NextProtoNego.ClientProvider()
|
||||
{
|
||||
@Override
|
||||
|
@ -340,7 +361,8 @@ public class SPDYClient
|
|||
{
|
||||
// Server does not support NPN, but this is a SPDY client, so hardcode SPDY
|
||||
ClientSPDYAsyncConnectionFactory connectionFactory = new ClientSPDYAsyncConnectionFactory();
|
||||
AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, attachment);
|
||||
AsyncEndPoint sslEndPoint = sslEndPointRef.get();
|
||||
AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, attachmentRef.get());
|
||||
sslEndPoint.setConnection(connection);
|
||||
}
|
||||
|
||||
|
@ -352,9 +374,9 @@ public class SPDYClient
|
|||
return null;
|
||||
|
||||
AsyncConnectionFactory connectionFactory = client.getAsyncConnectionFactory(protocol);
|
||||
AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, attachment);
|
||||
AsyncEndPoint sslEndPoint = sslEndPointRef.get();
|
||||
AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, attachmentRef.get());
|
||||
sslEndPoint.setConnection(connection);
|
||||
|
||||
return protocol;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -29,6 +29,7 @@ import java.util.concurrent.Executor;
|
|||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
|
@ -76,7 +77,7 @@ public class SPDYServerConnector extends SelectChannelConnector
|
|||
return bufferPool;
|
||||
}
|
||||
|
||||
protected Executor getExecutor()
|
||||
public Executor getExecutor()
|
||||
{
|
||||
final ThreadPool threadPool = getThreadPool();
|
||||
if (threadPool instanceof Executor)
|
||||
|
@ -91,11 +92,16 @@ public class SPDYServerConnector extends SelectChannelConnector
|
|||
};
|
||||
}
|
||||
|
||||
protected ScheduledExecutorService getScheduler()
|
||||
public ScheduledExecutorService getScheduler()
|
||||
{
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public SslContextFactory getSslContextFactory()
|
||||
{
|
||||
return sslContextFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
|
@ -171,16 +177,35 @@ public class SPDYServerConnector extends SelectChannelConnector
|
|||
if (sslContextFactory != null)
|
||||
{
|
||||
SSLEngine engine = newSSLEngine(sslContextFactory, channel);
|
||||
SslConnection sslConnection = new SslConnection(engine, endPoint);
|
||||
final AtomicReference<AsyncEndPoint> sslEndPointRef = new AtomicReference<>();
|
||||
SslConnection sslConnection = new SslConnection(engine, endPoint)
|
||||
{
|
||||
@Override
|
||||
public void onClose()
|
||||
{
|
||||
sslEndPointRef.set(null);
|
||||
super.onClose();
|
||||
}
|
||||
};
|
||||
endPoint.setConnection(sslConnection);
|
||||
final AsyncEndPoint sslEndPoint = sslConnection.getSslEndPoint();
|
||||
AsyncEndPoint sslEndPoint = sslConnection.getSslEndPoint();
|
||||
sslEndPointRef.set(sslEndPoint);
|
||||
|
||||
// Instances of the ServerProvider inner class strong reference the
|
||||
// SslEndPoint (via lexical scoping), which strong references the SSLEngine.
|
||||
// Since NextProtoNego stores in a WeakHashMap the SSLEngine as key
|
||||
// and this instance as value, we are in the situation where the value
|
||||
// of a WeakHashMap refers indirectly to the key, which is bad because
|
||||
// the entry will never be removed from the WeakHashMap.
|
||||
// We use AtomicReferences to be captured via lexical scoping,
|
||||
// and we null them out above when the connection is closed.
|
||||
NextProtoNego.put(engine, new NextProtoNego.ServerProvider()
|
||||
{
|
||||
@Override
|
||||
public void unsupported()
|
||||
{
|
||||
AsyncConnectionFactory connectionFactory = getDefaultAsyncConnectionFactory();
|
||||
AsyncEndPoint sslEndPoint = sslEndPointRef.get();
|
||||
AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, SPDYServerConnector.this);
|
||||
sslEndPoint.setConnection(connection);
|
||||
}
|
||||
|
@ -195,6 +220,7 @@ public class SPDYServerConnector extends SelectChannelConnector
|
|||
public void protocolSelected(String protocol)
|
||||
{
|
||||
AsyncConnectionFactory connectionFactory = getAsyncConnectionFactory(protocol);
|
||||
AsyncEndPoint sslEndPoint = sslEndPointRef.get();
|
||||
AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, SPDYServerConnector.this);
|
||||
sslEndPoint.setConnection(connection);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.npn.NextProtoNego;
|
||||
import org.eclipse.jetty.spdy.api.Session;
|
||||
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SSLEngineLeakTest extends AbstractTest
|
||||
{
|
||||
@Override
|
||||
protected SPDYServerConnector newSPDYServerConnector(ServerSessionFrameListener listener)
|
||||
{
|
||||
SslContextFactory sslContextFactory = newSslContextFactory();
|
||||
return new SPDYServerConnector(listener, sslContextFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)
|
||||
{
|
||||
SslContextFactory sslContextFactory = newSslContextFactory();
|
||||
return new SPDYClient.Factory(threadPool, sslContextFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSSLEngineLeak() throws Exception
|
||||
{
|
||||
System.gc();
|
||||
Thread.sleep(1000);
|
||||
|
||||
Field field = NextProtoNego.class.getDeclaredField("objects");
|
||||
field.setAccessible(true);
|
||||
Map<Object, NextProtoNego.Provider> objects = (Map<Object, NextProtoNego.Provider>)field.get(null);
|
||||
int initialSize = objects.size();
|
||||
|
||||
avoidStackLocalVariables();
|
||||
// Allow the close to arrive to the server and the selector to process it
|
||||
Thread.sleep(1000);
|
||||
|
||||
// Perform GC to be sure that the WeakHashMap is cleared
|
||||
System.gc();
|
||||
Thread.sleep(1000);
|
||||
|
||||
// Check that the WeakHashMap is empty
|
||||
Assert.assertEquals(initialSize, objects.size());
|
||||
}
|
||||
|
||||
private void avoidStackLocalVariables() throws Exception
|
||||
{
|
||||
Session session = startClient(startServer(null), null);
|
||||
session.goAway().get(5, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue