Merge branch 'master' into javawebsocket-jsr
This commit is contained in:
commit
ceabca5fbe
|
@ -152,7 +152,7 @@ etc/jetty-http.xml
|
||||||
|
|
||||||
#===========================================================
|
#===========================================================
|
||||||
# HTTPS Connector
|
# HTTPS Connector
|
||||||
# Must be used with 200-ssl.ini
|
# Must be used with jetty-ssl.xml
|
||||||
#-----------------------------------------------------------
|
#-----------------------------------------------------------
|
||||||
# jetty.https.port=8443
|
# jetty.https.port=8443
|
||||||
# etc/jetty-https.xml
|
# etc/jetty-https.xml
|
||||||
|
|
|
@ -2,93 +2,10 @@
|
||||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
|
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
|
||||||
|
|
||||||
<!-- =============================================================== -->
|
<!-- =============================================================== -->
|
||||||
<!-- Configure Jetty Plus features -->
|
<!-- Configure extended support for webapps -->
|
||||||
<!-- -->
|
|
||||||
<!-- This file sets up a WebAppDeployer to automatically deploy all -->
|
|
||||||
<!-- webapps in $jetty.home/webapps-plus at startup time, and to -->
|
|
||||||
<!-- enable all of them with Plus features (jndi etc). -->
|
|
||||||
<!-- -->
|
|
||||||
<!-- You can instead configure individual webapps with Jetty Plus -->
|
|
||||||
<!-- features by using the ContextDeployer (configured in -->
|
|
||||||
<!-- $jetty.home/etc/jetty.xml), and ensuring that you set the -->
|
|
||||||
<!-- same set of classes listed below in the "plusConfig" as the -->
|
|
||||||
<!-- webapp's configurationClasses. -->
|
|
||||||
<!-- -->
|
|
||||||
<!-- For more information about Jetty Plus, see the Jetty wiki at -->
|
|
||||||
<!-- http://docs.codehaus.org/display/JETTY/Jetty+Wiki -->
|
|
||||||
<!-- =============================================================== -->
|
<!-- =============================================================== -->
|
||||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||||
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<!-- Example JAAS realm setup. -->
|
|
||||||
<!-- The LoginModuleName must be exactly the same as in the -->
|
|
||||||
<!-- login.conf file, and the realm Name must be the same as in -->
|
|
||||||
<!-- the web.xml file. -->
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<!--
|
|
||||||
<Call name="addBean">
|
|
||||||
<Arg>
|
|
||||||
<New class="org.eclipse.jetty.plus.jaas.JAASLoginService">
|
|
||||||
<Set name="name">xyzrealm</Set>
|
|
||||||
<Set name="LoginModuleName">xyz</Set>
|
|
||||||
</New>
|
|
||||||
</Arg>
|
|
||||||
</Call>
|
|
||||||
-->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<!-- Enabling plus feature. -->
|
|
||||||
<!-- Choose one of the following methods. NOTE that by default -->
|
|
||||||
<!-- the last method (enabled for all webapps on this Server) is -->
|
|
||||||
<!-- enabled. -->
|
|
||||||
<!-- -->
|
|
||||||
<!-- For a single webapp: -->
|
|
||||||
<!-- Use a context xml file to call -->
|
|
||||||
<!-- setConfigurationClasses(plusConfig). -->
|
|
||||||
<!-- -->
|
|
||||||
<!-- For all webapps in a directory: -->
|
|
||||||
<!-- Uncomment the section entitled "Apply plusConfig to all -->
|
|
||||||
<!-- webapps in webapps-plus". -->
|
|
||||||
<!-- -->
|
|
||||||
<!-- For all webapps deployed via this Server instance: -->
|
|
||||||
<!-- Uncomment the section entitled "Apply plusConfig to all -->
|
|
||||||
<!-- webapps for this Server". NOTE: this is the default. -->
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<!-- Apply plusConfig to all webapps in webapps-plus -->
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<!-- Uncomment the following to set up a deployer that will -->
|
|
||||||
<!-- deploy webapps from a directory called webapps-plus. Note -->
|
|
||||||
<!-- that you will need to create this directory first! -->
|
|
||||||
<!--
|
|
||||||
<Ref refid="DeploymentManager">
|
|
||||||
<Call name="addAppProvider">
|
|
||||||
<Arg>
|
|
||||||
<New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
|
|
||||||
<Set name="monitoredDirName"><Property name="jetty.home" default="." />/webapps-plus</Set>
|
|
||||||
<Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
|
|
||||||
<Set name="scanInterval">5</Set>
|
|
||||||
<Set name="parentLoaderPriority">false</Set>
|
|
||||||
<Set name="extractWars">true</Set>
|
|
||||||
<Set name="configurationClasses">
|
|
||||||
<Array id="plusConfig" type="java.lang.String">
|
|
||||||
<Item>org.eclipse.jetty.webapp.WebInfConfiguration</Item>
|
|
||||||
<Item>org.eclipse.jetty.webapp.WebXmlConfiguration</Item>
|
|
||||||
<Item>org.eclipse.jetty.webapp.MetaInfConfiguration</Item>
|
|
||||||
<Item>org.eclipse.jetty.webapp.FragmentConfiguration</Item>
|
|
||||||
<Item>org.eclipse.jetty.plus.webapp.EnvConfiguration</Item>
|
|
||||||
<Item>org.eclipse.jetty.plus.webapp.PlusConfiguration</Item>
|
|
||||||
<Item>org.eclipse.jetty.webapp.JettyWebXmlConfiguration</Item>
|
|
||||||
</Array>
|
|
||||||
</Set>
|
|
||||||
</New>
|
|
||||||
</Arg>
|
|
||||||
</Call>
|
|
||||||
</Ref>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- =========================================================== -->
|
<!-- =========================================================== -->
|
||||||
<!-- Add plus Configuring classes to all webapps for this Server -->
|
<!-- Add plus Configuring classes to all webapps for this Server -->
|
||||||
<!-- =========================================================== -->
|
<!-- =========================================================== -->
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.server;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@ -45,7 +46,8 @@ import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.io.EofException;
|
import org.eclipse.jetty.io.EofException;
|
||||||
import org.eclipse.jetty.server.HttpChannelState.Next;
|
import org.eclipse.jetty.server.HttpChannelState.Next;
|
||||||
import org.eclipse.jetty.server.handler.ErrorHandler;
|
import org.eclipse.jetty.server.handler.ErrorHandler;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BlockingCallback;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
import org.eclipse.jetty.util.URIUtil;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
@ -88,7 +90,8 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
|
||||||
private final HttpURI _uri;
|
private final HttpURI _uri;
|
||||||
private final HttpChannelState _state;
|
private final HttpChannelState _state;
|
||||||
private final Request _request;
|
private final Request _request;
|
||||||
private final Response _response;
|
private final Response _response;
|
||||||
|
private final BlockingCallback _writeblock=new BlockingCallback();
|
||||||
private HttpVersion _version = HttpVersion.HTTP_1_1;
|
private HttpVersion _version = HttpVersion.HTTP_1_1;
|
||||||
private boolean _expect = false;
|
private boolean _expect = false;
|
||||||
private boolean _expect100Continue = false;
|
private boolean _expect100Continue = false;
|
||||||
|
@ -198,7 +201,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
|
||||||
throw new IOException("Committed before 100 Continues");
|
throw new IOException("Committed before 100 Continues");
|
||||||
|
|
||||||
// TODO: break this dependency with HttpGenerator
|
// TODO: break this dependency with HttpGenerator
|
||||||
boolean committed = commitResponse(HttpGenerator.CONTINUE_100_INFO, null, false);
|
boolean committed = sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false);
|
||||||
if (!committed)
|
if (!committed)
|
||||||
throw new IOException("Concurrent commit while trying to send 100-Continue");
|
throw new IOException("Concurrent commit while trying to send 100-Continue");
|
||||||
}
|
}
|
||||||
|
@ -356,7 +359,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
|
||||||
{
|
{
|
||||||
HttpFields fields = new HttpFields();
|
HttpFields fields = new HttpFields();
|
||||||
ResponseInfo info = new ResponseInfo(_request.getHttpVersion(), fields, 0, HttpStatus.INTERNAL_SERVER_ERROR_500, null, _request.isHead());
|
ResponseInfo info = new ResponseInfo(_request.getHttpVersion(), fields, 0, HttpStatus.INTERNAL_SERVER_ERROR_500, null, _request.isHead());
|
||||||
boolean committed = commitResponse(info, null, true);
|
boolean committed = sendResponse(info, null, true);
|
||||||
if (!committed)
|
if (!committed)
|
||||||
LOG.warn("Could not send response error 500: "+x);
|
LOG.warn("Could not send response error 500: "+x);
|
||||||
}
|
}
|
||||||
|
@ -500,6 +503,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
|
||||||
if (charset != null)
|
if (charset != null)
|
||||||
_request.setCharacterEncodingUnchecked(charset);
|
_request.setCharacterEncodingUnchecked(charset);
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -584,7 +588,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
|
||||||
{
|
{
|
||||||
if (_state.handling()==Next.CONTINUE)
|
if (_state.handling()==Next.CONTINUE)
|
||||||
{
|
{
|
||||||
commitResponse(new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(),0,status,reason,false),null,true);
|
sendResponse(new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(),0,status,reason,false),null,true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
|
@ -597,47 +601,92 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
|
||||||
_state.completed();
|
_state.completed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean commitResponse(ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
|
protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete, final Callback callback)
|
||||||
{
|
{
|
||||||
boolean committed = _committed.compareAndSet(false, true);
|
boolean committing = _committed.compareAndSet(false, true);
|
||||||
if (committed)
|
if (committing)
|
||||||
{
|
{
|
||||||
try
|
// We need an info to commit
|
||||||
|
if (info==null)
|
||||||
{
|
{
|
||||||
// Try to commit with the passed info
|
info = _response.newResponseInfo();
|
||||||
_transport.send(info, content, complete);
|
}
|
||||||
|
|
||||||
|
final int status=info.getStatus();
|
||||||
|
final Callback committed = new Callback()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void succeeded()
|
||||||
|
{
|
||||||
|
// If we are committing a 1xx response, we need to reset the commit
|
||||||
|
// status so that the "real" response can be committed again.
|
||||||
|
if (status<200 && status>=100)
|
||||||
|
_committed.set(false);
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
|
||||||
// If we are committing a 1xx response, we need to reset the commit
|
@Override
|
||||||
// status so that the "real" response can be committed again.
|
public void failed(final Throwable x)
|
||||||
if (info.getStatus() < 200)
|
{
|
||||||
_committed.set(false);
|
if (x instanceof EofException)
|
||||||
}
|
{
|
||||||
catch (EofException e)
|
LOG.debug(x);
|
||||||
{
|
_response.getHttpOutput().closed();
|
||||||
LOG.debug(e);
|
callback.failed(x);
|
||||||
// TODO is it worthwhile sending if we are at EoF?
|
}
|
||||||
// "application" info failed to commit, commit with a failsafe 500 info
|
else
|
||||||
_transport.send(HttpGenerator.RESPONSE_500_INFO,null,true);
|
{
|
||||||
complete=true;
|
LOG.warn(x);
|
||||||
throw e;
|
_transport.send(HttpGenerator.RESPONSE_500_INFO,null,true,new Callback()
|
||||||
}
|
{
|
||||||
catch (Exception e)
|
@Override
|
||||||
{
|
public void succeeded()
|
||||||
LOG.warn(e);
|
{
|
||||||
// "application" info failed to commit, commit with a failsafe 500 info
|
_response.getHttpOutput().closed();
|
||||||
_transport.send(HttpGenerator.RESPONSE_500_INFO,null,true);
|
callback.failed(x);
|
||||||
complete=true;
|
}
|
||||||
throw e;
|
|
||||||
}
|
@Override
|
||||||
finally
|
public void failed(Throwable th)
|
||||||
{
|
{
|
||||||
// TODO this indicates the relationship with HttpOutput is not exactly correct
|
LOG.ignore(th);
|
||||||
if (complete)
|
_response.getHttpOutput().closed();
|
||||||
_response.getHttpOutput().closed();
|
callback.failed(x);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_transport.send(info, content, complete, committed);
|
||||||
}
|
}
|
||||||
return committed;
|
else if (info==null)
|
||||||
|
{
|
||||||
|
// This is a normal write
|
||||||
|
_transport.send(null, content, complete, callback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
callback.failed(new IllegalStateException("committed"));
|
||||||
|
}
|
||||||
|
return committing;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
|
||||||
|
{
|
||||||
|
boolean committing=sendResponse(info,content,complete,_writeblock);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_writeblock.block();
|
||||||
|
}
|
||||||
|
catch (InterruptedException | TimeoutException e)
|
||||||
|
{
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return committing;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isCommitted()
|
protected boolean isCommitted()
|
||||||
|
@ -655,17 +704,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
|
||||||
*/
|
*/
|
||||||
protected void write(ByteBuffer content, boolean complete) throws IOException
|
protected void write(ByteBuffer content, boolean complete) throws IOException
|
||||||
{
|
{
|
||||||
if (isCommitted())
|
sendResponse(null, content, complete);
|
||||||
{
|
|
||||||
_transport.send(null, content, complete);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ResponseInfo info = _response.newResponseInfo();
|
|
||||||
boolean committed = commitResponse(info, content, complete);
|
|
||||||
if (!committed)
|
|
||||||
throw new IOException("Concurrent commit");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void execute(Runnable task)
|
protected void execute(Runnable task)
|
||||||
|
|
|
@ -51,7 +51,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
{
|
{
|
||||||
public static final String UPGRADE_CONNECTION_ATTRIBUTE = "org.eclipse.jetty.server.HttpConnection.UPGRADE";
|
public static final String UPGRADE_CONNECTION_ATTRIBUTE = "org.eclipse.jetty.server.HttpConnection.UPGRADE";
|
||||||
private static final boolean REQUEST_BUFFER_DIRECT=false;
|
private static final boolean REQUEST_BUFFER_DIRECT=false;
|
||||||
private static final boolean HEADER_BUFFER_DIRECT=true;
|
private static final boolean HEADER_BUFFER_DIRECT=false;
|
||||||
private static final boolean CHUNK_BUFFER_DIRECT=false;
|
private static final boolean CHUNK_BUFFER_DIRECT=false;
|
||||||
private static final Logger LOG = Log.getLogger(HttpConnection.class);
|
private static final Logger LOG = Log.getLogger(HttpConnection.class);
|
||||||
private static final ThreadLocal<HttpConnection> __currentConnection = new ThreadLocal<>();
|
private static final ThreadLocal<HttpConnection> __currentConnection = new ThreadLocal<>();
|
||||||
|
@ -307,149 +307,46 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent) throws IOException
|
public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent) throws IOException
|
||||||
{
|
|
||||||
// If we are still expecting a 100 continues
|
|
||||||
if (_channel.isExpecting100Continue())
|
|
||||||
// then we can't be persistent
|
|
||||||
_generator.setPersistent(false);
|
|
||||||
|
|
||||||
|
|
||||||
ByteBuffer header = null;
|
|
||||||
ByteBuffer chunk = null;
|
|
||||||
out: while (true)
|
|
||||||
{
|
|
||||||
HttpGenerator.Result result = _generator.generateResponse(info, header, chunk, content, lastContent);
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("{} generate: {} ({},{},{})@{}",
|
|
||||||
this,
|
|
||||||
result,
|
|
||||||
BufferUtil.toSummaryString(header),
|
|
||||||
BufferUtil.toSummaryString(content),
|
|
||||||
lastContent,
|
|
||||||
_generator.getState());
|
|
||||||
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
case NEED_HEADER:
|
|
||||||
{
|
|
||||||
if (lastContent && content!=null && BufferUtil.space(content)>_config.getResponseHeaderSize() && content.hasArray() )
|
|
||||||
{
|
|
||||||
// use spare space in content buffer for header buffer
|
|
||||||
int p=content.position();
|
|
||||||
int l=content.limit();
|
|
||||||
content.position(l);
|
|
||||||
content.limit(l+_config.getResponseHeaderSize());
|
|
||||||
header=content.slice();
|
|
||||||
header.limit(0);
|
|
||||||
content.position(p);
|
|
||||||
content.limit(l);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
case NEED_CHUNK:
|
|
||||||
{
|
|
||||||
chunk = _chunk;
|
|
||||||
if (chunk==null)
|
|
||||||
chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
case FLUSH:
|
|
||||||
{
|
|
||||||
// Don't write the chunk or the content if this is a HEAD response
|
|
||||||
if (_channel.getRequest().isHead())
|
|
||||||
{
|
|
||||||
BufferUtil.clear(chunk);
|
|
||||||
BufferUtil.clear(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a header
|
|
||||||
if (BufferUtil.hasContent(header))
|
|
||||||
{
|
|
||||||
// we know there will not be a chunk, so write either header+content or just the header
|
|
||||||
if (BufferUtil.hasContent(content))
|
|
||||||
blockingWrite(header, content);
|
|
||||||
else
|
|
||||||
blockingWrite(header);
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (BufferUtil.hasContent(chunk))
|
|
||||||
{
|
|
||||||
if (BufferUtil.hasContent(content))
|
|
||||||
blockingWrite(chunk,content);
|
|
||||||
else
|
|
||||||
blockingWrite(chunk);
|
|
||||||
}
|
|
||||||
else if (BufferUtil.hasContent(content))
|
|
||||||
{
|
|
||||||
blockingWrite(content);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
case SHUTDOWN_OUT:
|
|
||||||
{
|
|
||||||
getEndPoint().shutdownOutput();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
case DONE:
|
|
||||||
{
|
|
||||||
if (header!=null)
|
|
||||||
{
|
|
||||||
// don't release header in spare content buffer
|
|
||||||
if (!lastContent || content==null || !content.hasArray() || !header.hasArray() || content.array()!=header.array())
|
|
||||||
_bufferPool.release(header);
|
|
||||||
}
|
|
||||||
if (chunk!=null)
|
|
||||||
_bufferPool.release(chunk);
|
|
||||||
break out;
|
|
||||||
}
|
|
||||||
case CONTINUE:
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new IllegalStateException("generateResponse="+result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void send(ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
send(info,content,lastContent);
|
// If we are still expecting a 100 continues
|
||||||
callback.succeeded();
|
if (info !=null && _channel.isExpecting100Continue())
|
||||||
}
|
// then we can't be persistent
|
||||||
catch (IOException e)
|
_generator.setPersistent(false);
|
||||||
{
|
|
||||||
callback.failed(e);
|
Sender sender = new Sender(content,lastContent,_writeBlocker);
|
||||||
}
|
sender.process(info);
|
||||||
}
|
|
||||||
|
|
||||||
private void blockingWrite(ByteBuffer... bytes) throws IOException
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
getEndPoint().write(_writeBlocker, bytes);
|
|
||||||
_writeBlocker.block();
|
_writeBlocker.block();
|
||||||
}
|
}
|
||||||
catch (InterruptedException x)
|
catch (InterruptedException x)
|
||||||
{
|
{
|
||||||
|
x.printStackTrace();
|
||||||
throw (IOException)new InterruptedIOException().initCause(x);
|
throw (IOException)new InterruptedIOException().initCause(x);
|
||||||
}
|
}
|
||||||
catch (TimeoutException e)
|
catch (TimeoutException e)
|
||||||
{
|
{
|
||||||
|
e.printStackTrace();
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
catch (ClosedChannelException e)
|
catch (ClosedChannelException e)
|
||||||
{
|
{
|
||||||
|
e.printStackTrace();
|
||||||
throw new EofException(e);
|
throw new EofException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
|
||||||
|
{
|
||||||
|
// If we are still expecting a 100 continues
|
||||||
|
if (info !=null && _channel.isExpecting100Continue())
|
||||||
|
// then we can't be persistent
|
||||||
|
_generator.setPersistent(false);
|
||||||
|
|
||||||
|
new Sender(content,lastContent,callback).process(info);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void completed()
|
public void completed()
|
||||||
|
@ -692,5 +589,144 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class Sender implements Callback
|
||||||
|
{
|
||||||
|
final ByteBuffer _content;
|
||||||
|
final boolean _lastContent;
|
||||||
|
final Callback _callback;
|
||||||
|
|
||||||
|
Sender(ByteBuffer content, boolean last, Callback callback)
|
||||||
|
{
|
||||||
|
_callback=callback;
|
||||||
|
_content=content;
|
||||||
|
_lastContent=last;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void process(ResponseInfo info)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteBuffer header = null;
|
||||||
|
ByteBuffer chunk = null;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
HttpGenerator.Result result = _generator.generateResponse(info, header, chunk, _content, _lastContent);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} generate: {} ({},{},{})@{}",
|
||||||
|
this,
|
||||||
|
result,
|
||||||
|
BufferUtil.toSummaryString(header),
|
||||||
|
BufferUtil.toSummaryString(_content),
|
||||||
|
_lastContent,
|
||||||
|
_generator.getState());
|
||||||
|
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case NEED_HEADER:
|
||||||
|
{
|
||||||
|
if (_lastContent && _content!=null && BufferUtil.space(_content)>_config.getResponseHeaderSize() && _content.hasArray() )
|
||||||
|
{
|
||||||
|
// use spare space in content buffer for header buffer
|
||||||
|
int p=_content.position();
|
||||||
|
int l=_content.limit();
|
||||||
|
_content.position(l);
|
||||||
|
_content.limit(l+_config.getResponseHeaderSize());
|
||||||
|
header=_content.slice();
|
||||||
|
header.limit(0);
|
||||||
|
_content.position(p);
|
||||||
|
_content.limit(l);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case NEED_CHUNK:
|
||||||
|
{
|
||||||
|
chunk = _chunk;
|
||||||
|
if (chunk==null)
|
||||||
|
chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case FLUSH:
|
||||||
|
{
|
||||||
|
// Don't write the chunk or the content if this is a HEAD response
|
||||||
|
if (_channel.getRequest().isHead())
|
||||||
|
{
|
||||||
|
BufferUtil.clear(chunk);
|
||||||
|
BufferUtil.clear(_content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a header
|
||||||
|
if (BufferUtil.hasContent(header))
|
||||||
|
{
|
||||||
|
// we know there will not be a chunk, so write either header+content or just the header
|
||||||
|
if (BufferUtil.hasContent(_content))
|
||||||
|
getEndPoint().write(this, header, _content);
|
||||||
|
else
|
||||||
|
getEndPoint().write(this, header);
|
||||||
|
}
|
||||||
|
else if (BufferUtil.hasContent(chunk))
|
||||||
|
{
|
||||||
|
if (BufferUtil.hasContent(_content))
|
||||||
|
getEndPoint().write(this, chunk, _content);
|
||||||
|
else
|
||||||
|
getEndPoint().write(this, chunk);
|
||||||
|
}
|
||||||
|
else if (BufferUtil.hasContent(_content))
|
||||||
|
{
|
||||||
|
getEndPoint().write(this, _content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case SHUTDOWN_OUT:
|
||||||
|
{
|
||||||
|
getEndPoint().shutdownOutput();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case DONE:
|
||||||
|
{
|
||||||
|
if (header!=null)
|
||||||
|
{
|
||||||
|
// don't release header in spare content buffer
|
||||||
|
if (!_lastContent || _content==null || !_content.hasArray() || !header.hasArray() || _content.array()!=header.array())
|
||||||
|
_bufferPool.release(header);
|
||||||
|
}
|
||||||
|
if (chunk!=null)
|
||||||
|
_bufferPool.release(chunk);
|
||||||
|
_callback.succeeded();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case CONTINUE:
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("generateResponse="+result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
_callback.failed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void succeeded()
|
||||||
|
{
|
||||||
|
process(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failed(Throwable x)
|
||||||
|
{
|
||||||
|
_callback.failed(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -425,7 +425,7 @@ public class Response implements HttpServletResponse
|
||||||
{
|
{
|
||||||
if (_channel.isExpecting102Processing() && !isCommitted())
|
if (_channel.isExpecting102Processing() && !isCommitted())
|
||||||
{
|
{
|
||||||
_channel.commitResponse(HttpGenerator.PROGRESS_102_INFO, null, true);
|
_channel.sendResponse(HttpGenerator.PROGRESS_102_INFO, null, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -158,15 +158,15 @@ public class HttpConnectionTest
|
||||||
@Test
|
@Test
|
||||||
public void testHead() throws Exception
|
public void testHead() throws Exception
|
||||||
{
|
{
|
||||||
String responseHEAD=connector.getResponses("HEAD /R1 HTTP/1.1\015\012"+
|
|
||||||
"Host: localhost\015\012"+
|
|
||||||
"Connection: close\015\012"+
|
|
||||||
"\015\012");
|
|
||||||
|
|
||||||
String responsePOST=connector.getResponses("POST /R1 HTTP/1.1\015\012"+
|
String responsePOST=connector.getResponses("POST /R1 HTTP/1.1\015\012"+
|
||||||
"Host: localhost\015\012"+
|
"Host: localhost\015\012"+
|
||||||
"Connection: close\015\012"+
|
"Connection: close\015\012"+
|
||||||
"\015\012");
|
"\015\012");
|
||||||
|
|
||||||
|
String responseHEAD=connector.getResponses("HEAD /R1 HTTP/1.1\015\012"+
|
||||||
|
"Host: localhost\015\012"+
|
||||||
|
"Connection: close\015\012"+
|
||||||
|
"\015\012");
|
||||||
|
|
||||||
assertThat(responsePOST,startsWith(responseHEAD.substring(0,responseHEAD.length()-2)));
|
assertThat(responsePOST,startsWith(responseHEAD.substring(0,responseHEAD.length()-2)));
|
||||||
assertThat(responsePOST.length(),greaterThan(responseHEAD.length()));
|
assertThat(responsePOST.length(),greaterThan(responseHEAD.length()));
|
||||||
|
|
|
@ -89,6 +89,7 @@ public class ResponseTest
|
||||||
@Override
|
@Override
|
||||||
public void send(ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
|
public void send(ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
|
||||||
{
|
{
|
||||||
|
callback.succeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -334,7 +334,7 @@ public class EventSourceServletTest
|
||||||
{
|
{
|
||||||
// Read and discard the HTTP response
|
// Read and discard the HTTP response
|
||||||
InputStream input = socket.getInputStream();
|
InputStream input = socket.getInputStream();
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
|
||||||
String line = reader.readLine();
|
String line = reader.readLine();
|
||||||
while (line != null)
|
while (line != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
//
|
|
||||||
// ========================================================================
|
|
||||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// All rights reserved. This program and the accompanying materials
|
|
||||||
// are made available under the terms of the Eclipse Public License v1.0
|
|
||||||
// and Apache License v2.0 which accompanies this distribution.
|
|
||||||
//
|
|
||||||
// The Eclipse Public License is available at
|
|
||||||
// http://www.eclipse.org/legal/epl-v10.html
|
|
||||||
//
|
|
||||||
// The Apache License v2.0 is available at
|
|
||||||
// http://www.opensource.org/licenses/apache2.0.php
|
|
||||||
//
|
|
||||||
// You may elect to redistribute this code under either of these licenses.
|
|
||||||
// ========================================================================
|
|
||||||
//
|
|
||||||
|
|
||||||
package org.eclipse.jetty.websocket;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.server.Connector;
|
|
||||||
import org.eclipse.jetty.server.Server;
|
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
|
||||||
import org.eclipse.jetty.websocket.helper.CaptureSocket;
|
|
||||||
import org.eclipse.jetty.websocket.helper.SafariD00;
|
|
||||||
import org.eclipse.jetty.websocket.helper.WebSocketCaptureServlet;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class WebSocketMinVersionTest
|
|
||||||
{
|
|
||||||
private Server server;
|
|
||||||
private WebSocketCaptureServlet servlet;
|
|
||||||
private URI serverUri;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void initLogging()
|
|
||||||
{
|
|
||||||
// Configure Logging
|
|
||||||
// System.setProperty("org.eclipse.jetty.util.log.class",StdErrLog.class.getName());
|
|
||||||
System.setProperty("org.eclipse.jetty.websocket.helper.LEVEL","DEBUG");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void startServer() throws Exception
|
|
||||||
{
|
|
||||||
// Configure Server
|
|
||||||
server = new Server(0);
|
|
||||||
|
|
||||||
ServletContextHandler context = new ServletContextHandler();
|
|
||||||
context.setContextPath("/");
|
|
||||||
server.setHandler(context);
|
|
||||||
|
|
||||||
// Serve capture servlet
|
|
||||||
servlet = new WebSocketCaptureServlet();
|
|
||||||
ServletHolder holder = new ServletHolder(servlet);
|
|
||||||
holder.setInitParameter("minVersion","8");
|
|
||||||
context.addServlet(holder,"/");
|
|
||||||
|
|
||||||
// Start Server
|
|
||||||
server.start();
|
|
||||||
|
|
||||||
Connector conn = server.getConnectors()[0];
|
|
||||||
String host = conn.getHost();
|
|
||||||
if (host == null)
|
|
||||||
{
|
|
||||||
host = "localhost";
|
|
||||||
}
|
|
||||||
int port = conn.getLocalPort();
|
|
||||||
serverUri = new URI(String.format("ws://%s:%d/",host,port));
|
|
||||||
// System.out.printf("Server URI: %s%n",serverUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAttemptUpgrade() throws Exception
|
|
||||||
{
|
|
||||||
SafariD00 safari = new SafariD00(serverUri);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
safari.connect();
|
|
||||||
safari.issueHandshake();
|
|
||||||
Assert.fail("Expected upgrade failure");
|
|
||||||
}
|
|
||||||
catch(IllegalStateException e) {
|
|
||||||
String respHeader = e.getMessage();
|
|
||||||
Assert.assertThat("Response Header", respHeader, containsString("HTTP/1.1 400 Unsupported websocket version specification"));
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// System.out.println("Closing client socket");
|
|
||||||
safari.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void threadSleep(int dur, TimeUnit unit) throws InterruptedException
|
|
||||||
{
|
|
||||||
long ms = TimeUnit.MILLISECONDS.convert(dur,unit);
|
|
||||||
Thread.sleep(ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void stopServer() throws Exception
|
|
||||||
{
|
|
||||||
server.stop();
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue