merge from master
This commit is contained in:
commit
381615f52d
11
VERSION.txt
11
VERSION.txt
|
@ -1,5 +1,16 @@
|
|||
jetty-8.1.3-SNAPSHOT
|
||||
|
||||
jetty-7.6.2.v20120302 - 02 March 2012
|
||||
+ 370387 SafariWebsocketDraft0Test failure during build.
|
||||
+ 371168 Update ClientCrossContextSessionTest
|
||||
+ 372093 handle quotes in Require-Bundle manifest string
|
||||
+ 372457 Big response + slow clients + pipelined requests cause Jetty spinning
|
||||
and eventually closing connections. Added a TODO for a method renaming that
|
||||
will happen in the next major release (to avoid break implementers).
|
||||
+ 372487 JDBCSessionManager does not work with Oracle
|
||||
+ 372806 Command line should accept relative paths for xml config files
|
||||
+ JETTY-1489 WebAppProvider attempts to deploy .svn folder
|
||||
|
||||
jetty-8.1.2.v20120302 - 02 March 2012
|
||||
+ 370387 SafariWebsocketDraft0Test failure during build.
|
||||
+ 371168 Update ClientCrossContextSessionTest
|
||||
|
|
|
@ -106,7 +106,7 @@ public class GzipResponseWrapper extends HttpServletResponseWrapper
|
|||
}
|
||||
|
||||
if ((_gzStream==null || _gzStream._out==null) &&
|
||||
(_mimeTypes==null && "application/gzip".equalsIgnoreCase(ct) ||
|
||||
(_mimeTypes==null && ct!=null && ct.contains("gzip") ||
|
||||
_mimeTypes!=null && (ct==null||!_mimeTypes.contains(StringUtil.asciiToLowerCase(ct)))))
|
||||
{
|
||||
noGzip();
|
||||
|
|
|
@ -385,7 +385,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo
|
|||
try
|
||||
{
|
||||
updateKey();
|
||||
this.wait(timeoutMs>=0?(end-now):10000);
|
||||
this.wait(timeoutMs>0?(end-now):10000);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
|
@ -433,7 +433,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo
|
|||
try
|
||||
{
|
||||
updateKey();
|
||||
this.wait(timeoutMs>=0?(end-now):10000);
|
||||
this.wait(timeoutMs>0?(end-now):10000);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
|
@ -455,14 +455,14 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @see org.eclipse.jetty.io.AsyncEndPoint#scheduleWrite()
|
||||
*/
|
||||
public void scheduleWrite()
|
||||
{
|
||||
if (_writable==true)
|
||||
if (_writable)
|
||||
LOG.debug("Required scheduleWrite {}",this);
|
||||
|
||||
_writable=false;
|
||||
|
@ -687,14 +687,15 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo
|
|||
{
|
||||
try
|
||||
{
|
||||
if (_key!=null)
|
||||
_key.cancel();
|
||||
SelectionKey key = _key;
|
||||
if (key!=null)
|
||||
key.cancel();
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
super.close();
|
||||
|
|
|
@ -285,7 +285,7 @@ public class IOTest
|
|||
Socket client;
|
||||
Socket server;
|
||||
|
||||
connector = new ServerSocket(9123);
|
||||
connector = new ServerSocket(0);
|
||||
client = new Socket("127.0.0.1",connector.getLocalPort());
|
||||
server = connector.accept();
|
||||
client.setTcpNoDelay(true);
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<?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/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-project</artifactId>
|
||||
<version>7.6.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.eclipse.jetty.npn</groupId>
|
||||
<artifactId>npn-api</artifactId>
|
||||
<name>Jetty :: Next Protocol Negotiation :: API</name>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.npn;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
/**
|
||||
* <p>{@link NextProtoNego} provides an API to applications that want to make use of the
|
||||
* <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next Protocol Negotiation</a>.</p>
|
||||
* <p>The NPN extension is only available when using the TLS protocol, therefore applications must
|
||||
* ensure that the TLS protocol is used:</p>
|
||||
* <pre>
|
||||
* SSLContext context = SSLContext.getInstance("TLSv1");
|
||||
* </pre>
|
||||
* <p>Refer to the
|
||||
* <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext">list
|
||||
* of standard SSLContext protocol names</a> for further information on TLS protocol versions supported.</p>
|
||||
* <p>Applications must register instances of either {@link SSLSocket} or {@link SSLEngine} with a
|
||||
* {@link ClientProvider} or with a {@link ServerProvider}, depending whether they are on client or
|
||||
* server side.</p>
|
||||
* <p>The NPN implementation will invoke the provider callbacks to allow applications to interact
|
||||
* with the negotiation of the next protocol.</p>
|
||||
* <p>Client side typical usage:</p>
|
||||
* <pre>
|
||||
* SSLSocket sslSocket = ...;
|
||||
* NextProtoNego.put(sslSocket, new NextProtoNego.ClientProvider()
|
||||
* {
|
||||
* @Override
|
||||
* public boolean supports()
|
||||
* {
|
||||
* return true;
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public void unsupported()
|
||||
* {
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public String selectProtocol(List<String> protocols)
|
||||
* {
|
||||
* return protocols.get(0);
|
||||
* }
|
||||
* });
|
||||
* </pre>
|
||||
* <p>Server side typical usage:</p>
|
||||
* <pre>
|
||||
* SSLSocket sslSocket = ...;
|
||||
* NextProtoNego.put(sslSocket, new NextProtoNego.ServerProvider()
|
||||
* {
|
||||
* @Override
|
||||
* public void unsupported()
|
||||
* {
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public List<String> protocols()
|
||||
* {
|
||||
* return Arrays.asList("http/1.1");
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public void protocolSelected(String protocol)
|
||||
* {
|
||||
* System.out.println("Protocol Selected is: " + protocol);
|
||||
* }
|
||||
* });
|
||||
* </pre>
|
||||
* <p>There is no need to unregister {@link SSLSocket} or {@link SSLEngine} instances, as they
|
||||
* are kept in a {@link WeakHashMap} and will be garbage collected when the application does not
|
||||
* hard reference them anymore.</p>
|
||||
* <p>In order to help application development, you can set the {@link NextProtoNego#debug} field
|
||||
* to {@code true} to have debug code printed to {@link System#err}.</p>
|
||||
*/
|
||||
public class NextProtoNego
|
||||
{
|
||||
/**
|
||||
* <p>Enables debug logging on {@link System#err}.</p>
|
||||
*/
|
||||
public static boolean debug = false;
|
||||
|
||||
private static Map<Object, Provider> objects = Collections.synchronizedMap(new WeakHashMap<Object, Provider>());
|
||||
|
||||
private NextProtoNego()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Registers a SSLSocket with a provider.</p>
|
||||
*
|
||||
* @param socket the socket to register with the provider
|
||||
* @param provider the provider to register with the socket
|
||||
*/
|
||||
public static void put(SSLSocket socket, Provider provider)
|
||||
{
|
||||
objects.put(socket, provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param socket a socket registered with {@link #put(SSLSocket, Provider)}
|
||||
* @return the provider registered with the given socket
|
||||
*/
|
||||
public static Provider get(SSLSocket socket)
|
||||
{
|
||||
return objects.get(socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Registers a SSLEngine with a provider.</p>
|
||||
*
|
||||
* @param engine the engine to register with the provider
|
||||
* @param provider the provider to register with the engine
|
||||
*/
|
||||
public static void put(SSLEngine engine, Provider provider)
|
||||
{
|
||||
objects.put(engine, provider);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param engine an engine registered with {@link #put(SSLEngine, Provider)}
|
||||
* @return the provider registered with the given engine
|
||||
*/
|
||||
public static Provider get(SSLEngine engine)
|
||||
{
|
||||
return objects.get(engine);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Base, empty, interface for providers.</p>
|
||||
*/
|
||||
public interface Provider
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>The client-side provider interface that applications must implement to interact
|
||||
* with the negotiation of the next protocol.</p>
|
||||
*/
|
||||
public interface ClientProvider extends Provider
|
||||
{
|
||||
/**
|
||||
* <p>Callback invoked to let the implementation know whether an
|
||||
* empty NPN extension should be added to a ClientHello SSL message.</p>
|
||||
*
|
||||
* @return true to add the NPN extension, false otherwise
|
||||
*/
|
||||
public boolean supports();
|
||||
|
||||
/**
|
||||
* <p>Callback invoked to let the application know that the server does
|
||||
* not support NPN.</p>
|
||||
*/
|
||||
public void unsupported();
|
||||
|
||||
/**
|
||||
* <p>Callback invoked to let the application select a protocol
|
||||
* among the ones sent by the server.</p>
|
||||
*
|
||||
* @param protocols the protocols sent by the server
|
||||
* @return the protocol selected by the application, or null if the
|
||||
* NextProtocol SSL message should not be sent to the server
|
||||
*/
|
||||
public String selectProtocol(List<String> protocols);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>The server-side provider interface that applications must implement to interact
|
||||
* with the negotiation of the next protocol.</p>
|
||||
*/
|
||||
public interface ServerProvider extends Provider
|
||||
{
|
||||
/**
|
||||
* <p>Callback invoked to let the application know that the client does not
|
||||
* support NPN.</p>
|
||||
*/
|
||||
public void unsupported();
|
||||
|
||||
/**
|
||||
* <p>Callback invoked to let the implementation know the list
|
||||
* of protocols that should be added to an NPN extension in a
|
||||
* ServerHello SSL message.</p>
|
||||
* <p>This callback is invoked only if the client sent a NPN extension.</p>
|
||||
*
|
||||
* @return the list of protocols, or null if no NPN extension
|
||||
* should be sent to the client
|
||||
*/
|
||||
public List<String> protocols();
|
||||
|
||||
/**
|
||||
* <p>Callback invoked to let the application know the protocol selected
|
||||
* by the client.</p>
|
||||
* <p>This callback is invoked only if the client sent a NextProtocol SSL message.</p>
|
||||
*
|
||||
* @param protocol the selected protocol
|
||||
*/
|
||||
public void protocolSelected(String protocol);
|
||||
}
|
||||
}
|
|
@ -30,7 +30,6 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import org.eclipse.jetty.continuation.Continuation;
|
||||
import org.eclipse.jetty.continuation.ContinuationThrowable;
|
||||
import org.eclipse.jetty.continuation.ContinuationListener;
|
||||
import org.eclipse.jetty.http.PathMap;
|
||||
import org.eclipse.jetty.io.AsyncEndPoint;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
|
@ -940,8 +939,10 @@ public class AsyncContinuation implements AsyncContext, Continuation
|
|||
synchronized (this)
|
||||
{
|
||||
doSuspend(context,request,response);
|
||||
if ( request instanceof HttpServletRequest)
|
||||
_event._pathInContext=URIUtil.addPaths(((HttpServletRequest)request).getServletPath(),((HttpServletRequest)request).getPathInfo());
|
||||
if (request instanceof HttpServletRequest)
|
||||
{
|
||||
_event._pathInContext = URIUtil.addPaths(((HttpServletRequest)request).getServletPath(),((HttpServletRequest)request).getPathInfo());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -793,7 +793,7 @@ public class Response implements HttpServletResponse
|
|||
if (isCommitted() || _connection.isIncluding())
|
||||
return;
|
||||
_connection._generator.setContentLength(len);
|
||||
if (len>=0)
|
||||
if (len>0)
|
||||
{
|
||||
_connection.getResponseFields().putLongField(HttpHeaders.CONTENT_LENGTH, len);
|
||||
if (_connection._generator.isAllContentWritten())
|
||||
|
|
|
@ -24,8 +24,8 @@ import org.eclipse.jetty.server.Request;
|
|||
import org.eclipse.jetty.server.Server;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** A <code>HandlerContainer</code> that allows a hot swap
|
||||
* of a wrapped handler.
|
||||
/**
|
||||
* A <code>HandlerContainer</code> that allows a hot swap of a wrapped handler.
|
||||
*
|
||||
*/
|
||||
public class HotSwapHandler extends AbstractHandlerContainer
|
||||
|
@ -48,113 +48,106 @@ public class HotSwapHandler extends AbstractHandlerContainer
|
|||
{
|
||||
return _handler;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return Returns the handlers.
|
||||
*/
|
||||
public Handler[] getHandlers()
|
||||
{
|
||||
return new Handler[] {_handler};
|
||||
return new Handler[]
|
||||
{ _handler };
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param handler Set the {@link Handler} which should be wrapped.
|
||||
* @param handler
|
||||
* Set the {@link Handler} which should be wrapped.
|
||||
*/
|
||||
public void setHandler(Handler handler)
|
||||
{
|
||||
if (handler == null)
|
||||
throw new IllegalArgumentException("Parameter handler is null.");
|
||||
try
|
||||
{
|
||||
Handler old_handler = _handler;
|
||||
_handler = handler;
|
||||
if (handler!=null)
|
||||
{
|
||||
handler.setServer(getServer());
|
||||
if (isStarted())
|
||||
handler.start();
|
||||
}
|
||||
Server server = getServer();
|
||||
handler.setServer(server);
|
||||
addBean(handler);
|
||||
|
||||
if (getServer()!=null)
|
||||
getServer().getContainer().update(this, old_handler, handler, "handler");
|
||||
if (server != null)
|
||||
server.getContainer().update(this,old_handler,handler,"handler");
|
||||
|
||||
|
||||
// if there is an old handler and it was started, stop it
|
||||
if (old_handler != null && isStarted())
|
||||
if (old_handler != null)
|
||||
{
|
||||
old_handler.stop();
|
||||
removeBean(old_handler);
|
||||
}
|
||||
|
||||
}
|
||||
catch(RuntimeException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch(Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
/*
|
||||
* @see org.eclipse.thread.AbstractLifeCycle#doStart()
|
||||
*/
|
||||
@Override
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
if (_handler!=null)
|
||||
_handler.start();
|
||||
super.doStart();
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
/*
|
||||
* @see org.eclipse.thread.AbstractLifeCycle#doStop()
|
||||
*/
|
||||
@Override
|
||||
protected void doStop() throws Exception
|
||||
{
|
||||
super.doStop();
|
||||
if (_handler!=null)
|
||||
_handler.stop();
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
/*
|
||||
* @see org.eclipse.jetty.server.server.EventHandler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
|
||||
*/
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
if (_handler!=null && isStarted())
|
||||
if (_handler != null && isStarted())
|
||||
{
|
||||
_handler.handle(target,baseRequest, request, response);
|
||||
_handler.handle(target,baseRequest,request,response);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public void setServer(Server server)
|
||||
{
|
||||
Server old_server=getServer();
|
||||
if (server==old_server)
|
||||
Server old_server = getServer();
|
||||
if (server == old_server)
|
||||
return;
|
||||
|
||||
|
||||
if (isRunning())
|
||||
throw new IllegalStateException(RUNNING);
|
||||
|
||||
|
||||
super.setServer(server);
|
||||
|
||||
Handler h=getHandler();
|
||||
if (h!=null)
|
||||
|
||||
Handler h = getHandler();
|
||||
if (h != null)
|
||||
h.setServer(server);
|
||||
|
||||
if (server!=null && server!=old_server)
|
||||
server.getContainer().update(this, null,_handler, "handler");
|
||||
|
||||
if (server != null && server != old_server)
|
||||
server.getContainer().update(this,null,_handler,"handler");
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@SuppressWarnings(
|
||||
{ "rawtypes", "unchecked" })
|
||||
@Override
|
||||
protected Object expandChildren(Object list, Class byClass)
|
||||
{
|
||||
|
@ -167,8 +160,8 @@ public class HotSwapHandler extends AbstractHandlerContainer
|
|||
{
|
||||
if (!isStopped())
|
||||
throw new IllegalStateException("!STOPPED");
|
||||
Handler child=getHandler();
|
||||
if (child!=null)
|
||||
Handler child = getHandler();
|
||||
if (child != null)
|
||||
{
|
||||
setHandler(null);
|
||||
child.destroy();
|
||||
|
|
|
@ -79,7 +79,16 @@ public class SelectChannelConnector extends AbstractNIOConnector
|
|||
addBean(_manager,true);
|
||||
setAcceptors(Math.max(1,(Runtime.getRuntime().availableProcessors()+3)/4));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setThreadPool(ThreadPool pool)
|
||||
{
|
||||
super.setThreadPool(pool);
|
||||
// preserve start order
|
||||
removeBean(_manager);
|
||||
addBean(_manager,true);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public void accept(int acceptorID) throws IOException
|
||||
|
|
|
@ -39,7 +39,9 @@ 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.Matchers;
|
||||
import org.junit.Test;
|
||||
import org.junit.matchers.JUnitMatchers;
|
||||
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -1048,10 +1050,13 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
else
|
||||
out.println(avail);
|
||||
|
||||
for (int i=0;i<avail;i++)
|
||||
while (avail>0)
|
||||
{
|
||||
buf+=(char)in.read();
|
||||
|
||||
avail=in.available();
|
||||
avail=in.available();
|
||||
}
|
||||
|
||||
|
||||
out.println(avail);
|
||||
out.println(buf);
|
||||
out.close();
|
||||
|
@ -1098,9 +1103,9 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
// skip header
|
||||
while(reader.readLine().length()>0);
|
||||
assertEquals(10,Integer.parseInt(reader.readLine()));
|
||||
assertThat(Integer.parseInt(reader.readLine()),Matchers.greaterThan(0));
|
||||
assertEquals(0,Integer.parseInt(reader.readLine()));
|
||||
assertEquals(20,Integer.parseInt(reader.readLine()));
|
||||
assertThat(Integer.parseInt(reader.readLine()),Matchers.greaterThan(0));
|
||||
assertEquals(0,Integer.parseInt(reader.readLine()));
|
||||
assertEquals("1234567890abcdefghijklmnopqrst",reader.readLine());
|
||||
|
||||
|
|
|
@ -441,6 +441,20 @@ public class ResponseTest
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testZeroContent () throws Exception
|
||||
{
|
||||
Response response = new Response (new TestHttpConnection(connector, new ByteArrayEndPoint(), connector.getServer()));
|
||||
PrintWriter writer = response.getWriter();
|
||||
response.setContentLength(0);
|
||||
assertTrue(!response.isCommitted());
|
||||
assertTrue(!writer.checkError());
|
||||
writer.print("");
|
||||
assertTrue(!writer.checkError());
|
||||
assertTrue(response.isCommitted());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHead() throws Exception
|
||||
{
|
||||
|
|
|
@ -178,22 +178,26 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
|
|||
|
||||
private class AlwaysTrustManager implements X509TrustManager
|
||||
{
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers()
|
||||
{
|
||||
return null;
|
||||
return new X509Certificate[]{};
|
||||
}
|
||||
}
|
||||
|
||||
private static class ServerHandler extends AbstractHandler
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
|
||||
{
|
||||
request.setHandled(true);
|
||||
|
|
|
@ -51,16 +51,19 @@ public class SSLCloseTest extends TestCase
|
|||
private static AsyncEndPoint __endp;
|
||||
private static class CredulousTM implements TrustManager, X509TrustManager
|
||||
{
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers()
|
||||
{
|
||||
return null;
|
||||
return new X509Certificate[]{};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException
|
||||
{
|
||||
return;
|
||||
|
@ -129,6 +132,7 @@ public class SSLCloseTest extends TestCase
|
|||
|
||||
private static class WriteHandler extends AbstractHandler
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
try
|
||||
|
|
|
@ -27,6 +27,7 @@ import javax.net.ssl.TrustManagerFactory;
|
|||
import org.eclipse.jetty.server.HttpServerTestBase;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
|
@ -135,4 +136,13 @@ public class SelectChannelServerSslTest extends HttpServerTestBase
|
|||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Ignore
|
||||
public void testAvailable() throws Exception
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import javax.net.ssl.TrustManagerFactory;
|
|||
import org.eclipse.jetty.server.HttpServerTestBase;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
|
@ -75,4 +76,10 @@ public class SslSocketServerTest extends HttpServerTestBase
|
|||
// TODO this test uses URL, so noop for now
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Ignore
|
||||
public void testAvailable() throws Exception
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package org.eclipse.jetty.servlet;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
|
@ -10,8 +14,7 @@ import javax.servlet.ServletException;
|
|||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.eclipse.jetty.server.AsyncContinuation;
|
||||
|
@ -41,12 +44,13 @@ public class AsyncContextTest
|
|||
@Before
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
_connector.setMaxIdleTime(3000000);
|
||||
_connector.setMaxIdleTime(30000);
|
||||
_server.setConnectors(new Connector[]
|
||||
{ _connector });
|
||||
|
||||
_contextHandler.setContextPath("/");
|
||||
_contextHandler.addServlet(new ServletHolder(new TestServlet()),"/servletPath");
|
||||
_contextHandler.addServlet(new ServletHolder(new TestServlet()),"/path with spaces/servletPath");
|
||||
_contextHandler.addServlet(new ServletHolder(new TestServlet2()),"/servletPath2");
|
||||
_contextHandler.addServlet(new ServletHolder(new ForwardingServlet()),"/forward");
|
||||
_contextHandler.addServlet(new ServletHolder(new AsyncDispatchingServlet()),"/dispatchingServlet");
|
||||
|
@ -61,112 +65,102 @@ public class AsyncContextTest
|
|||
|
||||
@Test
|
||||
public void testSimpleAsyncContext() throws Exception
|
||||
{
|
||||
{
|
||||
String request = "GET /servletPath HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
+ "Connection: close\r\n" + "\r\n";
|
||||
String responseString = _connector.getResponses(request);
|
||||
|
||||
System.err.println("REsponse string="+responseString);
|
||||
BufferedReader br = new BufferedReader(new StringReader(responseString));
|
||||
|
||||
Assert.assertEquals("HTTP/1.1 200 OK",br.readLine());
|
||||
|
||||
br.readLine();// connection close
|
||||
br.readLine();// server
|
||||
br.readLine();// empty
|
||||
|
||||
Assert.assertEquals("servlet gets right path","doGet:getServletPath:/servletPath", br.readLine());
|
||||
Assert.assertEquals("async context gets right path in get","doGet:async:getServletPath:/servletPath", br.readLine());
|
||||
Assert.assertEquals("async context gets right path in async","async:run:attr:servletPath:/servletPath", br.readLine());
|
||||
|
||||
BufferedReader br = parseHeader(responseString);
|
||||
|
||||
Assert.assertEquals("servlet gets right path","doGet:getServletPath:/servletPath",br.readLine());
|
||||
Assert.assertEquals("async context gets right path in get","doGet:async:getServletPath:/servletPath",br.readLine());
|
||||
Assert.assertEquals("async context gets right path in async","async:run:attr:servletPath:/servletPath",br.readLine());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDispatchAsyncContext() throws Exception
|
||||
{
|
||||
{
|
||||
String request = "GET /servletPath?dispatch=true HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
+ "Connection: close\r\n" + "\r\n";
|
||||
String responseString = _connector.getResponses(request);
|
||||
|
||||
BufferedReader br = new BufferedReader(new StringReader(responseString));
|
||||
|
||||
Assert.assertEquals("HTTP/1.1 200 OK",br.readLine());
|
||||
|
||||
br.readLine();// connection close
|
||||
br.readLine();// server
|
||||
br.readLine();// empty
|
||||
|
||||
Assert.assertEquals("servlet gets right path","doGet:getServletPath:/servletPath2", br.readLine());
|
||||
Assert.assertEquals("async context gets right path in get","doGet:async:getServletPath:/servletPath2", br.readLine());
|
||||
Assert.assertEquals("servlet path attr is original","async:run:attr:servletPath:/servletPath", br.readLine());
|
||||
Assert.assertEquals("path info attr is correct","async:run:attr:pathInfo:null", br.readLine());
|
||||
Assert.assertEquals("query string attr is correct","async:run:attr:queryString:dispatch=true", br.readLine());
|
||||
Assert.assertEquals("context path attr is correct","async:run:attr:contextPath:", br.readLine());
|
||||
Assert.assertEquals("request uri attr is correct","async:run:attr:requestURI:/servletPath", br.readLine());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleWithContextAsyncContext() throws Exception
|
||||
{
|
||||
_contextHandler.setContextPath("/foo");
|
||||
|
||||
String request = "GET /foo/servletPath HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
+ "Connection: close\r\n" + "\r\n";
|
||||
String responseString = _connector.getResponses(request);
|
||||
|
||||
BufferedReader br = new BufferedReader(new StringReader(responseString));
|
||||
|
||||
Assert.assertEquals("HTTP/1.1 200 OK",br.readLine());
|
||||
|
||||
br.readLine();// connection close
|
||||
br.readLine();// server
|
||||
br.readLine();// empty
|
||||
|
||||
Assert.assertEquals("servlet gets right path","doGet:getServletPath:/servletPath", br.readLine());
|
||||
Assert.assertEquals("async context gets right path in get","doGet:async:getServletPath:/servletPath", br.readLine());
|
||||
Assert.assertEquals("async context gets right path in async","async:run:attr:servletPath:/servletPath", br.readLine());
|
||||
BufferedReader br = parseHeader(responseString);
|
||||
|
||||
Assert.assertEquals("servlet gets right path","doGet:getServletPath:/servletPath2",br.readLine());
|
||||
Assert.assertEquals("async context gets right path in get","doGet:async:getServletPath:/servletPath2",br.readLine());
|
||||
Assert.assertEquals("servlet path attr is original","async:run:attr:servletPath:/servletPath",br.readLine());
|
||||
Assert.assertEquals("path info attr is correct","async:run:attr:pathInfo:null",br.readLine());
|
||||
Assert.assertEquals("query string attr is correct","async:run:attr:queryString:dispatch=true",br.readLine());
|
||||
Assert.assertEquals("context path attr is correct","async:run:attr:contextPath:",br.readLine());
|
||||
Assert.assertEquals("request uri attr is correct","async:run:attr:requestURI:/servletPath",br.readLine());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDispatchWithContextAsyncContext() throws Exception
|
||||
{
|
||||
_contextHandler.setContextPath("/foo");
|
||||
|
||||
String request = "GET /foo/servletPath?dispatch=true HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
public void testDispatchAsyncContextEncodedPathAndQueryString() throws Exception
|
||||
{
|
||||
String request = "GET /path%20with%20spaces/servletPath?dispatch=true&queryStringWithEncoding=space%20space HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
+ "Connection: close\r\n" + "\r\n";
|
||||
String responseString = _connector.getResponses(request);
|
||||
|
||||
System.out.println(responseString);
|
||||
BufferedReader br = parseHeader(responseString);
|
||||
|
||||
BufferedReader br = new BufferedReader(new StringReader(responseString));
|
||||
|
||||
Assert.assertEquals("HTTP/1.1 200 OK",br.readLine());
|
||||
|
||||
br.readLine();// connection close
|
||||
br.readLine();// server
|
||||
br.readLine();// empty
|
||||
|
||||
Assert.assertEquals("servlet gets right path","doGet:getServletPath:/servletPath2", br.readLine());
|
||||
Assert.assertEquals("async context gets right path in get","doGet:async:getServletPath:/servletPath2", br.readLine());
|
||||
Assert.assertEquals("servlet path attr is original","async:run:attr:servletPath:/servletPath", br.readLine());
|
||||
Assert.assertEquals("path info attr is correct","async:run:attr:pathInfo:null", br.readLine());
|
||||
Assert.assertEquals("query string attr is correct","async:run:attr:queryString:dispatch=true", br.readLine());
|
||||
Assert.assertEquals("context path attr is correct","async:run:attr:contextPath:/foo", br.readLine());
|
||||
Assert.assertEquals("request uri attr is correct","async:run:attr:requestURI:/foo/servletPath", br.readLine());
|
||||
}
|
||||
assertThat("servlet gets right path",br.readLine(),equalTo("doGet:getServletPath:/servletPath2"));
|
||||
assertThat("async context gets right path in get",br.readLine(), equalTo("doGet:async:getServletPath:/servletPath2"));
|
||||
assertThat("servlet path attr is original",br.readLine(),equalTo("async:run:attr:servletPath:/path with spaces/servletPath"));
|
||||
assertThat("path info attr is correct",br.readLine(),equalTo("async:run:attr:pathInfo:null"));
|
||||
assertThat("query string attr is correct",br.readLine(),equalTo("async:run:attr:queryString:dispatch=true&queryStringWithEncoding=space%20space"));
|
||||
assertThat("context path attr is correct",br.readLine(),equalTo("async:run:attr:contextPath:"));
|
||||
assertThat("request uri attr is correct",br.readLine(),equalTo("async:run:attr:requestURI:/path%20with%20spaces/servletPath"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleWithContextAsyncContext() throws Exception
|
||||
{
|
||||
_contextHandler.setContextPath("/foo");
|
||||
|
||||
String request = "GET /foo/servletPath HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
+ "Connection: close\r\n" + "\r\n";
|
||||
|
||||
String responseString = _connector.getResponses(request);
|
||||
|
||||
BufferedReader br = parseHeader(responseString);
|
||||
|
||||
Assert.assertEquals("servlet gets right path","doGet:getServletPath:/servletPath",br.readLine());
|
||||
Assert.assertEquals("async context gets right path in get","doGet:async:getServletPath:/servletPath",br.readLine());
|
||||
Assert.assertEquals("async context gets right path in async","async:run:attr:servletPath:/servletPath",br.readLine());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDispatchWithContextAsyncContext() throws Exception
|
||||
{
|
||||
_contextHandler.setContextPath("/foo");
|
||||
|
||||
String request = "GET /foo/servletPath?dispatch=true HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
+ "Connection: close\r\n" + "\r\n";
|
||||
|
||||
String responseString = _connector.getResponses(request);
|
||||
|
||||
BufferedReader br = parseHeader(responseString);
|
||||
|
||||
Assert.assertEquals("servlet gets right path","doGet:getServletPath:/servletPath2",br.readLine());
|
||||
Assert.assertEquals("async context gets right path in get","doGet:async:getServletPath:/servletPath2",br.readLine());
|
||||
Assert.assertEquals("servlet path attr is original","async:run:attr:servletPath:/servletPath",br.readLine());
|
||||
Assert.assertEquals("path info attr is correct","async:run:attr:pathInfo:null",br.readLine());
|
||||
Assert.assertEquals("query string attr is correct","async:run:attr:queryString:dispatch=true",br.readLine());
|
||||
Assert.assertEquals("context path attr is correct","async:run:attr:contextPath:/foo",br.readLine());
|
||||
Assert.assertEquals("request uri attr is correct","async:run:attr:requestURI:/foo/servletPath",br.readLine());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDispatch() throws Exception
|
||||
{
|
||||
String request = "GET /forward HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Connection: close\r\n"
|
||||
+ "\r\n";
|
||||
|
||||
String responseString = _connector.getResponses(request);
|
||||
|
||||
BufferedReader br = new BufferedReader(new StringReader(responseString));
|
||||
assertEquals("HTTP/1.1 200 OK",br.readLine());
|
||||
|
||||
br.readLine();// connection close
|
||||
br.readLine();// server
|
||||
br.readLine();// empty
|
||||
BufferedReader br = parseHeader(responseString);
|
||||
|
||||
assertThat("!ForwardingServlet",br.readLine(),equalTo("Dispatched back to ForwardingServlet"));
|
||||
}
|
||||
|
@ -176,16 +170,24 @@ public class AsyncContextTest
|
|||
{
|
||||
String request = "GET /forward?dispatchRequestResponse=true HTTP/1.1\r\n" + "Host: localhost\r\n"
|
||||
+ "Content-Type: application/x-www-form-urlencoded\r\n" + "Connection: close\r\n" + "\r\n";
|
||||
|
||||
String responseString = _connector.getResponses(request);
|
||||
|
||||
BufferedReader br = parseHeader(responseString);
|
||||
|
||||
assertThat("!AsyncDispatchingServlet",br.readLine(),equalTo("Dispatched back to AsyncDispatchingServlet"));
|
||||
}
|
||||
|
||||
private BufferedReader parseHeader(String responseString) throws IOException
|
||||
{
|
||||
BufferedReader br = new BufferedReader(new StringReader(responseString));
|
||||
|
||||
assertEquals("HTTP/1.1 200 OK",br.readLine());
|
||||
|
||||
br.readLine();// connection close
|
||||
br.readLine();// server
|
||||
br.readLine();// empty
|
||||
|
||||
assertThat("!AsyncDispatchingServlet",br.readLine(),equalTo("Dispatched back to AsyncDispatchingServlet"));
|
||||
return br;
|
||||
}
|
||||
|
||||
private class ForwardingServlet extends HttpServlet
|
||||
|
@ -206,28 +208,32 @@ public class AsyncContextTest
|
|||
}
|
||||
}
|
||||
|
||||
private class AsyncDispatchingServlet extends HttpServlet
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private class AsyncDispatchingServlet extends HttpServlet
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, final HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
Request request = (Request)req;
|
||||
if (request.getDispatcherType() == DispatcherType.ASYNC)
|
||||
{
|
||||
response.getOutputStream().print("Dispatched back to AsyncDispatchingServlet");
|
||||
}
|
||||
else
|
||||
{
|
||||
final AsyncContext asyncContext;
|
||||
if (request.getParameter("dispatchRequestResponse") != null)
|
||||
asyncContext = request.startAsync(request,response);
|
||||
else
|
||||
asyncContext = request.startAsync();
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, final HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
Request request = (Request)req;
|
||||
if (request.getDispatcherType() == DispatcherType.ASYNC)
|
||||
{
|
||||
response.getOutputStream().print("Dispatched back to AsyncDispatchingServlet");
|
||||
}
|
||||
else
|
||||
{
|
||||
final AsyncContext asyncContext;
|
||||
if (request.getParameter("dispatchRequestResponse") != null)
|
||||
{
|
||||
asyncContext = request.startAsync(request,response);
|
||||
}
|
||||
else
|
||||
{
|
||||
asyncContext = request.startAsync();
|
||||
}
|
||||
|
||||
new Thread(new DispatchingRunnable(asyncContext)).start();
|
||||
}
|
||||
new Thread(new DispatchingRunnable(asyncContext)).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.util.ArrayList;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -287,7 +288,14 @@ public class MultiPartFilter implements Filter
|
|||
@Override
|
||||
public Map getParameterMap()
|
||||
{
|
||||
return Collections.unmodifiableMap(_params.toStringArrayMap());
|
||||
Map<String, String> cmap = new HashMap<String,String>();
|
||||
|
||||
for ( Object key : _params.keySet() )
|
||||
{
|
||||
cmap.put((String)key,getParameter((String)key));
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(cmap);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
|
|
|
@ -597,12 +597,14 @@ public class ProxyServlet implements Servlet
|
|||
{
|
||||
exchange.addRequestHeader("X-Forwarded-For",request.getRemoteAddr());
|
||||
exchange.addRequestHeader("X-Forwarded-Proto",request.getScheme());
|
||||
exchange.addRequestHeader("X-Forwarded-Host",request.getServerName());
|
||||
exchange.addRequestHeader("X-Forwarded-Host",request.getHeader("Host"));
|
||||
exchange.addRequestHeader("X-Forwarded-Server",request.getLocalName());
|
||||
}
|
||||
|
||||
if (hasContent)
|
||||
{
|
||||
exchange.setRequestContentSource(in);
|
||||
}
|
||||
|
||||
customizeExchange(exchange, request);
|
||||
|
||||
|
|
|
@ -50,7 +50,8 @@ import javax.servlet.http.HttpServletRequest;
|
|||
*/
|
||||
public class UserAgentFilter implements Filter
|
||||
{
|
||||
private Pattern _pattern;
|
||||
private static final String __defaultPattern = "(?:Mozilla[^\\(]*\\(compatible;\\s*+([^;]*);.*)|(?:.*?([^\\s]+/[^\\s]+).*)";
|
||||
private Pattern _pattern = Pattern.compile(__defaultPattern);
|
||||
private Map _agentCache = new ConcurrentHashMap();
|
||||
private int _agentCacheSize=1024;
|
||||
private String _attribute;
|
||||
|
|
|
@ -137,4 +137,26 @@ public class GzipFilterDefaultTest
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserAgentExclusion() throws Exception
|
||||
{
|
||||
GzipTester tester = new GzipTester(testingdir);
|
||||
|
||||
FilterHolder holder = tester.setContentServlet(DefaultServlet.class);
|
||||
holder.setInitParameter("excludedAgents", "foo");
|
||||
tester.setUserAgent("foo");
|
||||
|
||||
int filesize = GzipResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
|
||||
tester.prepareServerFile("file.txt",filesize);
|
||||
|
||||
try
|
||||
{
|
||||
tester.start();
|
||||
tester.assertIsResponseNotGzipCompressed("file.txt", filesize, HttpStatus.OK_200);
|
||||
}
|
||||
finally
|
||||
{
|
||||
tester.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,10 @@ public class MultipartFilterTest
|
|||
private ServletTester tester;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
|
@ -238,6 +242,65 @@ public class MultipartFilterTest
|
|||
assertTrue(response.getContent().indexOf("brown cow")>=0);
|
||||
}
|
||||
|
||||
/*
|
||||
* see the testParameterMap test
|
||||
*
|
||||
*/
|
||||
public static class TestServletParameterMap extends DumpServlet
|
||||
{
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
assertEquals("How now brown cow.", req.getParameterMap().get("strupContent-Type:"));
|
||||
|
||||
super.doPost(req, resp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the getParameterMap() call is correctly unencoding the parameters in the
|
||||
* map that it returns.
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testParameterMap() throws Exception
|
||||
{
|
||||
// generated and parsed test
|
||||
HttpTester request = new HttpTester();
|
||||
HttpTester response = new HttpTester();
|
||||
|
||||
tester.addServlet(TestServletParameterMap.class,"/test2");
|
||||
|
||||
// test GET
|
||||
request.setMethod("POST");
|
||||
request.setVersion("HTTP/1.0");
|
||||
request.setHeader("Host","tester");
|
||||
request.setURI("/context/test2");
|
||||
|
||||
String boundary="XyXyXy";
|
||||
request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
|
||||
|
||||
|
||||
String content = "--" + boundary + "\r\n"+
|
||||
"Content-Disposition: form-data; name=\"fileup\"; filename=\"Diplomsko Delo Lektorirano KONČNA.doc\"\r\n"+
|
||||
"Content-Type: application/octet-stream\r\n\r\n"+
|
||||
"How now brown cow."+
|
||||
"\r\n--" + boundary + "\r\n"+
|
||||
"Content-Disposition: form-data; name=\"strup\""+
|
||||
"Content-Type: application/octet-stream\r\n\r\n"+
|
||||
"How now brown cow."+
|
||||
"\r\n--" + boundary + "--\r\n\r\n";
|
||||
|
||||
request.setContent(content);
|
||||
|
||||
response.parse(tester.getResponses(request.generate()));
|
||||
assertTrue(response.getMethod()==null);
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
assertTrue(response.getContent().indexOf("brown cow")>=0);
|
||||
}
|
||||
|
||||
public static class DumpServlet extends HttpServlet
|
||||
{
|
||||
private static final long serialVersionUID = 201012011130L;
|
||||
|
|
|
@ -4,6 +4,7 @@ import java.io.File;
|
|||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -12,7 +13,6 @@ import javax.servlet.ServletOutputStream;
|
|||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import org.eclipse.jetty.client.ContentExchange;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
|
@ -30,21 +30,24 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
|||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
public class ProxyServletTest
|
||||
{
|
||||
private Server server;
|
||||
private Connector connector;
|
||||
private HttpClient client;
|
||||
private Server _server;
|
||||
private Connector _connector;
|
||||
private HttpClient _client;
|
||||
|
||||
public void init(HttpServlet servlet) throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
_server = new Server();
|
||||
|
||||
connector = new SelectChannelConnector();
|
||||
server.addConnector(connector);
|
||||
_connector = new SelectChannelConnector();
|
||||
_server.addConnector(_connector);
|
||||
|
||||
HandlerCollection handlers = new HandlerCollection();
|
||||
server.setHandler(handlers);
|
||||
_server.setHandler(handlers);
|
||||
|
||||
ServletContextHandler proxyCtx = new ServletContextHandler(handlers, "/proxy", ServletContextHandler.NO_SESSIONS);
|
||||
ServletHolder proxyServletHolder = new ServletHolder(new ProxyServlet()
|
||||
|
@ -66,25 +69,49 @@ public class ProxyServletTest
|
|||
handlers.addHandler(proxyCtx);
|
||||
handlers.addHandler(appCtx);
|
||||
|
||||
server.start();
|
||||
_server.start();
|
||||
|
||||
client = new HttpClient();
|
||||
client.start();
|
||||
_client = new HttpClient();
|
||||
_client.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void destroy() throws Exception
|
||||
{
|
||||
if (client != null)
|
||||
client.stop();
|
||||
if (_client != null)
|
||||
_client.stop();
|
||||
|
||||
if (server != null)
|
||||
if (_server != null)
|
||||
{
|
||||
server.stop();
|
||||
server.join();
|
||||
_server.stop();
|
||||
_server.join();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testXForwardedHostHeader() throws Exception
|
||||
{
|
||||
init(new HttpServlet()
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
PrintWriter writer = resp.getWriter();
|
||||
writer.write(req.getHeader("X-Forwarded-Host"));
|
||||
writer.flush();
|
||||
}
|
||||
});
|
||||
|
||||
String url = "http://localhost:" + _connector.getLocalPort() + "/proxy/test";
|
||||
ContentExchange exchange = new ContentExchange();
|
||||
exchange.setURL(url);
|
||||
_client.send(exchange);
|
||||
exchange.waitForDone();
|
||||
assertThat("Response expected to contain content of X-Forwarded-Host Header from the request",exchange.getResponseContent(),equalTo("localhost:"
|
||||
+ _connector.getLocalPort()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBigDownloadWithSlowReader() throws Exception
|
||||
|
@ -101,6 +128,8 @@ public class ProxyServletTest
|
|||
|
||||
init(new HttpServlet()
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
|
@ -114,7 +143,7 @@ public class ProxyServletTest
|
|||
}
|
||||
});
|
||||
|
||||
String url = "http://localhost:" + connector.getLocalPort() + "/proxy" + "/test";
|
||||
String url = "http://localhost:" + _connector.getLocalPort() + "/proxy/test";
|
||||
ContentExchange exchange = new ContentExchange(true)
|
||||
{
|
||||
@Override
|
||||
|
@ -134,7 +163,7 @@ public class ProxyServletTest
|
|||
};
|
||||
exchange.setURL(url);
|
||||
long start = System.nanoTime();
|
||||
client.send(exchange);
|
||||
_client.send(exchange);
|
||||
Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
|
||||
long elapsed = System.nanoTime() - start;
|
||||
Assert.assertEquals(HttpStatus.OK_200, exchange.getResponseStatus());
|
||||
|
|
|
@ -39,6 +39,7 @@ public class GzipTester
|
|||
{
|
||||
private Class<? extends GzipFilter> gzipFilterClass = GzipFilter.class;
|
||||
private String encoding = "ISO8859_1";
|
||||
private String userAgent = null;
|
||||
private ServletTester servletTester;
|
||||
private TestingDir testdir;
|
||||
|
||||
|
@ -64,6 +65,8 @@ public class GzipTester
|
|||
request.setVersion("HTTP/1.0");
|
||||
request.setHeader("Host","tester");
|
||||
request.setHeader("Accept-Encoding","gzip");
|
||||
if (this.userAgent != null)
|
||||
request.setHeader("User-Agent", this.userAgent);
|
||||
request.setURI("/context/" + requestedFilename);
|
||||
|
||||
// Issue the request
|
||||
|
@ -129,6 +132,8 @@ public class GzipTester
|
|||
request.setVersion("HTTP/1.0");
|
||||
request.setHeader("Host","tester");
|
||||
request.setHeader("Accept-Encoding","gzip");
|
||||
if (this.userAgent != null)
|
||||
request.setHeader("User-Agent", this.userAgent);
|
||||
request.setURI("/context/" + requestedFilename);
|
||||
|
||||
// Issue the request
|
||||
|
@ -214,6 +219,8 @@ public class GzipTester
|
|||
request.setVersion("HTTP/1.0");
|
||||
request.setHeader("Host","tester");
|
||||
request.setHeader("Accept-Encoding","gzip");
|
||||
if (this.userAgent != null)
|
||||
request.setHeader("User-Agent", this.userAgent);
|
||||
if (filename == null)
|
||||
request.setURI("/context/");
|
||||
else
|
||||
|
@ -387,6 +394,11 @@ public class GzipTester
|
|||
{
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
public void setUserAgent(String ua)
|
||||
{
|
||||
this.userAgent = ua;
|
||||
}
|
||||
|
||||
public void start() throws Exception
|
||||
{
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
<?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/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-project</artifactId>
|
||||
<version>7.6.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.eclipse.jetty.spdy</groupId>
|
||||
<artifactId>spdy-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Jetty :: SPDY :: Parent</name>
|
||||
|
||||
<modules>
|
||||
<module>spdy-core</module>
|
||||
<module>spdy-jetty</module>
|
||||
<module>spdy-jetty-http</module>
|
||||
<module>spdy-jetty-http-webapp</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>require-jdk7</id>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<requireJavaVersion>
|
||||
<version>[1.7,)</version>
|
||||
</requireJavaVersion>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-pmd-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,32 @@
|
|||
<?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/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.spdy</groupId>
|
||||
<artifactId>spdy-parent</artifactId>
|
||||
<version>7.6.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spdy-core</artifactId>
|
||||
<name>Jetty :: SPDY :: Core</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<version>${slf4j-version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* <p>A {@link ByteBuffer} pool.</p>
|
||||
* <p>Acquired buffers may be {@link #release(ByteBuffer) released} but they do not need to;
|
||||
* if they are released, they may be recycled and reused, otherwise they will be garbage
|
||||
* collected as usual.</p>
|
||||
*/
|
||||
public interface ByteBufferPool
|
||||
{
|
||||
/**
|
||||
* <p>Requests a {@link ByteBuffer} of the given size.</p>
|
||||
* <p>The returned buffer may have a bigger capacity than the size being
|
||||
* requested but it will have the limit set to the given size.</p>
|
||||
*
|
||||
* @param size the size of the buffer
|
||||
* @param direct whether the buffer must be direct or not
|
||||
* @return the requested buffer
|
||||
* @see #release(ByteBuffer)
|
||||
*/
|
||||
public ByteBuffer acquire(int size, boolean direct);
|
||||
|
||||
/**
|
||||
* <p>Returns a {@link ByteBuffer}, usually obtained with {@link #acquire(int, boolean)}
|
||||
* (but not necessarily), making it available for recycling and reuse.</p>
|
||||
*
|
||||
* @param buffer the buffer to return
|
||||
* @see #acquire(int, boolean)
|
||||
*/
|
||||
public void release(ByteBuffer buffer);
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
|
||||
public class CompressionDictionary
|
||||
{
|
||||
private static final byte[] DICTIONARY_V2 = ("" +
|
||||
"optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" +
|
||||
"languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" +
|
||||
"f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" +
|
||||
"-agent10010120020120220320420520630030130230330430530630740040140240340440" +
|
||||
"5406407408409410411412413414415416417500501502503504505accept-rangesageeta" +
|
||||
"glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic" +
|
||||
"ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran" +
|
||||
"sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati" +
|
||||
"oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo" +
|
||||
"ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe" +
|
||||
"pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" +
|
||||
"ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" +
|
||||
".1statusversionurl" +
|
||||
// Must be NUL terminated
|
||||
"\u0000").getBytes();
|
||||
|
||||
private static final byte[] DICTIONARY_V3 = ("" +
|
||||
"\u0000\u0000\u0000\u0007options\u0000\u0000\u0000\u0004head\u0000\u0000\u0000\u0004post" +
|
||||
"\u0000\u0000\u0000\u0003put\u0000\u0000\u0000\u0006delete\u0000\u0000\u0000\u0005trace" +
|
||||
"\u0000\u0000\u0000\u0006accept\u0000\u0000\u0000\u000Eaccept-charset" +
|
||||
"\u0000\u0000\u0000\u000Faccept-encoding\u0000\u0000\u0000\u000Faccept-language" +
|
||||
"\u0000\u0000\u0000\raccept-ranges\u0000\u0000\u0000\u0003age\u0000\u0000\u0000\u0005allow" +
|
||||
"\u0000\u0000\u0000\rauthorization\u0000\u0000\u0000\rcache-control" +
|
||||
"\u0000\u0000\u0000\nconnection\u0000\u0000\u0000\fcontent-base\u0000\u0000\u0000\u0010content-encoding" +
|
||||
"\u0000\u0000\u0000\u0010content-language\u0000\u0000\u0000\u000Econtent-length" +
|
||||
"\u0000\u0000\u0000\u0010content-location\u0000\u0000\u0000\u000Bcontent-md5" +
|
||||
"\u0000\u0000\u0000\rcontent-range\u0000\u0000\u0000\fcontent-type\u0000\u0000\u0000\u0004date" +
|
||||
"\u0000\u0000\u0000\u0004etag\u0000\u0000\u0000\u0006expect\u0000\u0000\u0000\u0007expires" +
|
||||
"\u0000\u0000\u0000\u0004from\u0000\u0000\u0000\u0004host\u0000\u0000\u0000\bif-match" +
|
||||
"\u0000\u0000\u0000\u0011if-modified-since\u0000\u0000\u0000\rif-none-match\u0000\u0000\u0000\bif-range" +
|
||||
"\u0000\u0000\u0000\u0013if-unmodified-since\u0000\u0000\u0000\rlast-modified" +
|
||||
"\u0000\u0000\u0000\blocation\u0000\u0000\u0000\fmax-forwards\u0000\u0000\u0000\u0006pragma" +
|
||||
"\u0000\u0000\u0000\u0012proxy-authenticate\u0000\u0000\u0000\u0013proxy-authorization" +
|
||||
"\u0000\u0000\u0000\u0005range\u0000\u0000\u0000\u0007referer\u0000\u0000\u0000\u000Bretry-after" +
|
||||
"\u0000\u0000\u0000\u0006server\u0000\u0000\u0000\u0002te\u0000\u0000\u0000\u0007trailer" +
|
||||
"\u0000\u0000\u0000\u0011transfer-encoding\u0000\u0000\u0000\u0007upgrade\u0000\u0000\u0000\nuser-agent" +
|
||||
"\u0000\u0000\u0000\u0004vary\u0000\u0000\u0000\u0003via\u0000\u0000\u0000\u0007warning" +
|
||||
"\u0000\u0000\u0000\u0010www-authenticate\u0000\u0000\u0000\u0006method\u0000\u0000\u0000\u0003get" +
|
||||
"\u0000\u0000\u0000\u0006status\u0000\u0000\u0000\u0006200 OK\u0000\u0000\u0000\u0007version" +
|
||||
"\u0000\u0000\u0000\bHTTP/1.1\u0000\u0000\u0000\u0003url\u0000\u0000\u0000\u0006public" +
|
||||
"\u0000\u0000\u0000\nset-cookie\u0000\u0000\u0000\nkeep-alive\u0000\u0000\u0000\u0006origin" +
|
||||
"100101201202205206300302303304305306307402405406407408409410411412413414415416417502504505" +
|
||||
"203 Non-Authoritative Information204 No Content301 Moved Permanently400 Bad Request401 Unauthorized" +
|
||||
"403 Forbidden404 Not Found500 Internal Server Error501 Not Implemented503 Service Unavailable" +
|
||||
"Jan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec 00:00:00 Mon, Tue, Wed, Thu, Fri, Sat, Sun, GMT" +
|
||||
"chunked,text/html,image/png,image/jpg,image/gif,application/xml,application/xhtml+xml,text/plain," +
|
||||
"text/javascript,publicprivatemax-age=gzip,deflate,sdchcharset=utf-8charset=iso-8859-1,utf-,*,enq=0.")
|
||||
.getBytes();
|
||||
|
||||
public static byte[] get(short version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
return DICTIONARY_V2;
|
||||
case SPDY.V3:
|
||||
return DICTIONARY_V3;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.util.zip.ZipException;
|
||||
|
||||
public interface CompressionFactory
|
||||
{
|
||||
public Compressor newCompressor();
|
||||
|
||||
public Decompressor newDecompressor();
|
||||
|
||||
public interface Compressor
|
||||
{
|
||||
public void setInput(byte[] input);
|
||||
|
||||
public void setDictionary(byte[] dictionary);
|
||||
|
||||
public int compress(byte[] output);
|
||||
}
|
||||
|
||||
public interface Decompressor
|
||||
{
|
||||
public void setDictionary(byte[] dictionary);
|
||||
|
||||
public void setInput(byte[] input);
|
||||
|
||||
public int decompress(byte[] output) throws ZipException;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.Handler;
|
||||
|
||||
public interface Controller<T>
|
||||
{
|
||||
public int write(ByteBuffer buffer, Handler<T> handler, T context);
|
||||
|
||||
public void close(boolean onlyOutput);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.api.Handler;
|
||||
import org.eclipse.jetty.spdy.api.Session;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
|
||||
public interface ISession extends Session
|
||||
{
|
||||
/**
|
||||
* <p>Initiates the flush of data to the other peer.</p>
|
||||
* <p>Note that the flush may do nothing if, for example, there is nothing to flush, or
|
||||
* if the data to be flushed belong to streams that have their flow-control stalled.</p>
|
||||
*/
|
||||
public void flush();
|
||||
|
||||
public <C> void control(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Handler<C> handler, C context);
|
||||
|
||||
public <C> void data(IStream stream, DataInfo dataInfo, long timeout, TimeUnit unit, Handler<C> handler, C context);
|
||||
|
||||
public int getWindowSize();
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SessionFrameListener;
|
||||
import org.eclipse.jetty.spdy.api.Stream;
|
||||
import org.eclipse.jetty.spdy.api.StreamFrameListener;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.DataFrame;
|
||||
|
||||
/**
|
||||
* <p>The internal interface that represents a stream.</p>
|
||||
* <p>{@link IStream} contains additional methods used by a SPDY
|
||||
* implementation (and not by an application).</p>
|
||||
*/
|
||||
public interface IStream extends Stream
|
||||
{
|
||||
/**
|
||||
* <p>Senders of data frames need to know the current window size
|
||||
* to determine whether they can send more data.</p>
|
||||
*
|
||||
* @return the current window size for this stream.
|
||||
* @see #updateWindowSize(int)
|
||||
*/
|
||||
public int getWindowSize();
|
||||
|
||||
/**
|
||||
* <p>Updates the window size for this stream by the given amount,
|
||||
* that can be positive or negative.</p>
|
||||
* <p>Senders and recipients of data frames update the window size,
|
||||
* respectively, with negative values and positive values.</p>
|
||||
*
|
||||
* @param delta the signed amount the window size needs to be updated
|
||||
* @see #getWindowSize()
|
||||
*/
|
||||
public void updateWindowSize(int delta);
|
||||
|
||||
/**
|
||||
* @param listener the stream frame listener associated to this stream
|
||||
* as returned by {@link SessionFrameListener#onSyn(Stream, SynInfo)}
|
||||
*/
|
||||
public void setStreamFrameListener(StreamFrameListener listener);
|
||||
|
||||
/**
|
||||
* <p>A stream can be open, {@link #isHalfClosed() half closed} or
|
||||
* {@link #isClosed() closed} and this method updates the close state
|
||||
* of this stream.</p>
|
||||
* <p>If the stream is open, calling this method with a value of true
|
||||
* puts the stream into half closed state.</p>
|
||||
* <p>If the stream is half closed, calling this method with a value
|
||||
* of true puts the stream into closed state.</p>
|
||||
*
|
||||
* @param close whether the close state should be updated
|
||||
*/
|
||||
public void updateCloseState(boolean close);
|
||||
|
||||
/**
|
||||
* <p>Processes the given control frame,
|
||||
* for example by updating the stream's state or by calling listeners.</p>
|
||||
*
|
||||
* @param frame the control frame to process
|
||||
* @see #process(DataFrame, ByteBuffer)
|
||||
*/
|
||||
public void process(ControlFrame frame);
|
||||
|
||||
/**
|
||||
* <p>Processes the give data frame along with the given byte buffer,
|
||||
* for example by updating the stream's state or by calling listeners.</p>
|
||||
*
|
||||
* @param frame the data frame to process
|
||||
* @param data the byte buffer to process
|
||||
* @see #process(ControlFrame)
|
||||
*/
|
||||
public void process(DataFrame frame, ByteBuffer data);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
public interface IdleListener
|
||||
{
|
||||
public void onIdle(boolean idle);
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.Handler;
|
||||
|
||||
/**
|
||||
* <p>A {@link Promise} is a {@link Future} that allows a result or a failure to be set,
|
||||
* so that the {@link Future} will be {@link #isDone() done}.</p>
|
||||
*
|
||||
* @param <T> the type of the result object
|
||||
*/
|
||||
public class Promise<T> implements Handler<T>, Future<T>
|
||||
{
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
private boolean cancelled;
|
||||
private Throwable failure;
|
||||
private T promise;
|
||||
|
||||
@Override
|
||||
public void completed(T result)
|
||||
{
|
||||
this.promise = result;
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
this.failure = x;
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning)
|
||||
{
|
||||
cancelled = true;
|
||||
latch.countDown();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone()
|
||||
{
|
||||
return cancelled || latch.getCount() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() throws InterruptedException, ExecutionException
|
||||
{
|
||||
latch.await();
|
||||
return result();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
latch.await(timeout, unit);
|
||||
return result();
|
||||
}
|
||||
|
||||
private T result() throws ExecutionException
|
||||
{
|
||||
Throwable failure = this.failure;
|
||||
if (failure != null)
|
||||
throw new ExecutionException(failure);
|
||||
return promise;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
|
||||
public class SessionException extends RuntimeException
|
||||
{
|
||||
private final SessionStatus sessionStatus;
|
||||
|
||||
public SessionException(SessionStatus sessionStatus)
|
||||
{
|
||||
this.sessionStatus = sessionStatus;
|
||||
}
|
||||
|
||||
public SessionException(SessionStatus sessionStatus, String message)
|
||||
{
|
||||
super(message);
|
||||
this.sessionStatus = sessionStatus;
|
||||
}
|
||||
|
||||
public SessionException(SessionStatus sessionStatus, Throwable cause)
|
||||
{
|
||||
super(cause);
|
||||
this.sessionStatus = sessionStatus;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
public class StandardByteBufferPool implements ByteBufferPool
|
||||
{
|
||||
private final ConcurrentMap<Integer, Queue<ByteBuffer>> directBuffers = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Integer, Queue<ByteBuffer>> heapBuffers = new ConcurrentHashMap<>();
|
||||
private final int factor;
|
||||
|
||||
public StandardByteBufferPool()
|
||||
{
|
||||
this(1024);
|
||||
}
|
||||
|
||||
public StandardByteBufferPool(int factor)
|
||||
{
|
||||
this.factor = factor;
|
||||
}
|
||||
|
||||
public ByteBuffer acquire(int size, boolean direct)
|
||||
{
|
||||
int bucket = bucketFor(size);
|
||||
ConcurrentMap<Integer, Queue<ByteBuffer>> buffers = buffersFor(direct);
|
||||
|
||||
ByteBuffer result = null;
|
||||
Queue<ByteBuffer> byteBuffers = buffers.get(bucket);
|
||||
if (byteBuffers != null)
|
||||
result = byteBuffers.poll();
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
int capacity = bucket * factor;
|
||||
result = direct ? ByteBuffer.allocateDirect(capacity) : ByteBuffer.allocate(capacity);
|
||||
}
|
||||
|
||||
result.clear();
|
||||
result.limit(size);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void release(ByteBuffer buffer)
|
||||
{
|
||||
int bucket = bucketFor(buffer.capacity());
|
||||
ConcurrentMap<Integer, Queue<ByteBuffer>> buffers = buffersFor(buffer.isDirect());
|
||||
|
||||
// Avoid to create a new queue every time, just to be discarded immediately
|
||||
Queue<ByteBuffer> byteBuffers = buffers.get(bucket);
|
||||
if (byteBuffers == null)
|
||||
{
|
||||
byteBuffers = new ConcurrentLinkedQueue<>();
|
||||
Queue<ByteBuffer> existing = buffers.putIfAbsent(bucket, byteBuffers);
|
||||
if (existing != null)
|
||||
byteBuffers = existing;
|
||||
}
|
||||
|
||||
buffer.clear();
|
||||
byteBuffers.offer(buffer);
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
directBuffers.clear();
|
||||
heapBuffers.clear();
|
||||
}
|
||||
|
||||
private int bucketFor(int size)
|
||||
{
|
||||
int bucket = size / factor;
|
||||
if (size % factor > 0)
|
||||
++bucket;
|
||||
return bucket;
|
||||
}
|
||||
|
||||
private ConcurrentMap<Integer, Queue<ByteBuffer>> buffersFor(boolean direct)
|
||||
{
|
||||
return direct ? directBuffers : heapBuffers;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.ZipException;
|
||||
|
||||
public class StandardCompressionFactory implements CompressionFactory
|
||||
{
|
||||
@Override
|
||||
public Compressor newCompressor()
|
||||
{
|
||||
return new StandardCompressor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Decompressor newDecompressor()
|
||||
{
|
||||
return new StandardDecompressor();
|
||||
}
|
||||
|
||||
public static class StandardCompressor implements Compressor
|
||||
{
|
||||
private final Deflater deflater = new Deflater();
|
||||
|
||||
@Override
|
||||
public void setInput(byte[] input)
|
||||
{
|
||||
deflater.setInput(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDictionary(byte[] dictionary)
|
||||
{
|
||||
deflater.setDictionary(dictionary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compress(byte[] output)
|
||||
{
|
||||
return deflater.deflate(output, 0, output.length, Deflater.SYNC_FLUSH);
|
||||
}
|
||||
}
|
||||
|
||||
public static class StandardDecompressor implements CompressionFactory.Decompressor
|
||||
{
|
||||
private final Inflater inflater = new Inflater();
|
||||
|
||||
@Override
|
||||
public void setDictionary(byte[] dictionary)
|
||||
{
|
||||
inflater.setDictionary(dictionary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInput(byte[] input)
|
||||
{
|
||||
inflater.setInput(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int decompress(byte[] output) throws ZipException
|
||||
{
|
||||
try
|
||||
{
|
||||
return inflater.inflate(output);
|
||||
}
|
||||
catch (DataFormatException x)
|
||||
{
|
||||
throw (ZipException)new ZipException().initCause(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,337 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.api.Handler;
|
||||
import org.eclipse.jetty.spdy.api.HeadersInfo;
|
||||
import org.eclipse.jetty.spdy.api.ReplyInfo;
|
||||
import org.eclipse.jetty.spdy.api.RstInfo;
|
||||
import org.eclipse.jetty.spdy.api.Session;
|
||||
import org.eclipse.jetty.spdy.api.Stream;
|
||||
import org.eclipse.jetty.spdy.api.StreamFrameListener;
|
||||
import org.eclipse.jetty.spdy.api.StreamStatus;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.DataFrame;
|
||||
import org.eclipse.jetty.spdy.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SynReplyFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SynStreamFrame;
|
||||
import org.eclipse.jetty.spdy.frames.WindowUpdateFrame;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class StandardStream implements IStream
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Stream.class);
|
||||
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
|
||||
private final SynStreamFrame frame;
|
||||
private final ISession session;
|
||||
private final AtomicInteger windowSize;
|
||||
private volatile StreamFrameListener listener;
|
||||
private volatile boolean opened;
|
||||
private volatile boolean halfClosed;
|
||||
private volatile boolean closed;
|
||||
|
||||
public StandardStream(SynStreamFrame frame, ISession session, int windowSize)
|
||||
{
|
||||
this.frame = frame;
|
||||
this.session = session;
|
||||
this.windowSize = new AtomicInteger(windowSize);
|
||||
this.halfClosed = frame.isClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId()
|
||||
{
|
||||
return frame.getStreamId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getPriority()
|
||||
{
|
||||
return frame.getPriority();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWindowSize()
|
||||
{
|
||||
return windowSize.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateWindowSize(int delta)
|
||||
{
|
||||
int size = windowSize.addAndGet(delta);
|
||||
logger.debug("Updated window size by {}, new window size {}", delta, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session getSession()
|
||||
{
|
||||
return session;
|
||||
}
|
||||
|
||||
public boolean isHalfClosed()
|
||||
{
|
||||
return halfClosed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String key)
|
||||
{
|
||||
return attributes.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String key, Object value)
|
||||
{
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object removeAttribute(String key)
|
||||
{
|
||||
return attributes.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStreamFrameListener(StreamFrameListener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCloseState(boolean close)
|
||||
{
|
||||
if (close)
|
||||
{
|
||||
if (isHalfClosed())
|
||||
closed = true;
|
||||
else
|
||||
halfClosed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ControlFrame frame)
|
||||
{
|
||||
switch (frame.getType())
|
||||
{
|
||||
case SYN_STREAM:
|
||||
{
|
||||
opened = true;
|
||||
break;
|
||||
}
|
||||
case SYN_REPLY:
|
||||
{
|
||||
opened = true;
|
||||
SynReplyFrame synReply = (SynReplyFrame)frame;
|
||||
updateCloseState(synReply.isClose());
|
||||
ReplyInfo replyInfo = new ReplyInfo(synReply.getHeaders(), synReply.isClose());
|
||||
notifyOnReply(replyInfo);
|
||||
break;
|
||||
}
|
||||
case HEADERS:
|
||||
{
|
||||
HeadersFrame headers = (HeadersFrame)frame;
|
||||
updateCloseState(headers.isClose());
|
||||
HeadersInfo headersInfo = new HeadersInfo(headers.getHeaders(), headers.isClose(), headers.isResetCompression());
|
||||
notifyOnHeaders(headersInfo);
|
||||
break;
|
||||
}
|
||||
case WINDOW_UPDATE:
|
||||
{
|
||||
WindowUpdateFrame windowUpdate = (WindowUpdateFrame)frame;
|
||||
updateWindowSize(windowUpdate.getWindowDelta());
|
||||
break;
|
||||
}
|
||||
case RST_STREAM:
|
||||
{
|
||||
// TODO:
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
session.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(DataFrame frame, ByteBuffer data)
|
||||
{
|
||||
if (!opened)
|
||||
{
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR));
|
||||
return;
|
||||
}
|
||||
|
||||
updateCloseState(frame.isClose());
|
||||
|
||||
ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(data, frame.isClose(), frame.isCompress())
|
||||
{
|
||||
@Override
|
||||
public void consume(int delta)
|
||||
{
|
||||
super.consume(delta);
|
||||
|
||||
// This is the algorithm for flow control.
|
||||
// This method may be called multiple times with delta=1, but we only send a window
|
||||
// update when the whole dataInfo has been consumed.
|
||||
// Other policies may be to send window updates when consumed() is greater than
|
||||
// a certain threshold, etc. but for now the policy is not pluggable for simplicity.
|
||||
// Note that the frequency of window updates depends on the read buffer, that
|
||||
// should not be too smaller than the window size to avoid frequent window updates.
|
||||
// Therefore, a pluggable policy should be able to modify the read buffer capacity.
|
||||
if (consumed() == length() && !isClosed())
|
||||
windowUpdate(length());
|
||||
}
|
||||
};
|
||||
notifyOnData(dataInfo);
|
||||
session.flush();
|
||||
}
|
||||
|
||||
private void windowUpdate(int delta)
|
||||
{
|
||||
if (delta > 0)
|
||||
{
|
||||
WindowUpdateFrame windowUpdateFrame = new WindowUpdateFrame(session.getVersion(), getId(), delta);
|
||||
session.control(this, windowUpdateFrame, 0, TimeUnit.MILLISECONDS, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyOnReply(ReplyInfo replyInfo)
|
||||
{
|
||||
final StreamFrameListener listener = this.listener;
|
||||
try
|
||||
{
|
||||
if (listener != null)
|
||||
{
|
||||
logger.debug("Invoking reply callback with {} on listener {}", replyInfo, listener);
|
||||
listener.onReply(this, replyInfo);
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyOnHeaders(HeadersInfo headersInfo)
|
||||
{
|
||||
final StreamFrameListener listener = this.listener;
|
||||
try
|
||||
{
|
||||
if (listener != null)
|
||||
{
|
||||
logger.debug("Invoking headers callback with {} on listener {}", frame, listener);
|
||||
listener.onHeaders(this, headersInfo);
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyOnData(DataInfo dataInfo)
|
||||
{
|
||||
final StreamFrameListener listener = this.listener;
|
||||
try
|
||||
{
|
||||
if (listener != null)
|
||||
{
|
||||
logger.debug("Invoking data callback with {} on listener {}", dataInfo, listener);
|
||||
listener.onData(this, dataInfo);
|
||||
logger.debug("Invoked data callback with {} on listener {}", dataInfo, listener);
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Void> reply(ReplyInfo replyInfo)
|
||||
{
|
||||
Promise<Void> result = new Promise<>();
|
||||
reply(replyInfo, 0, TimeUnit.MILLISECONDS, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reply(ReplyInfo replyInfo, long timeout, TimeUnit unit, Handler<Void> handler)
|
||||
{
|
||||
updateCloseState(replyInfo.isClose());
|
||||
SynReplyFrame frame = new SynReplyFrame(session.getVersion(), replyInfo.getFlags(), getId(), replyInfo.getHeaders());
|
||||
session.control(this, frame, timeout, unit, handler, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Void> data(DataInfo dataInfo)
|
||||
{
|
||||
Promise<Void> result = new Promise<>();
|
||||
data(dataInfo, 0, TimeUnit.MILLISECONDS, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void data(DataInfo dataInfo, long timeout, TimeUnit unit, Handler<Void> handler)
|
||||
{
|
||||
// Cannot update the close state here, because the data that we send may
|
||||
// be flow controlled, so we need the stream to update the window size.
|
||||
session.data(this, dataInfo, timeout, unit, handler, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Void> headers(HeadersInfo headersInfo)
|
||||
{
|
||||
Promise<Void> result = new Promise<>();
|
||||
headers(headersInfo, 0, TimeUnit.MILLISECONDS, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void headers(HeadersInfo headersInfo, long timeout, TimeUnit unit, Handler<Void> handler)
|
||||
{
|
||||
updateCloseState(headersInfo.isClose());
|
||||
HeadersFrame frame = new HeadersFrame(session.getVersion(), headersInfo.getFlags(), getId(), headersInfo.getHeaders());
|
||||
session.control(this, frame, timeout, unit, handler, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed()
|
||||
{
|
||||
return closed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("stream=%d v%d closed=%s", getId(), session.getVersion(), isClosed() ? "true" : isHalfClosed() ? "half" : "false");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.StreamStatus;
|
||||
|
||||
public class StreamException extends RuntimeException
|
||||
{
|
||||
private final int streamId;
|
||||
private final StreamStatus streamStatus;
|
||||
|
||||
public StreamException(int streamId, StreamStatus streamStatus)
|
||||
{
|
||||
this.streamId = streamId;
|
||||
this.streamStatus = streamStatus;
|
||||
}
|
||||
|
||||
public StreamException(int streamId, StreamStatus streamStatus, String message)
|
||||
{
|
||||
super(message);
|
||||
this.streamId = streamId;
|
||||
this.streamStatus = streamStatus;
|
||||
}
|
||||
|
||||
public StreamException(int streamId, StreamStatus streamStatus, Throwable x)
|
||||
{
|
||||
super(x);
|
||||
this.streamId = streamId;
|
||||
this.streamStatus = streamStatus;
|
||||
}
|
||||
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public StreamStatus getStreamStatus()
|
||||
{
|
||||
return streamStatus;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* <p>Specialized {@link DataInfo} for {@link ByteBuffer} content.</p>
|
||||
*/
|
||||
public class ByteBufferDataInfo extends DataInfo
|
||||
{
|
||||
private final ByteBuffer buffer;
|
||||
private final int length;
|
||||
|
||||
public ByteBufferDataInfo(ByteBuffer buffer, boolean close)
|
||||
{
|
||||
this(buffer, close, false);
|
||||
}
|
||||
|
||||
public ByteBufferDataInfo(ByteBuffer buffer, boolean close, boolean compress)
|
||||
{
|
||||
super(close, compress);
|
||||
this.buffer = buffer;
|
||||
this.length = buffer.remaining();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length()
|
||||
{
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available()
|
||||
{
|
||||
return buffer.remaining();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInto(ByteBuffer output)
|
||||
{
|
||||
int space = output.remaining();
|
||||
if (available() > space)
|
||||
{
|
||||
int limit = buffer.limit();
|
||||
buffer.limit(buffer.position() + space);
|
||||
output.put(buffer);
|
||||
buffer.limit(limit);
|
||||
}
|
||||
else
|
||||
{
|
||||
space = buffer.remaining();
|
||||
output.put(buffer);
|
||||
}
|
||||
return space;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* <p>Specialized {@link DataInfo} for byte array content.</p>
|
||||
*/
|
||||
public class BytesDataInfo extends DataInfo
|
||||
{
|
||||
private byte[] bytes;
|
||||
private int offset;
|
||||
|
||||
public BytesDataInfo(byte[] bytes, boolean close)
|
||||
{
|
||||
this(bytes, close, false);
|
||||
}
|
||||
|
||||
public BytesDataInfo(byte[] bytes, boolean close, boolean compress)
|
||||
{
|
||||
super(close, compress);
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length()
|
||||
{
|
||||
return bytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available()
|
||||
{
|
||||
return length() - offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInto(ByteBuffer output)
|
||||
{
|
||||
int space = output.remaining();
|
||||
int length = Math.min(available(), space);
|
||||
output.put(bytes, offset, length);
|
||||
offset += length;
|
||||
return length;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* <p>A container for DATA frames metadata and content bytes.</p>
|
||||
* <p>Specialized subclasses (like {@link StringDataInfo}) may be used by applications
|
||||
* to send specific types of content.</p>
|
||||
* <p>Applications may send multiple instances of {@link DataInfo}, usually of the same
|
||||
* type, via {@link Stream#data(DataInfo)}. The last instance must have the
|
||||
* {@link #isClose() close flag} set, so that the client knows that no more content is
|
||||
* expected.</p>
|
||||
* <p>Receivers of {@link DataInfo} via {@link StreamFrameListener#onData(Stream, DataInfo)}
|
||||
* have two different APIs to read the data content bytes: a {@link #readInto(ByteBuffer) read}
|
||||
* API that does not interact with flow control, and a {@link #consumeInto(ByteBuffer) drain}
|
||||
* API that interacts with flow control.</p>
|
||||
* <p>Flow control is defined so that when the sender wants to sends a number of bytes larger
|
||||
* than the {@link Settings.ID#INITIAL_WINDOW_SIZE} value, it will stop sending as soon as it
|
||||
* has sent a number of bytes equal to the window size. The receiver has to <em>consume</em>
|
||||
* the data bytes that it received in order to tell the sender to send more bytes.</p>
|
||||
* <p>Consuming the data bytes can be done only via {@link #consumeInto(ByteBuffer)} or by a combination
|
||||
* of {@link #readInto(ByteBuffer)} and {@link #consume(int)} (possibly at different times).</p>
|
||||
*/
|
||||
public abstract class DataInfo
|
||||
{
|
||||
/**
|
||||
* <p>Flag that indicates that this {@link DataInfo} is the last frame in the stream.</p>
|
||||
*
|
||||
* @see #isClose()
|
||||
* @see #getFlags()
|
||||
*/
|
||||
public final static byte FLAG_CLOSE = 1;
|
||||
/**
|
||||
* <p>Flag that indicates that this {@link DataInfo}'s data is compressed.</p>
|
||||
*
|
||||
* @see #isCompress()
|
||||
* @see #getFlags()
|
||||
*/
|
||||
public final static byte FLAG_COMPRESS = 2;
|
||||
|
||||
private final AtomicInteger consumed = new AtomicInteger();
|
||||
private boolean close;
|
||||
private boolean compress;
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link DataInfo} with the given close flag and no compression flag.</p>
|
||||
*
|
||||
* @param close the value of the close flag
|
||||
*/
|
||||
public DataInfo(boolean close)
|
||||
{
|
||||
setClose(close);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link DataInfo} with the given close flag and given compression flag.</p>
|
||||
*
|
||||
* @param close the close flag
|
||||
* @param compress the compress flag
|
||||
*/
|
||||
public DataInfo(boolean close, boolean compress)
|
||||
{
|
||||
setClose(close);
|
||||
setCompress(compress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of the compress flag
|
||||
* @see #setCompress(boolean)
|
||||
*/
|
||||
public boolean isCompress()
|
||||
{
|
||||
return compress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param compress the value of the compress flag
|
||||
* @see #isCompress()
|
||||
*/
|
||||
public void setCompress(boolean compress)
|
||||
{
|
||||
this.compress = compress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of the close flag
|
||||
* @see #setClose(boolean)
|
||||
*/
|
||||
public boolean isClose()
|
||||
{
|
||||
return close;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param close the value of the close flag
|
||||
* @see #isClose()
|
||||
*/
|
||||
public void setClose(boolean close)
|
||||
{
|
||||
this.close = close;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the close and compress flags as integer
|
||||
* @see #FLAG_CLOSE
|
||||
* @see #FLAG_COMPRESS
|
||||
*/
|
||||
public byte getFlags()
|
||||
{
|
||||
byte flags = isClose() ? FLAG_CLOSE : 0;
|
||||
flags |= isCompress() ? FLAG_COMPRESS : 0;
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the total number of content bytes
|
||||
* @see #available()
|
||||
*/
|
||||
public abstract int length();
|
||||
|
||||
/**
|
||||
* <p>Returns the available content bytes that can be read via {@link #readInto(ByteBuffer)}.</p>
|
||||
* <p>Each invocation to {@link #readInto(ByteBuffer)} modifies the value returned by this method,
|
||||
* until no more content bytes are available.</p>
|
||||
*
|
||||
* @return the available content bytes
|
||||
* @see #readInto(ByteBuffer)
|
||||
*/
|
||||
public abstract int available();
|
||||
|
||||
/**
|
||||
* <p>Copies the content bytes of this {@link DataInfo} into the given {@link ByteBuffer}.</p>
|
||||
* <p>If the given {@link ByteBuffer} cannot contain the whole content of this {@link DataInfo}
|
||||
* then after the read {@link #available()} will return a positive value, and further content
|
||||
* may be retrieved by invoking again this method with a new output buffer.</p>
|
||||
*
|
||||
* @param output the {@link ByteBuffer} to copy to bytes into
|
||||
* @return the number of bytes copied
|
||||
* @see #available()
|
||||
* @see #consumeInto(ByteBuffer)
|
||||
*/
|
||||
public abstract int readInto(ByteBuffer output);
|
||||
|
||||
/**
|
||||
* <p>Reads and consumes the content bytes of this {@link DataInfo} into the given {@link ByteBuffer}.</p>
|
||||
*
|
||||
* @param output the {@link ByteBuffer} to copy to bytes into
|
||||
* @return the number of bytes copied
|
||||
* @see #consume(int)
|
||||
*/
|
||||
public int consumeInto(ByteBuffer output)
|
||||
{
|
||||
int read = readInto(output);
|
||||
consume(read);
|
||||
return read;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Consumes the given number of bytes from this {@link DataInfo}.</p>
|
||||
*
|
||||
* @param delta the number of bytes consumed
|
||||
*/
|
||||
public void consume(int delta)
|
||||
{
|
||||
if (delta < 0)
|
||||
throw new IllegalArgumentException();
|
||||
int read = length() - available();
|
||||
int newConsumed = consumed() + delta;
|
||||
// if (newConsumed > read)
|
||||
// throw new IllegalStateException("Consuming without reading: consumed " + newConsumed + " but only read " + read);
|
||||
consumed.addAndGet(delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of bytes consumed
|
||||
*/
|
||||
public int consumed()
|
||||
{
|
||||
return consumed.get();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param charset the charset used to convert the bytes
|
||||
* @param consume whether to consume the content
|
||||
* @return a String with the content of this {@link DataInfo}
|
||||
*/
|
||||
public String asString(String charset, boolean consume)
|
||||
{
|
||||
ByteBuffer buffer = asByteBuffer(consume);
|
||||
return Charset.forName(charset).decode(buffer).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a byte array with the content of this {@link DataInfo}
|
||||
* @param consume whether to consume the content
|
||||
*/
|
||||
public byte[] asBytes(boolean consume)
|
||||
{
|
||||
ByteBuffer buffer = asByteBuffer(consume);
|
||||
byte[] result = new byte[buffer.remaining()];
|
||||
buffer.get(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a {@link ByteBuffer} with the content of this {@link DataInfo}
|
||||
* @param consume whether to consume the content
|
||||
*/
|
||||
public ByteBuffer asByteBuffer(boolean consume)
|
||||
{
|
||||
ByteBuffer buffer = allocate(available());
|
||||
if (consume)
|
||||
consumeInto(buffer);
|
||||
else
|
||||
readInto(buffer);
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
protected ByteBuffer allocate(int size)
|
||||
{
|
||||
return ByteBuffer.allocate(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("DATA @%x available=%d consumed=%d close=%b compress=%b", hashCode(), available(), consumed(), isClose(), isCompress());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>A container for GOAWAY frames metadata: the last good stream id and
|
||||
* the session status.</p>
|
||||
*/
|
||||
public class GoAwayInfo
|
||||
{
|
||||
private final int lastStreamId;
|
||||
private final SessionStatus sessionStatus;
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link GoAwayInfo} with the given last good stream id and session status</p>
|
||||
*
|
||||
* @param lastStreamId the last good stream id
|
||||
* @param sessionStatus the session status
|
||||
*/
|
||||
public GoAwayInfo(int lastStreamId, SessionStatus sessionStatus)
|
||||
{
|
||||
this.lastStreamId = lastStreamId;
|
||||
this.sessionStatus = sessionStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the last good stream id
|
||||
*/
|
||||
public int getLastStreamId()
|
||||
{
|
||||
return lastStreamId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the session status
|
||||
*/
|
||||
public SessionStatus getSessionStatus()
|
||||
{
|
||||
return sessionStatus;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>A callback abstraction that handles completed/failed events of asynchronous operations.</p>
|
||||
* <p>Instances of this class capture a context that is made available on the completion callback.</p>
|
||||
*
|
||||
* @param <C> the type of the context object
|
||||
*/
|
||||
public interface Handler<C>
|
||||
{
|
||||
/**
|
||||
* <p>Callback invoked when the operation completes.</p>
|
||||
*
|
||||
* @param context the context
|
||||
* @see #failed(Throwable)
|
||||
*/
|
||||
public abstract void completed(C context);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when the operation fails.</p>
|
||||
*
|
||||
* @param x the reason for the operation failure
|
||||
*/
|
||||
public void failed(Throwable x);
|
||||
|
||||
/**
|
||||
* <p>Empty implementation of {@link Handler}</p>
|
||||
*
|
||||
* @param <C> the type of the context object
|
||||
*/
|
||||
public static class Adapter<C> implements Handler<C>
|
||||
{
|
||||
@Override
|
||||
public void completed(C context)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
throw new SPDYException(x);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,284 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>A container for name/value pairs, known as headers.</p>
|
||||
* <p>A {@link Header} is composed of a case-insensitive name string and
|
||||
* of a case-sensitive set of value strings.</p>
|
||||
* <p>The implementation of this class is not thread safe.</p>
|
||||
*/
|
||||
public class Headers implements Iterable<Headers.Header>
|
||||
{
|
||||
private final Map<String, Header> headers;
|
||||
|
||||
/**
|
||||
* <p>Creates an empty modifiable {@link Headers} instance.</p>
|
||||
* @see #Headers(Headers, boolean)
|
||||
*/
|
||||
public Headers()
|
||||
{
|
||||
headers = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link Headers} instance by copying the headers from the given
|
||||
* {@link Headers} and making it (im)mutable depending on the given {@code immutable} parameter</p>
|
||||
*
|
||||
* @param original the {@link Headers} to copy headers from
|
||||
* @param immutable whether this instance is immutable
|
||||
*/
|
||||
public Headers(Headers original, boolean immutable)
|
||||
{
|
||||
Map<String, Header> copy = new LinkedHashMap<>();
|
||||
copy.putAll(original.headers);
|
||||
headers = immutable ? Collections.unmodifiableMap(copy) : copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Headers that = (Headers)obj;
|
||||
return headers.equals(that.headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return headers.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a set of header names
|
||||
*/
|
||||
public Set<String> names()
|
||||
{
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
for (Header header : headers.values())
|
||||
result.add(header.name);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name the header name
|
||||
* @return the {@link Header} with the given name, or null if no such header exists
|
||||
*/
|
||||
public Header get(String name)
|
||||
{
|
||||
return headers.get(name.trim().toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Inserts or replaces the given name/value pair as a single-valued {@link Header}.</p>
|
||||
*
|
||||
* @param name the header name
|
||||
* @param value the header value
|
||||
*/
|
||||
public void put(String name, String value)
|
||||
{
|
||||
name = name.trim();
|
||||
Header header = new Header(name, value.trim());
|
||||
headers.put(name.toLowerCase(), header);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Inserts or replaces the given {@link Header}, mapped to the {@link Header#name() header's name}</p>
|
||||
*
|
||||
* @param header the header to add
|
||||
*/
|
||||
public void put(Header header)
|
||||
{
|
||||
headers.put(header.name().toLowerCase(), header);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds the given value to a header with the given name, creating a {@link Header} is none exists
|
||||
* for the given name.</p>
|
||||
*
|
||||
* @param name the header name
|
||||
* @param value the header value to add
|
||||
*/
|
||||
public void add(String name, String value)
|
||||
{
|
||||
name = name.trim();
|
||||
Header header = headers.get(name.toLowerCase());
|
||||
if (header == null)
|
||||
{
|
||||
header = new Header(name, value.trim());
|
||||
headers.put(name.toLowerCase(), header);
|
||||
}
|
||||
else
|
||||
{
|
||||
header = new Header(header.name(), header.value() + "," + value.trim());
|
||||
headers.put(name.toLowerCase(), header);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Removes the {@link Header} with the given name</p>
|
||||
*
|
||||
* @param name the name of the header to remove
|
||||
* @return the removed header, or null if no such header existed
|
||||
*/
|
||||
public Header remove(String name)
|
||||
{
|
||||
name = name.trim();
|
||||
return headers.remove(name.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Empties this {@link Headers} instance from all headers</p>
|
||||
* @see #isEmpty()
|
||||
*/
|
||||
public void clear()
|
||||
{
|
||||
headers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this {@link Headers} instance is empty
|
||||
*/
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return headers.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of headers
|
||||
*/
|
||||
public int size()
|
||||
{
|
||||
return headers.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an iterator over the {@link Header} present in this instance
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Header> iterator()
|
||||
{
|
||||
return headers.values().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return headers.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>A named list of string values.</p>
|
||||
* <p>The name is case-sensitive and there must be at least one value.</p>
|
||||
*/
|
||||
public static class Header
|
||||
{
|
||||
private final String name;
|
||||
private final String[] values;
|
||||
|
||||
private Header(String name, String value, String... values)
|
||||
{
|
||||
this.name = name;
|
||||
this.values = new String[values.length + 1];
|
||||
this.values[0] = value;
|
||||
if (values.length > 0)
|
||||
System.arraycopy(values, 0, this.values, 1, values.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Header that = (Header)obj;
|
||||
return name.equals(that.name) && Arrays.equals(values, that.values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int result = name.hashCode();
|
||||
result = 31 * result + Arrays.hashCode(values);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the header's name
|
||||
*/
|
||||
public String name()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first header's value
|
||||
*/
|
||||
public String value()
|
||||
{
|
||||
return values[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Attempts to convert the result of {@link #value()} to an integer,
|
||||
* returning it if the conversion is successful; returns null if the
|
||||
* result of {@link #value()} is null.</p>
|
||||
*
|
||||
* @return the result of {@link #value()} converted to an integer, or null
|
||||
* @throws NumberFormatException if the conversion fails
|
||||
*/
|
||||
public Integer valueAsInt()
|
||||
{
|
||||
final String value = value();
|
||||
return value == null ? null : Integer.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the header's values
|
||||
*/
|
||||
public String[] values()
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the header has multiple values
|
||||
*/
|
||||
public boolean hasMultipleValues()
|
||||
{
|
||||
return values.length > 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return Arrays.toString(values);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>A container for HEADERS frame metadata and headers.</p>
|
||||
*/
|
||||
public class HeadersInfo
|
||||
{
|
||||
/**
|
||||
* <p>Flag that indicates that this {@link HeadersInfo} is the last frame in the stream.</p>
|
||||
*
|
||||
* @see #isClose()
|
||||
* @see #getFlags()
|
||||
*/
|
||||
public static final byte FLAG_CLOSE = 1;
|
||||
/**
|
||||
* <p>Flag that indicates that the compression of the stream must be reset.</p>
|
||||
*
|
||||
* @see #isResetCompression()
|
||||
* @see #getFlags()
|
||||
*/
|
||||
public static final byte FLAG_RESET_COMPRESSION = 2;
|
||||
|
||||
private final boolean close;
|
||||
private final boolean resetCompression;
|
||||
private final Headers headers;
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link HeadersInfo} instance with the given headers,
|
||||
* the given close flag and no reset compression flag</p>
|
||||
*
|
||||
* @param headers the {@link Headers}
|
||||
* @param close the value of the close flag
|
||||
*/
|
||||
public HeadersInfo(Headers headers, boolean close)
|
||||
{
|
||||
this(headers, close, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link HeadersInfo} instance with the given headers,
|
||||
* the given close flag and the given reset compression flag</p>
|
||||
*
|
||||
* @param headers the {@link Headers}
|
||||
* @param close the value of the close flag
|
||||
* @param resetCompression the value of the reset compression flag
|
||||
*/
|
||||
public HeadersInfo(Headers headers, boolean close, boolean resetCompression)
|
||||
{
|
||||
this.headers = headers;
|
||||
this.close = close;
|
||||
this.resetCompression = resetCompression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of the close flag
|
||||
*/
|
||||
public boolean isClose()
|
||||
{
|
||||
return close;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of the reset compression flag
|
||||
*/
|
||||
public boolean isResetCompression()
|
||||
{
|
||||
return resetCompression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link Headers}
|
||||
*/
|
||||
public Headers getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the close and reset compression flags as integer
|
||||
* @see #FLAG_CLOSE
|
||||
* @see #FLAG_RESET_COMPRESSION
|
||||
*/
|
||||
public byte getFlags()
|
||||
{
|
||||
byte flags = isClose() ? FLAG_CLOSE : 0;
|
||||
flags += isResetCompression() ? FLAG_RESET_COMPRESSION : 0;
|
||||
return flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("HEADER close=%b %s", close, headers);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>A container for PING frames data.</p>
|
||||
*/
|
||||
public class PingInfo
|
||||
{
|
||||
private final int pingId;
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link PingInfo} with the given ping id</p>
|
||||
* @param pingId the ping id
|
||||
*/
|
||||
public PingInfo(int pingId)
|
||||
{
|
||||
this.pingId = pingId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ping id
|
||||
*/
|
||||
public int getPingId()
|
||||
{
|
||||
return pingId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>A container for SYN_REPLY frames metadata and headers.</p>
|
||||
*/
|
||||
public class ReplyInfo
|
||||
{
|
||||
/**
|
||||
* <p>Flag that indicates that this {@link ReplyInfo} is the last frame in the stream.</p>
|
||||
*
|
||||
* @see #isClose()
|
||||
* @see #getFlags()
|
||||
*/
|
||||
public static final byte FLAG_CLOSE = 1;
|
||||
|
||||
private final Headers headers;
|
||||
private final boolean close;
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link ReplyInfo} instance with empty headers and the given close flag.</p>
|
||||
*
|
||||
* @param close the value of the close flag
|
||||
*/
|
||||
public ReplyInfo(boolean close)
|
||||
{
|
||||
this(new Headers(), close);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link ReplyInfo} instance with the given headers and the given close flag.</p>
|
||||
*
|
||||
* @param headers the {@link Headers}
|
||||
* @param close the value of the close flag
|
||||
*/
|
||||
public ReplyInfo(Headers headers, boolean close)
|
||||
{
|
||||
this.headers = headers;
|
||||
this.close = close;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link Headers}
|
||||
*/
|
||||
public Headers getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of the close flag
|
||||
*/
|
||||
public boolean isClose()
|
||||
{
|
||||
return close;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the close and reset compression flags as integer
|
||||
* @see #FLAG_CLOSE
|
||||
*/
|
||||
public byte getFlags()
|
||||
{
|
||||
return isClose() ? FLAG_CLOSE : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("REPLY close=%b %s", close, headers);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>A container for RST_STREAM frames data: the stream id and the stream status.</p>
|
||||
*/
|
||||
public class RstInfo
|
||||
{
|
||||
private final int streamId;
|
||||
private final StreamStatus streamStatus;
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link RstInfo} with the given stream id and stream status</p>
|
||||
*
|
||||
* @param streamId the stream id
|
||||
* @param streamStatus the stream status
|
||||
*/
|
||||
public RstInfo(int streamId, StreamStatus streamStatus)
|
||||
{
|
||||
this.streamId = streamId;
|
||||
this.streamStatus = streamStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the stream id
|
||||
*/
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the stream status
|
||||
*/
|
||||
public StreamStatus getStreamStatus()
|
||||
{
|
||||
return streamStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("RST stream=%d %s", streamId, streamStatus);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>Helper class that holds useful SPDY constants.</p>
|
||||
*/
|
||||
public class SPDY
|
||||
{
|
||||
/**
|
||||
* <p>Constant that indicates the version 2 of the SPDY protocol</p>
|
||||
*/
|
||||
public static final short V2 = 2;
|
||||
|
||||
/**
|
||||
* <p>Constant that indicates the version 3 of the SPDY protocol</p>
|
||||
*/
|
||||
public static final short V3 = 3;
|
||||
|
||||
private SPDY()
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>An unrecoverable exception that signals to the application that
|
||||
* something wrong happened.</p>
|
||||
*/
|
||||
public class SPDYException extends RuntimeException
|
||||
{
|
||||
public SPDYException()
|
||||
{
|
||||
}
|
||||
|
||||
public SPDYException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SPDYException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public SPDYException(Throwable cause)
|
||||
{
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public SPDYException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)
|
||||
{
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.util.EventListener;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* <p>A {@link Session} represents the client-side endpoint of a SPDY connection to a single origin server.</p>
|
||||
* <p>Once a {@link Session} has been obtained, it can be used to open SPDY streams:</p>
|
||||
* <pre>
|
||||
* Session session = ...;
|
||||
* SynInfo synInfo = new SynInfo(true);
|
||||
* session.syn(synInfo, new Stream.FrameListener.Adapter()
|
||||
* {
|
||||
* public void onReply(Stream stream, ReplyInfo replyInfo)
|
||||
* {
|
||||
* // Stream reply received
|
||||
* }
|
||||
* });
|
||||
* </pre>
|
||||
* <p>A {@link Session} is the active part of the endpoint, and by calling its API applications can generate
|
||||
* events on the connection; conversely {@link SessionFrameListener} is the passive part of the endpoint, and
|
||||
* has callbacks that are invoked when events happen on the connection.</p>
|
||||
*
|
||||
* @see SessionFrameListener
|
||||
*/
|
||||
public interface Session
|
||||
{
|
||||
/**
|
||||
* @return the SPDY protocol version used by this session
|
||||
*/
|
||||
public short getVersion();
|
||||
|
||||
/**
|
||||
* <p>Registers the given {@code listener} to be notified of session events.</p>
|
||||
*
|
||||
* @param listener the listener to register
|
||||
* @see #removeListener(Listener)
|
||||
*/
|
||||
public void addListener(Listener listener);
|
||||
|
||||
/**
|
||||
* <p>Deregisters the give {@code listener} from being notified of session events.</p>
|
||||
*
|
||||
* @param listener the listener to deregister
|
||||
* @see #addListener(Listener)
|
||||
*/
|
||||
public void removeListener(Listener listener);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a SYN_FRAME to create a new {@link Stream SPDY stream}.</p>
|
||||
* <p>Callers may use the returned future to wait for the stream to be created, and
|
||||
* use the stream, for example, to send data frames.</p>
|
||||
*
|
||||
* @param synInfo the metadata to send on stream creation
|
||||
* @param listener the listener to invoke when events happen on the stream just created
|
||||
* @return a future for the stream that will be created
|
||||
* @see #syn(SynInfo, StreamFrameListener, long, TimeUnit, Handler)
|
||||
*/
|
||||
public Future<Stream> syn(SynInfo synInfo, StreamFrameListener listener);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a SYN_FRAME to create a new {@link Stream SPDY stream}.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* stream has been created and use the stream, for example, to send data frames.</p>
|
||||
*
|
||||
* @param synInfo the metadata to send on stream creation
|
||||
* @param listener the listener to invoke when events happen on the stream just created
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param handler the completion handler that gets notified of stream creation
|
||||
* @see #syn(SynInfo, StreamFrameListener)
|
||||
*/
|
||||
public void syn(SynInfo synInfo, StreamFrameListener listener, long timeout, TimeUnit unit, Handler<Stream> handler);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a RST_STREAM to abort a stream.</p>
|
||||
* <p>Callers may use the returned future to wait for the reset to be sent.</p>
|
||||
*
|
||||
* @param rstInfo the metadata to reset the stream
|
||||
* @return a future to wait for the reset to be sent
|
||||
* @see #rst(RstInfo, long, TimeUnit, Handler)
|
||||
*/
|
||||
public Future<Void> rst(RstInfo rstInfo);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a RST_STREAM to abort a stream.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* reset has been actually sent.</p>
|
||||
*
|
||||
* @param rstInfo the metadata to reset the stream
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param handler the completion handler that gets notified of reset's send
|
||||
* @see #rst(RstInfo)
|
||||
*/
|
||||
public void rst(RstInfo rstInfo, long timeout, TimeUnit unit, Handler<Void> handler);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a SETTINGS to configure the SPDY connection.</p>
|
||||
* <p>Callers may use the returned future to wait for the settings to be sent.</p>
|
||||
*
|
||||
* @param settingsInfo the metadata to send
|
||||
* @return a future to wait for the settings to be sent
|
||||
* @see #settings(SettingsInfo, long, TimeUnit, Handler)
|
||||
*/
|
||||
public Future<Void> settings(SettingsInfo settingsInfo);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a SETTINGS to configure the SPDY connection.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* settings has been actually sent.</p>
|
||||
*
|
||||
* @param settingsInfo the metadata to send
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param handler the completion handler that gets notified of settings' send
|
||||
* @see #settings(SettingsInfo)
|
||||
*/
|
||||
public void settings(SettingsInfo settingsInfo, long timeout, TimeUnit unit, Handler<Void> handler);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a PING, normally to measure round-trip time.</p>
|
||||
* <p>Callers may use the returned future to wait for the ping to be sent.</p>
|
||||
*
|
||||
* @return a future for the metadata sent
|
||||
* @see #ping(long, TimeUnit, Handler)
|
||||
*/
|
||||
public Future<PingInfo> ping();
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a PING, normally to measure round-trip time.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* ping has been actually sent.</p>
|
||||
*
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param handler the completion handler that gets notified of ping's send
|
||||
* @see #ping()
|
||||
*/
|
||||
public void ping(long timeout, TimeUnit unit, Handler<PingInfo> handler);
|
||||
|
||||
/**
|
||||
* <p>Closes gracefully this session, sending a GO_AWAY frame and then closing the TCP connection.</p>
|
||||
* <p>Callers may use the returned future to wait for the go away to be sent.</p>
|
||||
*
|
||||
* @return a future to wait for the go away to be sent
|
||||
* @see #goAway(long, TimeUnit, Handler)
|
||||
*/
|
||||
public Future<Void> goAway();
|
||||
|
||||
/**
|
||||
* <p>Closes gracefully this session, sending a GO_AWAY frame and then closing the TCP connection.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* go away has been actually sent.</p>
|
||||
*
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param handler the completion handler that gets notified of go away's send
|
||||
* @see #goAway()
|
||||
*/
|
||||
public void goAway(long timeout, TimeUnit unit, Handler<Void> handler);
|
||||
|
||||
/**
|
||||
* @return the streams currently active in this session
|
||||
*/
|
||||
public List<Stream> getStreams();
|
||||
|
||||
/**
|
||||
* <p>Super interface for listeners with callbacks that are invoked on specific session events.</p>
|
||||
*/
|
||||
public interface Listener extends EventListener
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Specialized listener that is invoked upon creation and removal of streams.</p>
|
||||
*/
|
||||
public interface StreamListener extends Listener
|
||||
{
|
||||
/**
|
||||
* <p>Callback invoked when a new SPDY stream is created.</p>
|
||||
*
|
||||
* @param stream the stream just created
|
||||
*/
|
||||
public void onStreamCreated(Stream stream);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when a SPDY stream is closed.</p>
|
||||
*
|
||||
* @param stream the stream just closed.
|
||||
*/
|
||||
public void onStreamClosed(Stream stream);
|
||||
|
||||
/**
|
||||
* <p>Empty implementation of {@link StreamListener}.</p>
|
||||
*/
|
||||
public static class Adapter implements StreamListener
|
||||
{
|
||||
@Override
|
||||
public void onStreamCreated(Stream stream)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamClosed(Stream stream)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.util.EventListener;
|
||||
|
||||
/**
|
||||
* <p>A {@link SessionFrameListener} is the passive counterpart of a {@link Session} and receives events happening
|
||||
* on a SPDY session.</p>
|
||||
*
|
||||
* @see Session
|
||||
*/
|
||||
public interface SessionFrameListener extends EventListener
|
||||
{
|
||||
/**
|
||||
* <p>Callback invoked when a request to create a stream has been received.</p>
|
||||
* <p>Application code should implement this method and reply to the stream creation, eventually
|
||||
* sending data:</p>
|
||||
* <pre>
|
||||
* public Stream.FrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
* {
|
||||
* // Do something with the metadata contained in synInfo
|
||||
*
|
||||
* if (stream.isHalfClosed()) // The other peer will not send data
|
||||
* {
|
||||
* stream.reply(new ReplyInfo(false));
|
||||
* stream.data(new StringDataInfo("foo", true));
|
||||
* return null; // Not interested in further stream events
|
||||
* }
|
||||
*
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
* <p>Alternatively, if the stream creation requires reading data sent from the other peer:</p>
|
||||
* <pre>
|
||||
* public Stream.FrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
* {
|
||||
* // Do something with the metadata contained in synInfo
|
||||
*
|
||||
* if (!stream.isHalfClosed()) // The other peer will send data
|
||||
* {
|
||||
* stream.reply(new ReplyInfo(true));
|
||||
* return new Stream.FrameListener.Adapter() // Interested in stream events
|
||||
* {
|
||||
* public void onData(Stream stream, DataInfo dataInfo)
|
||||
* {
|
||||
* // Do something with the incoming data in dataInfo
|
||||
* }
|
||||
* };
|
||||
* }
|
||||
*
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param stream the stream just created
|
||||
* @param synInfo the metadata sent on stream creation
|
||||
* @return a listener for stream events, or null if there is no interest in being notified of stream events
|
||||
*/
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when a stream error happens.</p>
|
||||
*
|
||||
* @param session the session
|
||||
* @param rstInfo the metadata of the stream error
|
||||
*/
|
||||
public void onRst(Session session, RstInfo rstInfo);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when a request to configure the SPDY connection has been received.</p>
|
||||
*
|
||||
* @param session the session
|
||||
* @param settingsInfo the metadata sent to configure
|
||||
*/
|
||||
public void onSettings(Session session, SettingsInfo settingsInfo);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when a ping request has completed its round-trip.</p>
|
||||
*
|
||||
* @param session the session
|
||||
* @param pingInfo the metadata received
|
||||
*/
|
||||
public void onPing(Session session, PingInfo pingInfo);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when the other peer signals that it is closing the connection.</p>
|
||||
*
|
||||
* @param session the session
|
||||
* @param goAwayInfo the metadata sent
|
||||
*/
|
||||
public void onGoAway(Session session, GoAwayInfo goAwayInfo);
|
||||
|
||||
/**
|
||||
* <p>Empty implementation of {@link SessionFrameListener}</p>
|
||||
*/
|
||||
public static class Adapter implements SessionFrameListener
|
||||
{
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRst(Session session, RstInfo rstInfo)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSettings(Session session, SettingsInfo settingsInfo)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPing(Session session, PingInfo pingInfo)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGoAway(Session session, GoAwayInfo goAwayInfo)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>An enumeration of session statuses.</p>
|
||||
*/
|
||||
public enum SessionStatus
|
||||
{
|
||||
/**
|
||||
* <p>The session status indicating no errors</p>
|
||||
*/
|
||||
OK(0),
|
||||
/**
|
||||
* <p>The session status indicating a protocol error</p>
|
||||
*/
|
||||
PROTOCOL_ERROR(1);
|
||||
|
||||
/**
|
||||
* @param code the session status code
|
||||
* @return a {@link SessionStatus} from the given code,
|
||||
* or null if no status exists
|
||||
*/
|
||||
public static SessionStatus from(int code)
|
||||
{
|
||||
return Codes.codes.get(code);
|
||||
}
|
||||
|
||||
private final int code;
|
||||
|
||||
private SessionStatus(int code)
|
||||
{
|
||||
this.code = code;
|
||||
Codes.codes.put(code, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the code of this {@link SessionStatus}
|
||||
*/
|
||||
public int getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
private static class Codes
|
||||
{
|
||||
private static final Map<Integer, SessionStatus> codes = new HashMap<>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
public class Settings implements Iterable<Settings.Setting>
|
||||
{
|
||||
private Map<ID, Settings.Setting> settings = new HashMap<>();
|
||||
|
||||
public Settings()
|
||||
{
|
||||
}
|
||||
|
||||
public Settings(Settings original, boolean immutable)
|
||||
{
|
||||
Map<ID, Settings.Setting> copy = new HashMap<>(original.size());
|
||||
copy.putAll(original.settings);
|
||||
settings = immutable ? Collections.unmodifiableMap(copy) : copy;
|
||||
}
|
||||
|
||||
public Setting get(ID id)
|
||||
{
|
||||
return settings.get(id);
|
||||
}
|
||||
|
||||
public void put(Setting setting)
|
||||
{
|
||||
settings.put(setting.id(), setting);
|
||||
}
|
||||
|
||||
public Setting remove(ID id)
|
||||
{
|
||||
return settings.remove(id);
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
return settings.size();
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
settings.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Settings that = (Settings)obj;
|
||||
return settings.equals(that.settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return settings.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Setting> iterator()
|
||||
{
|
||||
return settings.values().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return settings.toString();
|
||||
}
|
||||
|
||||
public static final class ID
|
||||
{
|
||||
public static ID UPLOAD_BANDWIDTH = new ID(1);
|
||||
public static ID DOWNLOAD_BANDWIDTH = new ID(2);
|
||||
public static ID ROUND_TRIP_TIME = new ID(3);
|
||||
public static ID MAX_CONCURRENT_STREAMS = new ID(4);
|
||||
public static ID CURRENT_CONGESTION_WINDOW = new ID(5);
|
||||
public static ID DOWNLOAD_RETRANSMISSION_RATE = new ID(6);
|
||||
public static ID INITIAL_WINDOW_SIZE = new ID(7);
|
||||
|
||||
public synchronized static ID from(int code)
|
||||
{
|
||||
ID id = Codes.codes.get(code);
|
||||
if (id == null)
|
||||
id = new ID(code);
|
||||
return id;
|
||||
}
|
||||
|
||||
private final int code;
|
||||
|
||||
private ID(int code)
|
||||
{
|
||||
this.code = code;
|
||||
Codes.codes.put(code, this);
|
||||
}
|
||||
|
||||
public int code()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.valueOf(code);
|
||||
}
|
||||
|
||||
private static class Codes
|
||||
{
|
||||
private static final Map<Integer, ID> codes = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static enum Flag
|
||||
{
|
||||
NONE((byte)0),
|
||||
PERSIST((byte)1),
|
||||
PERSISTED((byte)2);
|
||||
|
||||
public static Flag from(byte code)
|
||||
{
|
||||
return Codes.codes.get(code);
|
||||
}
|
||||
|
||||
private final byte code;
|
||||
|
||||
private Flag(byte code)
|
||||
{
|
||||
this.code = code;
|
||||
Codes.codes.put(code, this);
|
||||
}
|
||||
|
||||
public byte code()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
private static class Codes
|
||||
{
|
||||
private static final Map<Byte, Flag> codes = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Setting
|
||||
{
|
||||
private final ID id;
|
||||
private final Flag flag;
|
||||
private final int value;
|
||||
|
||||
public Setting(ID id, int value)
|
||||
{
|
||||
this(id, Flag.NONE, value);
|
||||
}
|
||||
|
||||
public Setting(ID id, Flag flag, int value)
|
||||
{
|
||||
this.id = id;
|
||||
this.flag = flag;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public ID id()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public Flag flag()
|
||||
{
|
||||
return flag;
|
||||
}
|
||||
|
||||
public int value()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Setting that = (Setting)obj;
|
||||
return value == that.value && flag == that.flag && id == that.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int result = id.hashCode();
|
||||
result = 31 * result + flag.hashCode();
|
||||
result = 31 * result + value;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("[id=%s,flags=%s:value=%d]", id(), flag(), value());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
public class SettingsInfo
|
||||
{
|
||||
public static final byte CLEAR_PERSISTED = 1;
|
||||
|
||||
private final Settings settings;
|
||||
private final boolean clearPersisted;
|
||||
|
||||
public SettingsInfo(Settings settings)
|
||||
{
|
||||
this(settings, false);
|
||||
}
|
||||
|
||||
public SettingsInfo(Settings settings, boolean clearPersisted)
|
||||
{
|
||||
this.settings = settings;
|
||||
this.clearPersisted = clearPersisted;
|
||||
}
|
||||
|
||||
public boolean isClearPersisted()
|
||||
{
|
||||
return clearPersisted;
|
||||
}
|
||||
|
||||
public byte getFlags()
|
||||
{
|
||||
return isClearPersisted() ? CLEAR_PERSISTED : 0;
|
||||
}
|
||||
|
||||
public Settings getSettings()
|
||||
{
|
||||
return settings;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.nio.channels.WritePendingException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* <p>A {@link Stream} represents a bidirectional exchange of data on top of a {@link Session}.</p>
|
||||
* <p>Differently from socket streams, where the input and output streams are permanently associated
|
||||
* with the socket (and hence with the connection that the socket represents), there can be multiple
|
||||
* SPDY streams for a SPDY session.</p>
|
||||
* <p>SPDY streams may terminate without this implying that the SPDY session is terminated.</p>
|
||||
* <p>If SPDY is used to transport the HTTP protocol, then a SPDY stream maps to a HTTP request/response
|
||||
* cycle, and after the request/response cycle is completed, the stream is closed, and other streams
|
||||
* may be opened. Differently from HTTP, though, multiple SPDY streams may be opened concurrently
|
||||
* on the same SPDY session.</p>
|
||||
* <p>Like {@link Session}, {@link Stream} is the active part and by calling its API applications
|
||||
* can generate events on the stream; conversely, {@link StreamFrameListener} is the passive part, and its
|
||||
* callbacks are invoked when events happen on the stream.</p>
|
||||
* <p>A {@link Stream} can send multiple data frames one after the other but implementations use a
|
||||
* flow control mechanism that only sends the data frames if the other end has signalled that it can
|
||||
* accept the frame.</p>
|
||||
* <p>Data frames should be sent sequentially only when the previous frame has been completely sent.
|
||||
* The reason for this requirement is to avoid potentially confusing code such as:</p>
|
||||
* <pre>
|
||||
* // WRONG CODE, DO NOT USE IT
|
||||
* final Stream stream = ...;
|
||||
* stream.data(StringDataInfo("chunk1", false), 5, TimeUnit.SECONDS, new Handler<Void>() { ... });
|
||||
* stream.data(StringDataInfo("chunk2", true), 1, TimeUnit.SECONDS, new Handler<Void>() { ... });
|
||||
* </pre>
|
||||
* <p>where the second call to {@link #data(DataInfo, long, TimeUnit, Handler)} has a timeout smaller
|
||||
* than the previous call.</p>
|
||||
* <p>The behavior of such style of invocations is unspecified (it may even throw an exception - similar
|
||||
* to {@link WritePendingException}).</p>
|
||||
* <p>The correct sending of data frames is the following:</p>
|
||||
* <pre>
|
||||
* final Stream stream = ...;
|
||||
* ...
|
||||
* // Blocking version
|
||||
* stream.data(new StringDataInfo("chunk1", false)).get(1, TimeUnit.SECONDS);
|
||||
* stream.data(new StringDataInfo("chunk2", true)).get(1, TimeUnit.SECONDS);
|
||||
*
|
||||
* // Asynchronous version
|
||||
* stream.data(new StringDataInfo("chunk1", false), 1, TimeUnit.SECONDS, new Handler.Adapter<Void>()
|
||||
* {
|
||||
* public void completed(Void context)
|
||||
* {
|
||||
* stream.data(new StringDataInfo("chunk2", true));
|
||||
* }
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @see StreamFrameListener
|
||||
*/
|
||||
public interface Stream
|
||||
{
|
||||
/**
|
||||
* @return the id of this stream
|
||||
*/
|
||||
public int getId();
|
||||
|
||||
/**
|
||||
* @return the priority of this stream
|
||||
*/
|
||||
public byte getPriority();
|
||||
|
||||
/**
|
||||
* @return the session this stream is associated to
|
||||
*/
|
||||
public Session getSession();
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a SYN_REPLY frame in response to a SYN_STREAM frame.</p>
|
||||
* <p>Callers may use the returned future to wait for the reply to be actually sent.</p>
|
||||
*
|
||||
* @param replyInfo the metadata to send
|
||||
* @return a future to wait for the reply to be sent
|
||||
* @see #reply(ReplyInfo, long, TimeUnit, Handler)
|
||||
* @see SessionFrameListener#onSyn(Stream, SynInfo)
|
||||
*/
|
||||
public Future<Void> reply(ReplyInfo replyInfo);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a SYN_REPLY frame in response to a SYN_STREAM frame.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* reply has been actually sent.</p>
|
||||
*
|
||||
* @param replyInfo the metadata to send
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param handler the completion handler that gets notified of reply sent
|
||||
* @see #reply(ReplyInfo)
|
||||
*/
|
||||
public void reply(ReplyInfo replyInfo, long timeout, TimeUnit unit, Handler<Void> handler);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a DATA frame on this stream.</p>
|
||||
* <p>DATA frames should always be sent after a SYN_REPLY frame.</p>
|
||||
* <p>Callers may use the returned future to wait for the data to be actually sent.</p>
|
||||
*
|
||||
* @param dataInfo the metadata to send
|
||||
* @return a future to wait for the data to be sent
|
||||
* @see #data(DataInfo, long, TimeUnit, Handler)
|
||||
* @see #reply(ReplyInfo)
|
||||
*/
|
||||
public Future<Void> data(DataInfo dataInfo);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a DATA frame on this stream.</p>
|
||||
* <p>DATA frames should always be sent after a SYN_REPLY frame.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* data has been actually sent.</p>
|
||||
*
|
||||
* @param dataInfo the metadata to send
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param handler the completion handler that gets notified of data sent
|
||||
* @see #data(DataInfo)
|
||||
*/
|
||||
public void data(DataInfo dataInfo, long timeout, TimeUnit unit, Handler<Void> handler);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a HEADER frame on this stream.</p>
|
||||
* <p>HEADERS frames should always be sent after a SYN_REPLY frame.</p>
|
||||
* <p>Callers may use the returned future to wait for the headers to be actually sent.</p>
|
||||
*
|
||||
* @param headersInfo the metadata to send
|
||||
* @return a future to wait for the headers to be sent
|
||||
* @see #headers(HeadersInfo, long, TimeUnit, Handler)
|
||||
* @see #reply(ReplyInfo)
|
||||
*/
|
||||
public Future<Void> headers(HeadersInfo headersInfo);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a HEADER frame on this stream.</p>
|
||||
* <p>HEADERS frames should always be sent after a SYN_REPLY frame.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* headers have been actually sent.</p>
|
||||
*
|
||||
* @param headersInfo the metadata to send
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param handler the completion handler that gets notified of headers sent
|
||||
* @see #headers(HeadersInfo)
|
||||
*/
|
||||
public void headers(HeadersInfo headersInfo, long timeout, TimeUnit unit, Handler<Void> handler);
|
||||
|
||||
/**
|
||||
* @return whether this stream has been closed by both parties
|
||||
* @see #isHalfClosed()
|
||||
*/
|
||||
public boolean isClosed();
|
||||
|
||||
/**
|
||||
* @return whether this stream has been closed by one party only
|
||||
* @see #isClosed() * @param timeout the timeout for the stream creation
|
||||
* @param unit the timeout's unit
|
||||
|
||||
*/
|
||||
public boolean isHalfClosed();
|
||||
|
||||
/**
|
||||
* @param key the attribute key
|
||||
* @return an arbitrary object associated with the given key to this stream
|
||||
* @see #setAttribute(String, Object)
|
||||
*/
|
||||
public Object getAttribute(String key);
|
||||
|
||||
/**
|
||||
* @param key the attribute key
|
||||
* @param value an arbitrary object to associate with the given key to this stream
|
||||
* @see #getAttribute(String)
|
||||
* @see #removeAttribute(String)
|
||||
*/
|
||||
public void setAttribute(String key, Object value);
|
||||
|
||||
/**
|
||||
* @param key the attribute key
|
||||
* @return the arbitrary object associated with the given key to this stream
|
||||
* @see #setAttribute(String, Object)
|
||||
*/
|
||||
public Object removeAttribute(String key);
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.util.EventListener;
|
||||
|
||||
/**
|
||||
* <p>A {@link StreamFrameListener} is the passive counterpart of a {@link Stream} and receives
|
||||
* events happening on a SPDY stream.</p>
|
||||
*
|
||||
* @see Stream
|
||||
*/
|
||||
public interface StreamFrameListener extends EventListener
|
||||
{
|
||||
/**
|
||||
* <p>Callback invoked when a reply to a stream creation has been received.</p>
|
||||
* <p>Application code may implement this method to send more data to the other end:</p>
|
||||
* <pre>
|
||||
* public void onReply(Stream stream, ReplyInfo replyInfo)
|
||||
* {
|
||||
* stream.data(new StringDataInfo("content"), true);
|
||||
* }
|
||||
* </pre>
|
||||
* @param stream the stream
|
||||
* @param replyInfo the reply metadata
|
||||
*/
|
||||
public void onReply(Stream stream, ReplyInfo replyInfo);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when headers are received on a stream.</p>
|
||||
*
|
||||
* @param stream the stream
|
||||
* @param headersInfo the headers metadata
|
||||
*/
|
||||
public void onHeaders(Stream stream, HeadersInfo headersInfo);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when data bytes are received on a stream.</p>
|
||||
* <p>Implementers should be read or consume the content of the
|
||||
* {@link DataInfo} before this method returns.</p>
|
||||
*
|
||||
* @param stream the stream
|
||||
* @param dataInfo the data metadata
|
||||
*/
|
||||
public void onData(Stream stream, DataInfo dataInfo);
|
||||
|
||||
/**
|
||||
* <p>Empty implementation of {@link StreamFrameListener}</p>
|
||||
*/
|
||||
public static class Adapter implements StreamFrameListener
|
||||
{
|
||||
@Override
|
||||
public void onReply(Stream stream, ReplyInfo replyInfo)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeaders(Stream stream, HeadersInfo headersInfo)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onData(Stream stream, DataInfo dataInfo)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>An enumeration of stream statuses.</p>
|
||||
*/
|
||||
public enum StreamStatus
|
||||
{
|
||||
/**
|
||||
* <p>The stream status indicating a protocol error</p>
|
||||
*/
|
||||
PROTOCOL_ERROR(1, 1),
|
||||
/**
|
||||
* <p>The stream status indicating that the stream is not valid</p>
|
||||
*/
|
||||
INVALID_STREAM(2, 2),
|
||||
/**
|
||||
* <p>The stream status indicating that the stream has been refused</p>
|
||||
*/
|
||||
REFUSED_STREAM(3, 3),
|
||||
/**
|
||||
* <p>The stream status indicating that the implementation does not support the SPDY version of the stream</p>
|
||||
*/
|
||||
UNSUPPORTED_VERSION(4, 4),
|
||||
/**
|
||||
* <p>The stream status indicating that the stream is no longer needed</p>
|
||||
*/
|
||||
CANCEL_STREAM(5, 5),
|
||||
/**
|
||||
* <p>The stream status indicating an implementation error</p>
|
||||
*/
|
||||
INTERNAL_ERROR(6, 11),
|
||||
/**
|
||||
* <p>The stream status indicating a flow control error</p>
|
||||
*/
|
||||
FLOW_CONTROL_ERROR(7, 6),
|
||||
/**
|
||||
* <p>The stream status indicating a stream opened more than once</p>
|
||||
*/
|
||||
STREAM_IN_USE(-1, 7),
|
||||
/**
|
||||
* <p>The stream status indicating data on a stream already closed</p>
|
||||
*/
|
||||
STREAM_ALREADY_CLOSED(-1, 8),
|
||||
/**
|
||||
* <p>The stream status indicating credentials not valid</p>
|
||||
*/
|
||||
INVALID_CREDENTIALS(-1, 9),
|
||||
/**
|
||||
* <p>The stream status indicating that the implementation could not support a frame too large</p>
|
||||
*/
|
||||
FRAME_TOO_LARGE(-1, 10);
|
||||
|
||||
/**
|
||||
* @param version the SPDY protocol version
|
||||
* @param code the stream status code
|
||||
* @return a {@link StreamStatus} from the given version and code,
|
||||
* or null if no such status exists
|
||||
*/
|
||||
public static StreamStatus from(short version, int code)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
return Codes.v2Codes.get(code);
|
||||
case SPDY.V3:
|
||||
return Codes.v3Codes.get(code);
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private final int v2Code;
|
||||
private final int v3Code;
|
||||
|
||||
private StreamStatus(int v2Code, int v3Code)
|
||||
{
|
||||
this.v2Code = v2Code;
|
||||
if (v2Code >= 0)
|
||||
Codes.v2Codes.put(v2Code, this);
|
||||
this.v3Code = v3Code;
|
||||
if (v3Code >= 0)
|
||||
Codes.v3Codes.put(v3Code, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param version the SPDY protocol version
|
||||
* @return the stream status code
|
||||
*/
|
||||
public int getCode(short version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
return v2Code;
|
||||
case SPDY.V3:
|
||||
return v3Code;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Codes
|
||||
{
|
||||
private static final Map<Integer, StreamStatus> v2Codes = new HashMap<>();
|
||||
private static final Map<Integer, StreamStatus> v3Codes = new HashMap<>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* <p>Specialized {@link DataInfo} for {@link String} content.</p>
|
||||
*/
|
||||
public class StringDataInfo extends BytesDataInfo
|
||||
{
|
||||
public StringDataInfo(String string, boolean close)
|
||||
{
|
||||
this(string, close, false);
|
||||
}
|
||||
|
||||
public StringDataInfo(String string, boolean close, boolean compress)
|
||||
{
|
||||
super(string.getBytes(Charset.forName("UTF-8")), close, compress);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>A container for SYN_STREAM frames metadata and data.</p>
|
||||
*/
|
||||
public class SynInfo
|
||||
{
|
||||
/**
|
||||
* <p>Flag that indicates that this {@link DataInfo} is the last frame in the stream.</p>
|
||||
*
|
||||
* @see #isClose()
|
||||
* @see #getFlags()
|
||||
*/
|
||||
public static final byte FLAG_CLOSE = 1;
|
||||
public static final byte FLAG_UNIDIRECTIONAL = 2;
|
||||
|
||||
private final boolean close;
|
||||
private final boolean unidirectional;
|
||||
private final int associatedStreamId;
|
||||
private final byte priority;
|
||||
private final Headers headers;
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link SynInfo} instance with empty headers and the given close flag,
|
||||
* not unidirectional, without associated stream, and with default priority.</p>
|
||||
*
|
||||
* @param close the value of the close flag
|
||||
*/
|
||||
public SynInfo(boolean close)
|
||||
{
|
||||
this(new Headers(), close);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link ReplyInfo} instance with the given headers and the given close flag,
|
||||
* not unidirectional, without associated stream, and with default priority.</p>
|
||||
*
|
||||
* @param headers the {@link Headers}
|
||||
* @param close the value of the close flag
|
||||
*/
|
||||
public SynInfo(Headers headers, boolean close)
|
||||
{
|
||||
this(headers, close, false, 0, (byte)0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link ReplyInfo} instance with the given headers and the given close flag,
|
||||
* the given unidirectional flag, the given associated stream, and with the given priority.</p>
|
||||
*
|
||||
* @param headers the {@link Headers}
|
||||
* @param close the value of the close flag
|
||||
* @param unidirectional the value of the unidirectional flag
|
||||
* @param associatedStreamId the associated stream id
|
||||
* @param priority the priority
|
||||
*/
|
||||
public SynInfo(Headers headers, boolean close, boolean unidirectional, int associatedStreamId, byte priority)
|
||||
{
|
||||
this.close = close;
|
||||
this.unidirectional = unidirectional;
|
||||
this.associatedStreamId = associatedStreamId;
|
||||
this.priority = priority;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of the close flag
|
||||
*/
|
||||
public boolean isClose()
|
||||
{
|
||||
return close;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of the unidirectional flag
|
||||
*/
|
||||
public boolean isUnidirectional()
|
||||
{
|
||||
return unidirectional;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the associated stream id
|
||||
*/
|
||||
public int getAssociatedStreamId()
|
||||
{
|
||||
return associatedStreamId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the priority
|
||||
*/
|
||||
public byte getPriority()
|
||||
{
|
||||
return priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link Headers}
|
||||
*/
|
||||
public Headers getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the close and unidirectional flags as integer
|
||||
* @see #FLAG_CLOSE
|
||||
* @see #FLAG_UNIDIRECTIONAL
|
||||
*/
|
||||
public byte getFlags()
|
||||
{
|
||||
byte flags = isClose() ? FLAG_CLOSE : 0;
|
||||
flags += isUnidirectional() ? FLAG_UNIDIRECTIONAL : 0;
|
||||
return flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("SYN close=%b headers=%s", close, headers);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api.server;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.Session;
|
||||
import org.eclipse.jetty.spdy.api.SessionFrameListener;
|
||||
|
||||
/**
|
||||
* <p>Specific, server-side, {@link SessionFrameListener}.</p>
|
||||
* <p>In addition to {@link SessionFrameListener}, this listener adds method
|
||||
* {@link #onConnect(Session)} that is called when a client first connects to the
|
||||
* server and may be used by a server-side application to send a SETTINGS frame
|
||||
* to configure the connection before the client can open any stream.</p>
|
||||
*/
|
||||
public interface ServerSessionFrameListener extends SessionFrameListener
|
||||
{
|
||||
/**
|
||||
* <p>Callback invoked when a client opens a connection.</p>
|
||||
*
|
||||
* @param session the session
|
||||
*/
|
||||
public void onConnect(Session session);
|
||||
|
||||
/**
|
||||
* <p>Empty implementation of {@link ServerSessionFrameListener}</p>
|
||||
*/
|
||||
public static class Adapter extends SessionFrameListener.Adapter implements ServerSessionFrameListener
|
||||
{
|
||||
@Override
|
||||
public void onConnect(Session session)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
public abstract class ControlFrame
|
||||
{
|
||||
public static final int HEADER_LENGTH = 8;
|
||||
|
||||
private final short version;
|
||||
private final ControlFrameType type;
|
||||
private final byte flags;
|
||||
|
||||
public ControlFrame(short version, ControlFrameType type, byte flags)
|
||||
{
|
||||
this.version = version;
|
||||
this.type = type;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public short getVersion()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
public ControlFrameType getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
public byte getFlags()
|
||||
{
|
||||
return flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s frame v%s", getType(), getVersion());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum ControlFrameType
|
||||
{
|
||||
SYN_STREAM((short)1),
|
||||
SYN_REPLY((short)2),
|
||||
RST_STREAM((short)3),
|
||||
SETTINGS((short)4),
|
||||
NOOP((short)5),
|
||||
PING((short)6),
|
||||
GO_AWAY((short)7),
|
||||
HEADERS((short)8),
|
||||
WINDOW_UPDATE((short)9);
|
||||
|
||||
public static ControlFrameType from(short code)
|
||||
{
|
||||
return Codes.codes.get(code);
|
||||
}
|
||||
|
||||
private final short code;
|
||||
|
||||
private ControlFrameType(short code)
|
||||
{
|
||||
this.code = code;
|
||||
Codes.codes.put(code, this);
|
||||
}
|
||||
|
||||
public short getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
private static class Codes
|
||||
{
|
||||
private static final Map<Short, ControlFrameType> codes = new HashMap<>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
|
||||
public class DataFrame
|
||||
{
|
||||
public static final int HEADER_LENGTH = 8;
|
||||
|
||||
private final int streamId;
|
||||
private final byte flags;
|
||||
private final int length;
|
||||
|
||||
public DataFrame(int streamId, byte flags, int length)
|
||||
{
|
||||
this.streamId = streamId;
|
||||
this.flags = flags;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public byte getFlags()
|
||||
{
|
||||
return flags;
|
||||
}
|
||||
|
||||
public int getLength()
|
||||
{
|
||||
return length;
|
||||
}
|
||||
|
||||
public boolean isClose()
|
||||
{
|
||||
return (flags & DataInfo.FLAG_CLOSE) == DataInfo.FLAG_CLOSE;
|
||||
}
|
||||
|
||||
public boolean isCompress()
|
||||
{
|
||||
return (flags & DataInfo.FLAG_COMPRESS) == DataInfo.FLAG_COMPRESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("DATA frame stream=%d length=%d close=%b compress=%b", getStreamId(), getLength(), isClose(), isCompress());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
|
||||
public class GoAwayFrame extends ControlFrame
|
||||
{
|
||||
private final int lastStreamId;
|
||||
private final int statusCode;
|
||||
|
||||
public GoAwayFrame(short version, int lastStreamId, int statusCode)
|
||||
{
|
||||
super(version, ControlFrameType.GO_AWAY, (byte)0);
|
||||
this.lastStreamId = lastStreamId;
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public int getLastStreamId()
|
||||
{
|
||||
return lastStreamId;
|
||||
}
|
||||
|
||||
public int getStatusCode()
|
||||
{
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
SessionStatus sessionStatus = SessionStatus.from(getStatusCode());
|
||||
return String.format("%s last_stream=%d status=%s", super.toString(), getLastStreamId(), sessionStatus == null ? getStatusCode() : sessionStatus);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.HeadersInfo;
|
||||
|
||||
public class HeadersFrame extends ControlFrame
|
||||
{
|
||||
private final int streamId;
|
||||
private final Headers headers;
|
||||
|
||||
public HeadersFrame(short version, byte flags, int streamId, Headers headers)
|
||||
{
|
||||
super(version, ControlFrameType.HEADERS, flags);
|
||||
this.streamId = streamId;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public Headers getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
public boolean isClose()
|
||||
{
|
||||
return (getFlags() & HeadersInfo.FLAG_CLOSE) == HeadersInfo.FLAG_CLOSE;
|
||||
}
|
||||
|
||||
public boolean isResetCompression()
|
||||
{
|
||||
return (getFlags() & HeadersInfo.FLAG_RESET_COMPRESSION) == HeadersInfo.FLAG_RESET_COMPRESSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s stream=%d close=%b reset_compression=%b", super.toString(), getStreamId(), isClose(), isResetCompression());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
|
||||
public class NoOpFrame extends ControlFrame
|
||||
{
|
||||
public NoOpFrame()
|
||||
{
|
||||
super(SPDY.V2, ControlFrameType.NOOP, (byte)0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
public class PingFrame extends ControlFrame
|
||||
{
|
||||
private final int pingId;
|
||||
|
||||
public PingFrame(short version, int pingId)
|
||||
{
|
||||
super(version, ControlFrameType.PING, (byte)0);
|
||||
this.pingId = pingId;
|
||||
}
|
||||
|
||||
public int getPingId()
|
||||
{
|
||||
return pingId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s ping=%d", super.toString(), getPingId());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.StreamStatus;
|
||||
|
||||
public class RstStreamFrame extends ControlFrame
|
||||
{
|
||||
private final int streamId;
|
||||
private final int statusCode;
|
||||
|
||||
public RstStreamFrame(short version, int streamId, int statusCode)
|
||||
{
|
||||
super(version, ControlFrameType.RST_STREAM, (byte)0);
|
||||
this.streamId = streamId;
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public int getStatusCode()
|
||||
{
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StreamStatus streamStatus = StreamStatus.from(getVersion(), getStatusCode());
|
||||
return String.format("%s stream=%d status=%s", super.toString(), getStreamId(), streamStatus == null ? getStatusCode() : streamStatus);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.Settings;
|
||||
import org.eclipse.jetty.spdy.api.SettingsInfo;
|
||||
|
||||
public class SettingsFrame extends ControlFrame
|
||||
{
|
||||
private final Settings settings;
|
||||
|
||||
public SettingsFrame(short version, byte flags, Settings settings)
|
||||
{
|
||||
super(version, ControlFrameType.SETTINGS, flags);
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public boolean isClearPersisted()
|
||||
{
|
||||
return (getFlags() & SettingsInfo.CLEAR_PERSISTED) == SettingsInfo.CLEAR_PERSISTED;
|
||||
}
|
||||
|
||||
public Settings getSettings()
|
||||
{
|
||||
return settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s clear_persisted=%b settings=%s", super.toString(), isClearPersisted(), getSettings());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.ReplyInfo;
|
||||
|
||||
public class SynReplyFrame extends ControlFrame
|
||||
{
|
||||
private final int streamId;
|
||||
private final Headers headers;
|
||||
|
||||
public SynReplyFrame(short version, byte flags, int streamId, Headers headers)
|
||||
{
|
||||
super(version, ControlFrameType.SYN_REPLY, flags);
|
||||
this.streamId = streamId;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public Headers getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
public boolean isClose()
|
||||
{
|
||||
return (getFlags() & ReplyInfo.FLAG_CLOSE) == ReplyInfo.FLAG_CLOSE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s stream=%d close=%b", super.toString(), getStreamId(), isClose());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
|
||||
public class SynStreamFrame extends ControlFrame
|
||||
{
|
||||
private final int streamId;
|
||||
private final int associatedStreamId;
|
||||
private final byte priority;
|
||||
private final Headers headers;
|
||||
|
||||
public SynStreamFrame(short version, byte flags, int streamId, int associatedStreamId, byte priority, Headers headers)
|
||||
{
|
||||
super(version, ControlFrameType.SYN_STREAM, flags);
|
||||
this.streamId = streamId;
|
||||
this.associatedStreamId = associatedStreamId;
|
||||
this.priority = priority;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public int getAssociatedStreamId()
|
||||
{
|
||||
return associatedStreamId;
|
||||
}
|
||||
|
||||
public byte getPriority()
|
||||
{
|
||||
return priority;
|
||||
}
|
||||
|
||||
public Headers getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
public boolean isClose()
|
||||
{
|
||||
return (getFlags() & SynInfo.FLAG_CLOSE) == SynInfo.FLAG_CLOSE;
|
||||
}
|
||||
|
||||
public boolean isUnidirectional()
|
||||
{
|
||||
return (getFlags() & SynInfo.FLAG_UNIDIRECTIONAL) == SynInfo.FLAG_UNIDIRECTIONAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s stream=%d close=%b", super.toString(), getStreamId(), isClose());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
public class WindowUpdateFrame extends ControlFrame
|
||||
{
|
||||
private final int streamId;
|
||||
private final int windowDelta;
|
||||
|
||||
public WindowUpdateFrame(short version, int streamId, int windowDelta)
|
||||
{
|
||||
super(version, ControlFrameType.WINDOW_UPDATE, (byte)0);
|
||||
this.streamId = streamId;
|
||||
this.windowDelta = windowDelta;
|
||||
}
|
||||
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public int getWindowDelta()
|
||||
{
|
||||
return windowDelta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s stream=%d delta=%d", super.toString(), getStreamId(), getWindowDelta());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
|
||||
public abstract class ControlFrameGenerator
|
||||
{
|
||||
private final ByteBufferPool bufferPool;
|
||||
|
||||
protected ControlFrameGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
this.bufferPool = bufferPool;
|
||||
}
|
||||
|
||||
protected ByteBufferPool getByteBufferPool()
|
||||
{
|
||||
return bufferPool;
|
||||
}
|
||||
|
||||
public abstract ByteBuffer generate(ControlFrame frame);
|
||||
|
||||
protected void generateControlFrameHeader(ControlFrame frame, int frameLength, ByteBuffer buffer)
|
||||
{
|
||||
buffer.putShort((short)(0x8000 + frame.getVersion()));
|
||||
buffer.putShort(frame.getType().getCode());
|
||||
int flagsAndLength = frame.getFlags();
|
||||
flagsAndLength <<= 24;
|
||||
flagsAndLength += frameLength;
|
||||
buffer.putInt(flagsAndLength);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.frames.DataFrame;
|
||||
|
||||
public class DataFrameGenerator
|
||||
{
|
||||
private final ByteBufferPool bufferPool;
|
||||
|
||||
public DataFrameGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
this.bufferPool = bufferPool;
|
||||
}
|
||||
|
||||
public ByteBuffer generate(int streamId, int length, DataInfo dataInfo)
|
||||
{
|
||||
ByteBuffer buffer = bufferPool.acquire(DataFrame.HEADER_LENGTH + length, true);
|
||||
buffer.position(DataFrame.HEADER_LENGTH);
|
||||
// Guaranteed to always be >= 0
|
||||
int read = dataInfo.readInto(buffer);
|
||||
|
||||
buffer.putInt(0, streamId & 0x7F_FF_FF_FF);
|
||||
buffer.putInt(4, read & 0x00_FF_FF_FF);
|
||||
|
||||
byte flags = dataInfo.getFlags();
|
||||
if (dataInfo.available() > 0)
|
||||
flags &= ~DataInfo.FLAG_CLOSE;
|
||||
buffer.put(4, flags);
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumMap;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrameType;
|
||||
|
||||
public class Generator
|
||||
{
|
||||
private final EnumMap<ControlFrameType, ControlFrameGenerator> generators = new EnumMap<>(ControlFrameType.class);
|
||||
private final DataFrameGenerator dataFrameGenerator;
|
||||
|
||||
public Generator(ByteBufferPool bufferPool, CompressionFactory.Compressor compressor)
|
||||
{
|
||||
HeadersBlockGenerator headersBlockGenerator = new HeadersBlockGenerator(compressor);
|
||||
generators.put(ControlFrameType.SYN_STREAM, new SynStreamGenerator(bufferPool, headersBlockGenerator));
|
||||
generators.put(ControlFrameType.SYN_REPLY, new SynReplyGenerator(bufferPool, headersBlockGenerator));
|
||||
generators.put(ControlFrameType.RST_STREAM, new RstStreamGenerator(bufferPool));
|
||||
generators.put(ControlFrameType.SETTINGS, new SettingsGenerator(bufferPool));
|
||||
generators.put(ControlFrameType.NOOP, new NoOpGenerator(bufferPool));
|
||||
generators.put(ControlFrameType.PING, new PingGenerator(bufferPool));
|
||||
generators.put(ControlFrameType.GO_AWAY, new GoAwayGenerator(bufferPool));
|
||||
generators.put(ControlFrameType.HEADERS, new HeadersGenerator(bufferPool, headersBlockGenerator));
|
||||
generators.put(ControlFrameType.WINDOW_UPDATE, new WindowUpdateGenerator(bufferPool));
|
||||
|
||||
dataFrameGenerator = new DataFrameGenerator(bufferPool);
|
||||
}
|
||||
|
||||
public ByteBuffer control(ControlFrame frame)
|
||||
{
|
||||
ControlFrameGenerator generator = generators.get(frame.getType());
|
||||
return generator.generate(frame);
|
||||
}
|
||||
|
||||
public ByteBuffer data(int streamId, int length, DataInfo dataInfo)
|
||||
{
|
||||
return dataFrameGenerator.generate(streamId, length, dataInfo);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.GoAwayFrame;
|
||||
|
||||
public class GoAwayGenerator extends ControlFrameGenerator
|
||||
{
|
||||
public GoAwayGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
super(bufferPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
GoAwayFrame goAway = (GoAwayFrame)frame;
|
||||
|
||||
int frameBodyLength = 8;
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(goAway, frameBodyLength, buffer);
|
||||
|
||||
buffer.putInt(goAway.getLastStreamId() & 0x7F_FF_FF_FF);
|
||||
writeStatusCode(goAway, buffer);
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private void writeStatusCode(GoAwayFrame goAway, ByteBuffer buffer)
|
||||
{
|
||||
switch (goAway.getVersion())
|
||||
{
|
||||
case SPDY.V2:
|
||||
break;
|
||||
case SPDY.V3:
|
||||
buffer.putInt(goAway.getStatusCode());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.eclipse.jetty.spdy.CompressionDictionary;
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
|
||||
public class HeadersBlockGenerator
|
||||
{
|
||||
private final CompressionFactory.Compressor compressor;
|
||||
private boolean needsDictionary = true;
|
||||
|
||||
public HeadersBlockGenerator(CompressionFactory.Compressor compressor)
|
||||
{
|
||||
this.compressor = compressor;
|
||||
}
|
||||
|
||||
public ByteBuffer generate(short version, Headers headers)
|
||||
{
|
||||
// TODO: ByteArrayOutputStream is quite inefficient, but grows on demand; optimize using ByteBuffer ?
|
||||
Charset iso1 = Charset.forName("ISO-8859-1");
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(headers.size() * 64);
|
||||
writeCount(version, buffer, headers.size());
|
||||
for (Headers.Header header : headers)
|
||||
{
|
||||
String name = header.name();
|
||||
byte[] nameBytes = name.getBytes(iso1);
|
||||
writeNameLength(version, buffer, nameBytes.length);
|
||||
buffer.write(nameBytes, 0, nameBytes.length);
|
||||
|
||||
// Most common path first
|
||||
String value = header.value();
|
||||
byte[] valueBytes = value.getBytes(iso1);
|
||||
if (header.hasMultipleValues())
|
||||
{
|
||||
String[] values = header.values();
|
||||
for (int i = 1; i < values.length; ++i)
|
||||
{
|
||||
byte[] moreValueBytes = values[i].getBytes(iso1);
|
||||
byte[] newValueBytes = new byte[valueBytes.length + 1 + moreValueBytes.length];
|
||||
System.arraycopy(valueBytes, 0, newValueBytes, 0, valueBytes.length);
|
||||
newValueBytes[valueBytes.length] = 0;
|
||||
System.arraycopy(moreValueBytes, 0, newValueBytes, valueBytes.length + 1, moreValueBytes.length);
|
||||
valueBytes = newValueBytes;
|
||||
}
|
||||
}
|
||||
|
||||
writeValueLength(version, buffer, valueBytes.length);
|
||||
buffer.write(valueBytes, 0, valueBytes.length);
|
||||
}
|
||||
|
||||
return compress(version, buffer.toByteArray());
|
||||
}
|
||||
|
||||
private ByteBuffer compress(short version, byte[] bytes)
|
||||
{
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(bytes.length);
|
||||
|
||||
// The headers compression context is per-session, so we need to synchronize
|
||||
synchronized (compressor)
|
||||
{
|
||||
if (needsDictionary)
|
||||
{
|
||||
compressor.setDictionary(CompressionDictionary.get(version));
|
||||
needsDictionary = false;
|
||||
}
|
||||
|
||||
compressor.setInput(bytes);
|
||||
|
||||
// Compressed bytes may be bigger than input bytes, so we need to loop and accumulate them
|
||||
// Beware that the minimum amount of bytes generated by the compressor is few bytes, so we
|
||||
// need to use an output buffer that is big enough to exit the compress loop
|
||||
buffer.reset();
|
||||
int compressed;
|
||||
byte[] output = new byte[Math.max(256, bytes.length)];
|
||||
while (true)
|
||||
{
|
||||
// SPDY uses the SYNC_FLUSH mode
|
||||
compressed = compressor.compress(output);
|
||||
buffer.write(output, 0, compressed);
|
||||
if (compressed < output.length)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ByteBuffer.wrap(buffer.toByteArray());
|
||||
}
|
||||
|
||||
private void writeCount(short version, ByteArrayOutputStream buffer, int value)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
{
|
||||
buffer.write((value & 0xFF_00) >>> 8);
|
||||
buffer.write(value & 0x00_FF);
|
||||
break;
|
||||
}
|
||||
case SPDY.V3:
|
||||
{
|
||||
buffer.write((value & 0xFF_00_00_00) >>> 24);
|
||||
buffer.write((value & 0x00_FF_00_00) >>> 16);
|
||||
buffer.write((value & 0x00_00_FF_00) >>> 8);
|
||||
buffer.write(value & 0x00_00_00_FF);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// Here the version is trusted to be correct; if it's not
|
||||
// then it's a bug rather than an application error
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeNameLength(short version, ByteArrayOutputStream buffer, int length)
|
||||
{
|
||||
writeCount(version, buffer, length);
|
||||
}
|
||||
|
||||
private void writeValueLength(short version, ByteArrayOutputStream buffer, int length)
|
||||
{
|
||||
writeCount(version, buffer, length);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.SessionException;
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.HeadersFrame;
|
||||
|
||||
public class HeadersGenerator extends ControlFrameGenerator
|
||||
{
|
||||
private final HeadersBlockGenerator headersBlockGenerator;
|
||||
|
||||
public HeadersGenerator(ByteBufferPool bufferPool, HeadersBlockGenerator headersBlockGenerator)
|
||||
{
|
||||
super(bufferPool);
|
||||
this.headersBlockGenerator = headersBlockGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
HeadersFrame headers = (HeadersFrame)frame;
|
||||
short version = headers.getVersion();
|
||||
|
||||
ByteBuffer headersBuffer = headersBlockGenerator.generate(version, headers.getHeaders());
|
||||
|
||||
int frameBodyLength = 4;
|
||||
|
||||
int frameLength = frameBodyLength + headersBuffer.remaining();
|
||||
if (frameLength > 0xFF_FF_FF)
|
||||
{
|
||||
// Too many headers, but unfortunately we have already modified the compression
|
||||
// context, so we have no other choice than tear down the connection.
|
||||
throw new SessionException(SessionStatus.PROTOCOL_ERROR, "Too many headers");
|
||||
}
|
||||
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameLength;
|
||||
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(headers, frameLength, buffer);
|
||||
|
||||
buffer.putInt(headers.getStreamId() & 0x7F_FF_FF_FF);
|
||||
|
||||
buffer.put(headersBuffer);
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.NoOpFrame;
|
||||
|
||||
public class NoOpGenerator extends ControlFrameGenerator
|
||||
{
|
||||
public NoOpGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
super(bufferPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
NoOpFrame noOp = (NoOpFrame)frame;
|
||||
|
||||
int frameBodyLength = 0;
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(noOp, frameBodyLength, buffer);
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.PingFrame;
|
||||
|
||||
public class PingGenerator extends ControlFrameGenerator
|
||||
{
|
||||
public PingGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
super(bufferPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
PingFrame ping = (PingFrame)frame;
|
||||
|
||||
int frameBodyLength = 4;
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(ping, frameBodyLength, buffer);
|
||||
|
||||
buffer.putInt(ping.getPingId());
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.RstStreamFrame;
|
||||
|
||||
public class RstStreamGenerator extends ControlFrameGenerator
|
||||
{
|
||||
public RstStreamGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
super(bufferPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
RstStreamFrame rstStream = (RstStreamFrame)frame;
|
||||
|
||||
int frameBodyLength = 8;
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(rstStream, frameBodyLength, buffer);
|
||||
|
||||
buffer.putInt(rstStream.getStreamId() & 0x7F_FF_FF_FF);
|
||||
buffer.putInt(rstStream.getStatusCode());
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.Settings;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SettingsFrame;
|
||||
|
||||
public class SettingsGenerator extends ControlFrameGenerator
|
||||
{
|
||||
public SettingsGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
super(bufferPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
SettingsFrame settingsFrame = (SettingsFrame)frame;
|
||||
|
||||
Settings settings = settingsFrame.getSettings();
|
||||
int size = settings.size();
|
||||
int frameBodyLength = 4 + 8 * size;
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(settingsFrame, frameBodyLength, buffer);
|
||||
|
||||
buffer.putInt(size);
|
||||
|
||||
for (Settings.Setting setting : settings)
|
||||
{
|
||||
int id = setting.id().code();
|
||||
byte flags = setting.flag().code();
|
||||
int idAndFlags = convertIdAndFlags(frame.getVersion(), id, flags);
|
||||
buffer.putInt(idAndFlags);
|
||||
buffer.putInt(setting.value());
|
||||
}
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private int convertIdAndFlags(short version, int id, byte flags)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
{
|
||||
// In v2 the format is 24 bits of ID + 8 bits of flag
|
||||
int idAndFlags = (id << 8) + flags;
|
||||
// A bug in the Chromium implementation forces v2 to have
|
||||
// the 3 ID bytes little endian, so we swap first and third
|
||||
int result = idAndFlags & 0x00_FF_00_FF;
|
||||
result += (idAndFlags & 0xFF_00_00_00) >>> 16;
|
||||
result += (idAndFlags & 0x00_00_FF_00) << 16;
|
||||
return result;
|
||||
}
|
||||
case SPDY.V3:
|
||||
{
|
||||
// In v3 the format is 8 bits of flags + 24 bits of ID
|
||||
return (flags << 24) + (id & 0xFF_FF_FF);
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.SessionException;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SynReplyFrame;
|
||||
|
||||
public class SynReplyGenerator extends ControlFrameGenerator
|
||||
{
|
||||
private final HeadersBlockGenerator headersBlockGenerator;
|
||||
|
||||
public SynReplyGenerator(ByteBufferPool bufferPool, HeadersBlockGenerator headersBlockGenerator)
|
||||
{
|
||||
super(bufferPool);
|
||||
this.headersBlockGenerator = headersBlockGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
SynReplyFrame synReply = (SynReplyFrame)frame;
|
||||
short version = synReply.getVersion();
|
||||
|
||||
ByteBuffer headersBuffer = headersBlockGenerator.generate(version, synReply.getHeaders());
|
||||
|
||||
int frameBodyLength = getFrameDataLength(version);
|
||||
|
||||
int frameLength = frameBodyLength + headersBuffer.remaining();
|
||||
if (frameLength > 0xFF_FF_FF)
|
||||
{
|
||||
// Too many headers, but unfortunately we have already modified the compression
|
||||
// context, so we have no other choice than tear down the connection.
|
||||
throw new SessionException(SessionStatus.PROTOCOL_ERROR, "Too many headers");
|
||||
}
|
||||
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameLength;
|
||||
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(synReply, frameLength, buffer);
|
||||
|
||||
buffer.putInt(synReply.getStreamId() & 0x7F_FF_FF_FF);
|
||||
writeAdditional(version, buffer);
|
||||
|
||||
buffer.put(headersBuffer);
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private int getFrameDataLength(short version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
return 6;
|
||||
case SPDY.V3:
|
||||
return 4;
|
||||
default:
|
||||
// Here the version is trusted to be correct; if it's not
|
||||
// then it's a bug rather than an application error
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAdditional(short version, ByteBuffer buffer)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
buffer.putShort((short)0);
|
||||
break;
|
||||
case SPDY.V3:
|
||||
break;
|
||||
default:
|
||||
// Here the version is trusted to be correct; if it's not
|
||||
// then it's a bug rather than an application error
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.SessionException;
|
||||
import org.eclipse.jetty.spdy.StreamException;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
import org.eclipse.jetty.spdy.api.StreamStatus;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SynStreamFrame;
|
||||
|
||||
public class SynStreamGenerator extends ControlFrameGenerator
|
||||
{
|
||||
private final HeadersBlockGenerator headersBlockGenerator;
|
||||
|
||||
public SynStreamGenerator(ByteBufferPool bufferPool, HeadersBlockGenerator headersBlockGenerator)
|
||||
{
|
||||
super(bufferPool);
|
||||
this.headersBlockGenerator = headersBlockGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
SynStreamFrame synStream = (SynStreamFrame)frame;
|
||||
short version = synStream.getVersion();
|
||||
|
||||
ByteBuffer headersBuffer = headersBlockGenerator.generate(version, synStream.getHeaders());
|
||||
|
||||
int frameBodyLength = 10;
|
||||
|
||||
int frameLength = frameBodyLength + headersBuffer.remaining();
|
||||
if (frameLength > 0xFF_FF_FF)
|
||||
{
|
||||
// Too many headers, but unfortunately we have already modified the compression
|
||||
// context, so we have no other choice than tear down the connection.
|
||||
throw new SessionException(SessionStatus.PROTOCOL_ERROR, "Too many headers");
|
||||
}
|
||||
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameLength;
|
||||
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(synStream, frameLength, buffer);
|
||||
|
||||
int streamId = synStream.getStreamId();
|
||||
buffer.putInt(streamId & 0x7F_FF_FF_FF);
|
||||
buffer.putInt(synStream.getAssociatedStreamId() & 0x7F_FF_FF_FF);
|
||||
writePriority(streamId, version, synStream.getPriority(), buffer);
|
||||
|
||||
buffer.put(headersBuffer);
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private void writePriority(int streamId, short version, byte priority, ByteBuffer buffer)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
priority <<= 6;
|
||||
break;
|
||||
case SPDY.V3:
|
||||
priority <<= 5;
|
||||
break;
|
||||
default:
|
||||
throw new StreamException(streamId, StreamStatus.UNSUPPORTED_VERSION);
|
||||
}
|
||||
buffer.put(priority);
|
||||
buffer.put((byte)0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.WindowUpdateFrame;
|
||||
|
||||
public class WindowUpdateGenerator extends ControlFrameGenerator
|
||||
{
|
||||
public WindowUpdateGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
super(bufferPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
WindowUpdateFrame windowUpdate = (WindowUpdateFrame)frame;
|
||||
|
||||
int frameBodyLength = 8;
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(windowUpdate, frameBodyLength, buffer);
|
||||
|
||||
buffer.putInt(windowUpdate.getStreamId() & 0x7F_FF_FF_FF);
|
||||
buffer.putInt(windowUpdate.getWindowDelta() & 0x7F_FF_FF_FF);
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public abstract class ControlFrameBodyParser
|
||||
{
|
||||
public abstract boolean parse(ByteBuffer buffer);
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumMap;
|
||||
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.SessionException;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrameType;
|
||||
|
||||
public abstract class ControlFrameParser
|
||||
{
|
||||
private final EnumMap<ControlFrameType, ControlFrameBodyParser> parsers = new EnumMap<>(ControlFrameType.class);
|
||||
private final ControlFrameBodyParser unknownParser = new UnknownControlFrameBodyParser(this);
|
||||
private State state = State.VERSION;
|
||||
private int cursor;
|
||||
private short version;
|
||||
private short type;
|
||||
private byte flags;
|
||||
private int length;
|
||||
private ControlFrameBodyParser parser;
|
||||
|
||||
public ControlFrameParser(CompressionFactory.Decompressor decompressor)
|
||||
{
|
||||
parsers.put(ControlFrameType.SYN_STREAM, new SynStreamBodyParser(decompressor, this));
|
||||
parsers.put(ControlFrameType.SYN_REPLY, new SynReplyBodyParser(decompressor, this));
|
||||
parsers.put(ControlFrameType.RST_STREAM, new RstStreamBodyParser(this));
|
||||
parsers.put(ControlFrameType.SETTINGS, new SettingsBodyParser(this));
|
||||
parsers.put(ControlFrameType.NOOP, new NoOpBodyParser(this));
|
||||
parsers.put(ControlFrameType.PING, new PingBodyParser(this));
|
||||
parsers.put(ControlFrameType.GO_AWAY, new GoAwayBodyParser(this));
|
||||
parsers.put(ControlFrameType.HEADERS, new HeadersBodyParser(decompressor, this));
|
||||
parsers.put(ControlFrameType.WINDOW_UPDATE, new WindowUpdateBodyParser(this));
|
||||
}
|
||||
|
||||
public short getVersion()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
public byte getFlags()
|
||||
{
|
||||
return flags;
|
||||
}
|
||||
|
||||
public int getLength()
|
||||
{
|
||||
return length;
|
||||
}
|
||||
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case VERSION:
|
||||
{
|
||||
if (buffer.remaining() >= 2)
|
||||
{
|
||||
version = (short)(buffer.getShort() & 0x7F_FF);
|
||||
checkVersion(version);
|
||||
state = State.TYPE;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.VERSION_BYTES;
|
||||
cursor = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VERSION_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
version += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
version &= 0x7F_FF;
|
||||
checkVersion(version);
|
||||
state = State.TYPE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TYPE:
|
||||
{
|
||||
if (buffer.remaining() >= 2)
|
||||
{
|
||||
type = buffer.getShort();
|
||||
state = State.FLAGS;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.TYPE_BYTES;
|
||||
cursor = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TYPE_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
type += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
state = State.FLAGS;
|
||||
break;
|
||||
}
|
||||
case FLAGS:
|
||||
{
|
||||
flags = buffer.get();
|
||||
cursor = 3;
|
||||
state = State.LENGTH;
|
||||
break;
|
||||
}
|
||||
case LENGTH:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
length += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor > 0)
|
||||
break;
|
||||
|
||||
ControlFrameType controlFrameType = ControlFrameType.from(type);
|
||||
|
||||
// SPEC v3, 2.2.1: unrecognized control frames must be ignored
|
||||
if (controlFrameType == null)
|
||||
parser = unknownParser;
|
||||
else
|
||||
parser = parsers.get(controlFrameType);
|
||||
|
||||
state = State.BODY;
|
||||
|
||||
// We have to let it fall through the next switch:
|
||||
// the NOOP frame has no body and we cannot break
|
||||
// because the buffer may be consumed and we will
|
||||
// never enter the BODY case.
|
||||
}
|
||||
case BODY:
|
||||
{
|
||||
if (parser.parse(buffer))
|
||||
{
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void checkVersion(short version)
|
||||
{
|
||||
if (version != SPDY.V2 && version != SPDY.V3)
|
||||
throw new SessionException(SessionStatus.PROTOCOL_ERROR, "Unrecognized version " + version);
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
state = State.VERSION;
|
||||
cursor = 0;
|
||||
version = 0;
|
||||
type = 0;
|
||||
flags = 0;
|
||||
length = 0;
|
||||
parser = null;
|
||||
}
|
||||
|
||||
protected abstract void onControlFrame(ControlFrame frame);
|
||||
|
||||
private enum State
|
||||
{
|
||||
VERSION, VERSION_BYTES, TYPE, TYPE_BYTES, FLAGS, LENGTH, BODY
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.frames.DataFrame;
|
||||
|
||||
public abstract class DataFrameParser
|
||||
{
|
||||
private State state = State.STREAM_ID;
|
||||
private int cursor;
|
||||
private int streamId;
|
||||
private byte flags;
|
||||
private int length;
|
||||
|
||||
/**
|
||||
* <p>Parses the given {@link ByteBuffer} for a data frame.</p>
|
||||
*
|
||||
* @param buffer the {@link ByteBuffer} to parse
|
||||
* @return true if the data frame has been fully parsed, false otherwise
|
||||
*/
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case STREAM_ID:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
streamId = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
state = State.FLAGS;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.STREAM_ID_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STREAM_ID_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
streamId += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
state = State.FLAGS;
|
||||
break;
|
||||
}
|
||||
case FLAGS:
|
||||
{
|
||||
flags = buffer.get();
|
||||
cursor = 3;
|
||||
state = State.LENGTH;
|
||||
break;
|
||||
}
|
||||
case LENGTH:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
length += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor > 0)
|
||||
break;
|
||||
state = State.DATA;
|
||||
// Fall down if length == 0: we can't loop because the buffer
|
||||
// may be empty but we need to invoke the application anyway
|
||||
if (length > 0)
|
||||
break;
|
||||
}
|
||||
case DATA:
|
||||
{
|
||||
// Length can only be at most 3 bytes, which is 16_777_215 i.e. 16 MiB.
|
||||
// However, compliant clients should implement flow control, so it's
|
||||
// unlikely that we will get that 16 MiB chunk.
|
||||
// However, TCP may further split the flow control window, so we may
|
||||
// only have part of the data at this point.
|
||||
|
||||
int size = Math.min(length, buffer.remaining());
|
||||
int limit = buffer.limit();
|
||||
buffer.limit(buffer.position() + size);
|
||||
ByteBuffer bytes = buffer.slice();
|
||||
buffer.limit(limit);
|
||||
buffer.position(buffer.position() + size);
|
||||
length -= size;
|
||||
if (length == 0)
|
||||
{
|
||||
onDataFrame(bytes);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We got only part of the frame data bytes,
|
||||
// so we generate a synthetic data frame
|
||||
onDataFragment(bytes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onDataFrame(ByteBuffer bytes)
|
||||
{
|
||||
DataFrame frame = new DataFrame(streamId, flags, bytes.remaining());
|
||||
onDataFrame(frame, bytes);
|
||||
reset();
|
||||
}
|
||||
|
||||
private void onDataFragment(ByteBuffer bytes)
|
||||
{
|
||||
DataFrame frame = new DataFrame(streamId, (byte)(flags & ~DataInfo.FLAG_CLOSE), bytes.remaining());
|
||||
onDataFrame(frame, bytes);
|
||||
// Do not reset, we're expecting more data
|
||||
}
|
||||
|
||||
protected abstract void onDataFrame(DataFrame frame, ByteBuffer data);
|
||||
|
||||
private void reset()
|
||||
{
|
||||
state = State.STREAM_ID;
|
||||
cursor = 0;
|
||||
streamId = 0;
|
||||
flags = 0;
|
||||
length = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
STREAM_ID, STREAM_ID_BYTES, FLAGS, LENGTH, DATA
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.frames.GoAwayFrame;
|
||||
|
||||
public class GoAwayBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private State state = State.LAST_STREAM_ID;
|
||||
private int cursor;
|
||||
private int lastStreamId;
|
||||
private int statusCode;
|
||||
|
||||
public GoAwayBodyParser(ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case LAST_STREAM_ID:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
lastStreamId = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
switch (controlFrameParser.getVersion())
|
||||
{
|
||||
case SPDY.V2:
|
||||
{
|
||||
onGoAway();
|
||||
return true;
|
||||
}
|
||||
case SPDY.V3:
|
||||
{
|
||||
state = State.STATUS_CODE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.LAST_STREAM_ID_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LAST_STREAM_ID_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
lastStreamId += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
lastStreamId &= 0x7F_FF_FF_FF;
|
||||
switch (controlFrameParser.getVersion())
|
||||
{
|
||||
case SPDY.V2:
|
||||
{
|
||||
onGoAway();
|
||||
return true;
|
||||
}
|
||||
case SPDY.V3:
|
||||
{
|
||||
state = State.STATUS_CODE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STATUS_CODE:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
statusCode = buffer.getInt();
|
||||
onGoAway();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.STATUS_CODE_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STATUS_CODE_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
statusCode += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
onGoAway();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onGoAway()
|
||||
{
|
||||
GoAwayFrame frame = new GoAwayFrame(controlFrameParser.getVersion(), lastStreamId, statusCode);
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
state = State.LAST_STREAM_ID;
|
||||
cursor = 0;
|
||||
lastStreamId = 0;
|
||||
statusCode = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
LAST_STREAM_ID, LAST_STREAM_ID_BYTES, STATUS_CODE, STATUS_CODE_BYTES
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.zip.ZipException;
|
||||
|
||||
import org.eclipse.jetty.spdy.CompressionDictionary;
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.SessionException;
|
||||
import org.eclipse.jetty.spdy.StreamException;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
import org.eclipse.jetty.spdy.api.StreamStatus;
|
||||
|
||||
public abstract class HeadersBlockParser
|
||||
{
|
||||
private final CompressionFactory.Decompressor decompressor;
|
||||
private byte[] data;
|
||||
private boolean needsDictionary = true;
|
||||
|
||||
protected HeadersBlockParser(CompressionFactory.Decompressor decompressor)
|
||||
{
|
||||
this.decompressor = decompressor;
|
||||
}
|
||||
|
||||
public boolean parse(int streamId, short version, int length, ByteBuffer buffer)
|
||||
{
|
||||
// Need to be sure that all the compressed data has arrived
|
||||
// Because SPDY uses SYNC_FLUSH mode, and the Java API
|
||||
// does not expose when decompression is finished with this mode
|
||||
// (but only when using NO_FLUSH), then we need to
|
||||
// accumulate the compressed bytes until we have all of them
|
||||
|
||||
boolean accumulated = accumulate(length, buffer);
|
||||
if (!accumulated)
|
||||
return false;
|
||||
|
||||
byte[] compressedHeaders = data;
|
||||
data = null;
|
||||
ByteBuffer decompressedHeaders = decompress(version, compressedHeaders);
|
||||
|
||||
Charset iso1 = Charset.forName("ISO-8859-1");
|
||||
|
||||
// We know the decoded bytes contain the full headers,
|
||||
// so optimize instead of looping byte by byte
|
||||
int count = readCount(version, decompressedHeaders);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
int nameLength = readNameLength(version, decompressedHeaders);
|
||||
if (nameLength == 0)
|
||||
throw new StreamException(streamId, StreamStatus.PROTOCOL_ERROR, "Invalid header name length");
|
||||
byte[] nameBytes = new byte[nameLength];
|
||||
decompressedHeaders.get(nameBytes);
|
||||
String name = new String(nameBytes, iso1);
|
||||
|
||||
int valueLength = readValueLength(version, decompressedHeaders);
|
||||
if (valueLength == 0)
|
||||
throw new StreamException(streamId, StreamStatus.PROTOCOL_ERROR, "Invalid header value length");
|
||||
byte[] valueBytes = new byte[valueLength];
|
||||
decompressedHeaders.get(valueBytes);
|
||||
String value = new String(valueBytes, iso1);
|
||||
// Multi valued headers are separate by NUL
|
||||
String[] values = value.split("\u0000");
|
||||
// Check if there are multiple NULs (section 2.6.9)
|
||||
for (String v : values)
|
||||
if (v.length() == 0)
|
||||
throw new StreamException(streamId, StreamStatus.PROTOCOL_ERROR, "Invalid multi valued header");
|
||||
|
||||
onHeader(name, values);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean accumulate(int length, ByteBuffer buffer)
|
||||
{
|
||||
int remaining = buffer.remaining();
|
||||
if (data == null)
|
||||
{
|
||||
if (remaining < length)
|
||||
{
|
||||
data = new byte[remaining];
|
||||
buffer.get(data);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
data = new byte[length];
|
||||
buffer.get(data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int accumulated = data.length;
|
||||
int needed = length - accumulated;
|
||||
if (remaining < needed)
|
||||
{
|
||||
byte[] local = new byte[accumulated + remaining];
|
||||
System.arraycopy(data, 0, local, 0, accumulated);
|
||||
buffer.get(local, accumulated, remaining);
|
||||
data = local;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] local = new byte[length];
|
||||
System.arraycopy(data, 0, local, 0, accumulated);
|
||||
buffer.get(local, accumulated, needed);
|
||||
data = local;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int readCount(int version, ByteBuffer buffer)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
return buffer.getShort();
|
||||
case SPDY.V3:
|
||||
return buffer.getInt();
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private int readNameLength(int version, ByteBuffer buffer)
|
||||
{
|
||||
return readCount(version, buffer);
|
||||
}
|
||||
|
||||
private int readValueLength(int version, ByteBuffer buffer)
|
||||
{
|
||||
return readCount(version, buffer);
|
||||
}
|
||||
|
||||
protected abstract void onHeader(String name, String[] values);
|
||||
|
||||
private ByteBuffer decompress(short version, byte[] compressed)
|
||||
{
|
||||
// Differently from compression, decompression always happens
|
||||
// non-concurrently because we read and parse with a single
|
||||
// thread, and therefore there is no need for synchronization.
|
||||
|
||||
try
|
||||
{
|
||||
byte[] decompressed = null;
|
||||
byte[] buffer = new byte[compressed.length * 2];
|
||||
decompressor.setInput(compressed);
|
||||
|
||||
while (true)
|
||||
{
|
||||
int count = decompressor.decompress(buffer);
|
||||
if (count == 0)
|
||||
{
|
||||
if (decompressed != null)
|
||||
{
|
||||
return ByteBuffer.wrap(decompressed);
|
||||
}
|
||||
else if (needsDictionary)
|
||||
{
|
||||
decompressor.setDictionary(CompressionDictionary.get(version));
|
||||
needsDictionary = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (count < buffer.length)
|
||||
{
|
||||
if (decompressed == null)
|
||||
{
|
||||
// Only one pass was needed to decompress
|
||||
return ByteBuffer.wrap(buffer, 0, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Last pass needed to decompress, merge decompressed bytes
|
||||
byte[] result = new byte[decompressed.length + count];
|
||||
System.arraycopy(decompressed, 0, result, 0, decompressed.length);
|
||||
System.arraycopy(buffer, 0, result, decompressed.length, count);
|
||||
return ByteBuffer.wrap(result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (decompressed == null)
|
||||
{
|
||||
decompressed = buffer;
|
||||
buffer = new byte[buffer.length];
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] result = new byte[decompressed.length + buffer.length];
|
||||
System.arraycopy(decompressed, 0, result, 0, decompressed.length);
|
||||
System.arraycopy(buffer, 0, result, decompressed.length, buffer.length);
|
||||
decompressed = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ZipException x)
|
||||
{
|
||||
// We had a compression problem, and since the compression context
|
||||
// is per-connection, we need to tear down the connection
|
||||
throw new SessionException(SessionStatus.PROTOCOL_ERROR, x);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.HeadersInfo;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrameType;
|
||||
import org.eclipse.jetty.spdy.frames.HeadersFrame;
|
||||
|
||||
public class HeadersBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final Headers headers = new Headers();
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private final HeadersBlockParser headersBlockParser;
|
||||
private State state = State.STREAM_ID;
|
||||
private int cursor;
|
||||
private int streamId;
|
||||
|
||||
public HeadersBodyParser(CompressionFactory.Decompressor decompressor, ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
this.headersBlockParser = new HeadersHeadersBlockParser(decompressor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case STREAM_ID:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
streamId = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
state = State.HEADERS;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.STREAM_ID_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STREAM_ID_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
streamId += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
streamId &= 0x7F_FF_FF_FF;
|
||||
state = State.HEADERS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HEADERS:
|
||||
{
|
||||
short version = controlFrameParser.getVersion();
|
||||
int length = controlFrameParser.getLength() - 4;
|
||||
if (headersBlockParser.parse(streamId, version, length, buffer))
|
||||
{
|
||||
byte flags = controlFrameParser.getFlags();
|
||||
if (flags != 0 && flags != HeadersInfo.FLAG_CLOSE && flags != HeadersInfo.FLAG_RESET_COMPRESSION)
|
||||
throw new IllegalArgumentException("Invalid flag " + flags + " for frame " + ControlFrameType.HEADERS);
|
||||
|
||||
HeadersFrame frame = new HeadersFrame(version, flags, streamId, new Headers(headers, true));
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
headers.clear();
|
||||
state = State.STREAM_ID;
|
||||
cursor = 0;
|
||||
streamId = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
STREAM_ID, STREAM_ID_BYTES, HEADERS
|
||||
}
|
||||
|
||||
private class HeadersHeadersBlockParser extends HeadersBlockParser
|
||||
{
|
||||
public HeadersHeadersBlockParser(CompressionFactory.Decompressor decompressor)
|
||||
{
|
||||
super(decompressor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHeader(String name, String[] values)
|
||||
{
|
||||
for (String value : values)
|
||||
headers.add(name, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.frames.NoOpFrame;
|
||||
|
||||
public class NoOpBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
|
||||
public NoOpBodyParser(ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
NoOpFrame frame = new NoOpFrame();
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EventListener;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.SessionException;
|
||||
import org.eclipse.jetty.spdy.StreamException;
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.DataFrame;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Parser
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Parser.class);
|
||||
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private final DataFrameParser dataFrameParser;
|
||||
private State state = State.CONTROL_BIT;
|
||||
|
||||
public Parser(CompressionFactory.Decompressor decompressor)
|
||||
{
|
||||
// It is important to allocate one decompression context per
|
||||
// SPDY session for the control frames (to decompress the headers)
|
||||
controlFrameParser = new ControlFrameParser(decompressor)
|
||||
{
|
||||
@Override
|
||||
protected void onControlFrame(ControlFrame frame)
|
||||
{
|
||||
logger.debug("Parsed {}", frame);
|
||||
notifyControlFrame(frame);
|
||||
}
|
||||
};
|
||||
dataFrameParser = new DataFrameParser()
|
||||
{
|
||||
@Override
|
||||
protected void onDataFrame(DataFrame frame, ByteBuffer data)
|
||||
{
|
||||
logger.debug("Parsed {}, {} data bytes", frame, data.remaining());
|
||||
notifyDataFrame(frame, data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void addListener(Listener listener)
|
||||
{
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener)
|
||||
{
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
protected void notifyControlFrame(ControlFrame frame)
|
||||
{
|
||||
for (Listener listener : listeners)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onControlFrame(frame);
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifyDataFrame(DataFrame frame, ByteBuffer data)
|
||||
{
|
||||
for (Listener listener : listeners)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onDataFrame(frame, data);
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifyStreamException(StreamException x)
|
||||
{
|
||||
for (Listener listener : listeners)
|
||||
{
|
||||
listener.onStreamException(x);
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifySessionException(SessionException x)
|
||||
{
|
||||
logger.debug("SPDY session exception", x);
|
||||
for (Listener listener : listeners)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onSessionException(x);
|
||||
}
|
||||
catch (Exception xx)
|
||||
{
|
||||
logger.debug("Could not notify listener " + listener, xx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.debug("Parsing {} bytes", buffer.remaining());
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case CONTROL_BIT:
|
||||
{
|
||||
// We must only peek the first byte and not advance the buffer
|
||||
// because the 7 least significant bits may be relevant in data frames
|
||||
int currByte = buffer.get(buffer.position());
|
||||
boolean isControlFrame = (currByte & 0x80) == 0x80;
|
||||
state = isControlFrame ? State.CONTROL_FRAME : State.DATA_FRAME;
|
||||
break;
|
||||
}
|
||||
case CONTROL_FRAME:
|
||||
{
|
||||
if (controlFrameParser.parse(buffer))
|
||||
reset();
|
||||
break;
|
||||
}
|
||||
case DATA_FRAME:
|
||||
{
|
||||
if (dataFrameParser.parse(buffer))
|
||||
reset();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SessionException x)
|
||||
{
|
||||
notifySessionException(x);
|
||||
}
|
||||
catch (StreamException x)
|
||||
{
|
||||
notifyStreamException(x);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
notifySessionException(new SessionException(SessionStatus.PROTOCOL_ERROR, x));
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Be sure to consume after exceptions
|
||||
buffer.position(buffer.limit());
|
||||
}
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
state = State.CONTROL_BIT;
|
||||
}
|
||||
|
||||
public interface Listener extends EventListener
|
||||
{
|
||||
public void onControlFrame(ControlFrame frame);
|
||||
|
||||
public void onDataFrame(DataFrame frame, ByteBuffer data);
|
||||
|
||||
public void onStreamException(StreamException x);
|
||||
|
||||
public void onSessionException(SessionException x);
|
||||
|
||||
public static class Adapter implements Listener
|
||||
{
|
||||
@Override
|
||||
public void onControlFrame(ControlFrame frame)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataFrame(DataFrame frame, ByteBuffer data)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamException(StreamException x)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionException(SessionException x)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
CONTROL_BIT, CONTROL_FRAME, DATA_FRAME
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.frames.PingFrame;
|
||||
|
||||
public class PingBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private State state = State.PING_ID;
|
||||
private int cursor;
|
||||
private int pingId;
|
||||
|
||||
public PingBodyParser(ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case PING_ID:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
pingId = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
onPing();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.PING_ID_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PING_ID_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
pingId += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
onPing();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onPing()
|
||||
{
|
||||
PingFrame frame = new PingFrame(controlFrameParser.getVersion(), pingId);
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
state = State.PING_ID;
|
||||
cursor = 0;
|
||||
pingId = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
PING_ID, PING_ID_BYTES
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.frames.RstStreamFrame;
|
||||
|
||||
public class RstStreamBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private State state = State.STREAM_ID;
|
||||
private int cursor;
|
||||
private int streamId;
|
||||
private int statusCode;
|
||||
|
||||
public RstStreamBodyParser(ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case STREAM_ID:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
streamId = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
state = State.STATUS_CODE;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.STREAM_ID_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STREAM_ID_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
streamId += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
streamId &= 0x7F_FF_FF_FF;
|
||||
state = State.STATUS_CODE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STATUS_CODE:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
statusCode = buffer.getInt();
|
||||
onRstStream();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.STATUS_CODE_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STATUS_CODE_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
statusCode += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
onRstStream();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onRstStream()
|
||||
{
|
||||
// TODO: check that statusCode is not 0
|
||||
RstStreamFrame frame = new RstStreamFrame(controlFrameParser.getVersion(), streamId, statusCode);
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
state = State.STREAM_ID;
|
||||
cursor = 0;
|
||||
streamId = 0;
|
||||
statusCode = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
STREAM_ID, STREAM_ID_BYTES, STATUS_CODE, STATUS_CODE_BYTES
|
||||
}
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.Settings;
|
||||
import org.eclipse.jetty.spdy.frames.SettingsFrame;
|
||||
|
||||
public class SettingsBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final Settings settings = new Settings();
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private State state = State.COUNT;
|
||||
private int cursor;
|
||||
private int count;
|
||||
private int idAndFlags;
|
||||
private int value;
|
||||
|
||||
public SettingsBodyParser(ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case COUNT:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
count = buffer.getInt();
|
||||
state = State.ID_FLAGS;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.COUNT_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case COUNT_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
count += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
state = State.ID_FLAGS;
|
||||
break;
|
||||
}
|
||||
case ID_FLAGS:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
idAndFlags = convertIdAndFlags(controlFrameParser.getVersion(), buffer.getInt());
|
||||
state = State.VALUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.ID_FLAGS_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ID_FLAGS_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
value += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
idAndFlags = convertIdAndFlags(controlFrameParser.getVersion(), value);
|
||||
state = State.VALUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VALUE:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
value = buffer.getInt();
|
||||
if (onPair())
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.VALUE_BYTES;
|
||||
cursor = 4;
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VALUE_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
value += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
if (onPair())
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private int convertIdAndFlags(short version, int idAndFlags)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
{
|
||||
// A bug in the Chromium implementation forces v2 to have
|
||||
// 3 ID bytes little endian + 1 byte of flags
|
||||
// Here we normalize this to conform with v3, which is
|
||||
// 1 bytes of flag + 3 ID bytes big endian
|
||||
int result = (idAndFlags & 0x00_00_00_FF) << 24;
|
||||
result += (idAndFlags & 0x00_00_FF_00) << 8;
|
||||
result += (idAndFlags & 0x00_FF_00_00) >>> 8;
|
||||
result += (idAndFlags & 0xFF_00_00_00) >>> 24;
|
||||
return result;
|
||||
}
|
||||
case SPDY.V3:
|
||||
{
|
||||
return idAndFlags;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean onPair()
|
||||
{
|
||||
int id = idAndFlags & 0x00_FF_FF_FF;
|
||||
byte flags = (byte)((idAndFlags & 0xFF_00_00_00) >>> 24);
|
||||
settings.put(new Settings.Setting(Settings.ID.from(id), Settings.Flag.from(flags), value));
|
||||
state = State.ID_FLAGS;
|
||||
idAndFlags = 0;
|
||||
value = 0;
|
||||
--count;
|
||||
if (count == 0)
|
||||
{
|
||||
onSettings();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onSettings()
|
||||
{
|
||||
SettingsFrame frame = new SettingsFrame(controlFrameParser.getVersion(), controlFrameParser.getFlags(), new Settings(settings, true));
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
settings.clear();
|
||||
state = State.COUNT;
|
||||
cursor = 0;
|
||||
count = 0;
|
||||
idAndFlags = 0;
|
||||
value = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
COUNT, COUNT_BYTES, ID_FLAGS, ID_FLAGS_BYTES, VALUE, VALUE_BYTES
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.ReplyInfo;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrameType;
|
||||
import org.eclipse.jetty.spdy.frames.SynReplyFrame;
|
||||
|
||||
public class SynReplyBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final Headers headers = new Headers();
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private final HeadersBlockParser headersBlockParser;
|
||||
private State state = State.STREAM_ID;
|
||||
private int cursor;
|
||||
private int streamId;
|
||||
|
||||
public SynReplyBodyParser(CompressionFactory.Decompressor decompressor, ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
this.headersBlockParser = new SynReplyHeadersBlockParser(decompressor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case STREAM_ID:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
streamId = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
state = State.ADDITIONAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.STREAM_ID_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STREAM_ID_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
streamId += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
streamId &= 0x7F_FF_FF_FF;
|
||||
state = State.ADDITIONAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ADDITIONAL:
|
||||
{
|
||||
switch (controlFrameParser.getVersion())
|
||||
{
|
||||
case SPDY.V2:
|
||||
{
|
||||
if (buffer.remaining() >= 2)
|
||||
{
|
||||
buffer.getShort();
|
||||
state = State.HEADERS;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.ADDITIONAL_BYTES;
|
||||
cursor = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SPDY.V3:
|
||||
{
|
||||
state = State.HEADERS;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ADDITIONAL_BYTES:
|
||||
{
|
||||
assert controlFrameParser.getVersion() == SPDY.V2;
|
||||
buffer.get();
|
||||
--cursor;
|
||||
if (cursor == 0)
|
||||
state = State.HEADERS;
|
||||
break;
|
||||
}
|
||||
case HEADERS:
|
||||
{
|
||||
short version = controlFrameParser.getVersion();
|
||||
int length = controlFrameParser.getLength() - getSynReplyDataLength(version);
|
||||
if (headersBlockParser.parse(streamId, version, length, buffer))
|
||||
{
|
||||
byte flags = controlFrameParser.getFlags();
|
||||
if (flags != 0 && flags != ReplyInfo.FLAG_CLOSE)
|
||||
throw new IllegalArgumentException("Invalid flag " + flags + " for frame " + ControlFrameType.SYN_REPLY);
|
||||
|
||||
SynReplyFrame frame = new SynReplyFrame(version, flags, streamId, new Headers(headers, true));
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private int getSynReplyDataLength(short version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case 2:
|
||||
return 6;
|
||||
case 3:
|
||||
return 4;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
headers.clear();
|
||||
state = State.STREAM_ID;
|
||||
cursor = 0;
|
||||
streamId = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
STREAM_ID, STREAM_ID_BYTES, ADDITIONAL, ADDITIONAL_BYTES, HEADERS
|
||||
}
|
||||
|
||||
private class SynReplyHeadersBlockParser extends HeadersBlockParser
|
||||
{
|
||||
public SynReplyHeadersBlockParser(CompressionFactory.Decompressor decompressor)
|
||||
{
|
||||
super(decompressor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHeader(String name, String[] values)
|
||||
{
|
||||
for (String value : values)
|
||||
headers.add(name, value);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue