Merge remote-tracking branch 'origin/master' into jetty-8

This commit is contained in:
Jan Bartel 2012-06-04 13:49:25 +02:00
commit 9149a69446
75 changed files with 2508 additions and 814 deletions

View File

@ -235,6 +235,7 @@ public abstract class AbstractCompressedStream extends ServletOutputStream
throw new IllegalStateException();
setHeader("Content-Encoding", _encoding);
setHeader("Vary","Accept-Encoding");
if (_response.containsHeader("Content-Encoding"))
{

View File

@ -9,7 +9,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.jetty.npn</groupId>
<artifactId>npn-api</artifactId>
<version>1.0.1-SNAPSHOT</version>
<version>1.1.1-SNAPSHOT</version>
<name>Jetty :: Next Protocol Negotiation :: API</name>
<scm>

View File

@ -87,7 +87,8 @@ import javax.net.ssl.SSLSocket;
* </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>
* hard reference them anymore. However, methods to explicitly unregister {@link SSLSocket} or
* {@link SSLEngine} instances are provided.</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>
*/
@ -109,6 +110,7 @@ public class NextProtoNego
*
* @param socket the socket to register with the provider
* @param provider the provider to register with the socket
* @see #remove(SSLSocket)
*/
public static void put(SSLSocket socket, Provider provider)
{
@ -124,11 +126,24 @@ public class NextProtoNego
return objects.get(socket);
}
/**
* <p>Unregisters the given SSLSocket.</p>
*
* @param socket the socket to unregister
* @return the provider registered with the socket
* @see #put(SSLSocket, Provider)
*/
public static Provider remove(SSLSocket socket)
{
return objects.remove(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
* @see #remove(SSLEngine)
*/
public static void put(SSLEngine engine, Provider provider)
{
@ -145,6 +160,18 @@ public class NextProtoNego
return objects.get(engine);
}
/**
* <p>Unregisters the given SSLEngine.</p>
*
* @param engine the engine to unregister
* @return the provider registered with the engine
* @see #put(SSLEngine, Provider)
*/
public static Provider remove(SSLEngine engine)
{
return objects.remove(engine);
}
/**
* <p>Base, empty, interface for providers.</p>
*/

View File

@ -312,7 +312,6 @@ public class CookieCutter
}
catch (Exception e)
{
LOG.warn(e.toString());
LOG.debug(e);
}

View File

@ -137,11 +137,21 @@ public class Server extends HandlerWrapper implements Attributes
/* ------------------------------------------------------------ */
public void setStopAtShutdown(boolean stop)
{
_stopAtShutdown=stop;
//if we now want to stop
if (stop)
ShutdownThread.register(this);
{
//and we weren't stopping before
if (!_stopAtShutdown)
{
//only register to stop if we're already started (otherwise we'll do it in doStart())
if (isStarted())
ShutdownThread.register(this);
}
}
else
ShutdownThread.deregister(this);
_stopAtShutdown=stop;
}
/* ------------------------------------------------------------ */

View File

@ -68,6 +68,7 @@ import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.component.AggregateLifeCycle;
@ -143,6 +144,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
private Object _requestListeners;
private Object _requestAttributeListeners;
private Map<String, Object> _managedAttributes;
private String[] _protectedTargets;
private boolean _shutdown = false;
private boolean _available = true;
@ -1131,13 +1133,46 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
/* ------------------------------------------------------------ */
/**
* Check the target. Called by {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} when a target within a context is determined. If
* the target is protected, 404 is returned. The default implementation always returns false.
* the target is protected, 404 is returned.
*/
/* ------------------------------------------------------------ */
protected boolean isProtectedTarget(String target)
public boolean isProtectedTarget(String target)
{
return false;
if (target == null || _protectedTargets == null)
return false;
while (target.startsWith("//"))
target=URIUtil.compactPath(target);
boolean isProtected = false;
int i=0;
while (!isProtected && i<_protectedTargets.length)
{
isProtected = StringUtil.startsWithIgnoreCase(target, _protectedTargets[i++]);
}
return isProtected;
}
public void setProtectedTargets (String[] targets)
{
if (targets == null)
{
_protectedTargets = null;
return;
}
_protectedTargets = Arrays.copyOf(targets, targets.length);
}
public String[] getProtectedTargets ()
{
if (_protectedTargets == null)
return null;
return Arrays.copyOf(_protectedTargets, _protectedTargets.length);
}
/* ------------------------------------------------------------ */
/*

View File

@ -77,6 +77,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
_accessed=accessed;
_lastAccessed=accessed;
_requests=1;
_maxIdleMs=_manager._dftMaxIdleSecs>0?_manager._dftMaxIdleSecs*1000L:-1;
if (LOG.isDebugEnabled())
LOG.debug("new session "+_nodeId+" "+_clusterId);
}

View File

@ -21,9 +21,11 @@ import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.net.URL;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.Exchanger;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
@ -131,6 +133,11 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
Assert.assertThat(response, Matchers.containsString("HTTP/1.1 413 "));
}
catch(SocketException e)
{
// TODO looks like a close is overtaking the 413 in SSL
System.err.println("Investigate this "+e);
}
finally
{
client.close();

View File

@ -292,6 +292,34 @@ public class ContextHandlerTest
assertEquals("333",handler.getServletContext().getAttribute("ccc"));
assertEquals(null,handler.getServletContext().getAttribute("ddd"));
}
@Test
public void testProtected() throws Exception
{
ContextHandler handler = new ContextHandler();
String[] protectedTargets = {"/foo-inf", "/bar-inf"};
handler.setProtectedTargets(protectedTargets);
assertTrue(handler.isProtectedTarget("/foo-inf/x/y/z"));
assertFalse(handler.isProtectedTarget("/foo/x/y/z"));
assertTrue(handler.isProtectedTarget("/foo-inf?x=y&z=1"));
protectedTargets = new String[4];
System.arraycopy(handler.getProtectedTargets(), 0, protectedTargets, 0, 2);
protectedTargets[2] = "/abc";
protectedTargets[3] = "/def";
handler.setProtectedTargets(protectedTargets);
assertTrue(handler.isProtectedTarget("/foo-inf/x/y/z"));
assertFalse(handler.isProtectedTarget("/foo/x/y/z"));
assertTrue(handler.isProtectedTarget("/foo-inf?x=y&z=1"));
assertTrue(handler.isProtectedTarget("/abc/124"));
assertTrue(handler.isProtectedTarget("//def"));
assertTrue(handler.isProtectedTarget("/ABC/7777"));
}
private void checkResourcePathsForExampleWebApp(String root) throws IOException
{

View File

@ -497,6 +497,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
if (gzip)
{
response.setHeader(HttpHeaders.CONTENT_ENCODING,"gzip");
response.setHeader(HttpHeaders.VARY,HttpHeaders.ACCEPT_ENCODING);
String mt=_servletContext.getMimeType(pathInContext);
if (mt!=null)
response.setContentType(mt);

View File

@ -13,7 +13,7 @@
<name>Jetty :: SPDY :: Parent</name>
<properties>
<npn.version>1.0.0.v20120402</npn.version>
<npn.version>1.1.0.v20120525</npn.version>
</properties>
<modules>

View File

@ -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 org.eclipse.jetty.spdy.api.DataInfo;
// TODO: add methods that tell how much written and whether we're TCP congested ?
public interface FlowControlStrategy
{
public int getWindowSize(ISession session);
public void setWindowSize(ISession session, int windowSize);
public void onNewStream(ISession session, IStream stream);
public void onWindowUpdate(ISession session, IStream stream, int delta);
public void updateWindow(ISession session, IStream stream, int delta);
public void onDataReceived(ISession session, IStream stream, DataInfo dataInfo);
public void onDataConsumed(ISession session, IStream stream, DataInfo dataInfo, int delta);
public static class None implements FlowControlStrategy
{
private volatile int windowSize;
public None()
{
this(65536);
}
public None(int windowSize)
{
this.windowSize = windowSize;
}
@Override
public int getWindowSize(ISession session)
{
return windowSize;
}
@Override
public void setWindowSize(ISession session, int windowSize)
{
this.windowSize = windowSize;
}
@Override
public void onNewStream(ISession session, IStream stream)
{
stream.updateWindowSize(windowSize);
}
@Override
public void onWindowUpdate(ISession session, IStream stream, int delta)
{
}
@Override
public void updateWindow(ISession session, IStream stream, int delta)
{
}
@Override
public void onDataReceived(ISession session, IStream stream, DataInfo dataInfo)
{
}
@Override
public void onDataConsumed(ISession session, IStream stream, DataInfo dataInfo, int delta)
{
}
}
}

View File

@ -16,14 +16,12 @@
package org.eclipse.jetty.spdy;
import java.nio.ByteBuffer;
import org.eclipse.jetty.spdy.api.DataInfo;
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>
@ -77,37 +75,36 @@ public interface IStream extends Stream
* for example by updating the stream's state or by calling listeners.</p>
*
* @param frame the control frame to process
* @see #process(DataFrame, ByteBuffer)
* @see #process(DataInfo)
*/
public void process(ControlFrame frame);
/**
* <p>Processes the given data frame along with the given byte buffer,
* <p>Processes the given {@code dataInfo},
* 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
* @param dataInfo the DataInfo to process
* @see #process(ControlFrame)
*/
public void process(DataFrame frame, ByteBuffer data);
public void process(DataInfo dataInfo);
/**
* <p>Associate the given {@link IStream} to this {@link IStream}.</p>
*
*
* @param stream the stream to associate with this stream
*/
public void associate(IStream stream);
/**
* <p>remove the given associated {@link IStream} from this stream</p>
*
*
* @param stream the stream to be removed
*/
public void disassociate(IStream stream);
/**
* <p>Overrides Stream.getAssociatedStream() to return an instance of IStream instead of Stream
*
*
* @see Stream#getAssociatedStream()
*/
@Override

View File

@ -44,7 +44,8 @@ public class Promise<T> implements Handler<T>, Future<T>
latch.countDown();
}
public void failed(Throwable x)
@Override
public void failed(T context, Throwable x)
{
this.failure = x;
latch.countDown();

View File

@ -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;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.frames.WindowUpdateFrame;
public class SPDYv3FlowControlStrategy implements FlowControlStrategy
{
private volatile int windowSize;
@Override
public int getWindowSize(ISession session)
{
return windowSize;
}
@Override
public void setWindowSize(ISession session, int windowSize)
{
int prevWindowSize = this.windowSize;
this.windowSize = windowSize;
for (Stream stream : session.getStreams())
((IStream)stream).updateWindowSize(windowSize - prevWindowSize);
}
@Override
public void onNewStream(ISession session, IStream stream)
{
stream.updateWindowSize(windowSize);
}
@Override
public void onWindowUpdate(ISession session, IStream stream, int delta)
{
if (stream != null)
stream.updateWindowSize(delta);
}
@Override
public void updateWindow(ISession session, IStream stream, int delta)
{
stream.updateWindowSize(delta);
}
@Override
public void onDataReceived(ISession session, IStream stream, DataInfo dataInfo)
{
// Do nothing
}
@Override
public void onDataConsumed(ISession session, IStream stream, DataInfo dataInfo, int 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.
int length = dataInfo.length();
if (dataInfo.consumed() == length && !stream.isClosed() && length > 0)
{
WindowUpdateFrame windowUpdateFrame = new WindowUpdateFrame(session.getVersion(), stream.getId(), length);
session.control(stream, windowUpdateFrame, 0, TimeUnit.MILLISECONDS, null, null);
}
}
}

View File

@ -20,7 +20,6 @@ import org.eclipse.jetty.spdy.api.SessionStatus;
public class SessionException extends RuntimeException
{
private final SessionStatus sessionStatus;
public SessionException(SessionStatus sessionStatus)

View File

@ -18,6 +18,7 @@ package org.eclipse.jetty.spdy;
import java.nio.ByteBuffer;
import java.nio.channels.InterruptedByTimeoutException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@ -33,6 +34,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
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.GoAwayInfo;
import org.eclipse.jetty.spdy.api.Handler;
@ -50,6 +52,7 @@ import org.eclipse.jetty.spdy.api.StreamStatus;
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.frames.ControlFrame;
import org.eclipse.jetty.spdy.frames.ControlFrameType;
import org.eclipse.jetty.spdy.frames.CredentialFrame;
import org.eclipse.jetty.spdy.frames.DataFrame;
import org.eclipse.jetty.spdy.frames.GoAwayFrame;
import org.eclipse.jetty.spdy.frames.HeadersFrame;
@ -92,11 +95,13 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
private final AtomicBoolean goAwaySent = new AtomicBoolean();
private final AtomicBoolean goAwayReceived = new AtomicBoolean();
private final AtomicInteger lastStreamId = new AtomicInteger();
private final FlowControlStrategy flowControlStrategy;
private boolean flushing;
private volatile int windowSize = 65536;
private boolean failed = false;
public StandardSession(short version, ByteBufferPool bufferPool, Executor threadPool, ScheduledExecutorService scheduler,
Controller<FrameBytes> controller, IdleListener idleListener, int initialStreamId, SessionFrameListener listener, Generator generator)
Controller<FrameBytes> controller, IdleListener idleListener, int initialStreamId, SessionFrameListener listener,
Generator generator, FlowControlStrategy flowControlStrategy)
{
this.version = version;
this.bufferPool = bufferPool;
@ -108,6 +113,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
this.pingIds = new AtomicInteger(initialStreamId);
this.listener = listener;
this.generator = generator;
this.flowControlStrategy = flowControlStrategy;
}
@Override
@ -152,7 +158,8 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
synchronized (this)
{
int streamId = streamIds.getAndAdd(2);
SynStreamFrame synStream = new SynStreamFrame(version, synInfo.getFlags(), streamId, associatedStreamId, synInfo.getPriority(), synInfo.getHeaders());
// TODO: for SPDYv3 we need to support the "slot" argument
SynStreamFrame synStream = new SynStreamFrame(version, synInfo.getFlags(), streamId, associatedStreamId, synInfo.getPriority(), (short)0, synInfo.getHeaders());
IStream stream = createStream(synStream, listener, true);
generateAndEnqueueControlFrame(stream, synStream, timeout, unit, handler, stream);
}
@ -265,14 +272,14 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
@Override
public void onControlFrame(ControlFrame frame)
{
notifyIdle(idleListener,false);
notifyIdle(idleListener, false);
try
{
logger.debug("Processing {}",frame);
logger.debug("Processing {}", frame);
if (goAwaySent.get())
{
logger.debug("Skipped processing of {}",frame);
logger.debug("Skipped processing of {}", frame);
return;
}
@ -323,6 +330,11 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
onWindowUpdate((WindowUpdateFrame)frame);
break;
}
case CREDENTIAL:
{
onCredential((CredentialFrame)frame);
break;
}
default:
{
throw new IllegalStateException();
@ -331,7 +343,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
}
finally
{
notifyIdle(idleListener,true);
notifyIdle(idleListener, true);
}
}
@ -341,11 +353,11 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
notifyIdle(idleListener, false);
try
{
logger.debug("Processing {}, {} data bytes",frame,data.remaining());
logger.debug("Processing {}, {} data bytes", frame, data.remaining());
if (goAwaySent.get())
{
logger.debug("Skipped processing of {}",frame);
logger.debug("Skipped processing of {}", frame);
return;
}
@ -353,18 +365,18 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
IStream stream = streams.get(streamId);
if (stream == null)
{
RstInfo rstInfo = new RstInfo(streamId,StreamStatus.INVALID_STREAM);
logger.debug("Unknown stream {}",rstInfo);
RstInfo rstInfo = new RstInfo(streamId, StreamStatus.INVALID_STREAM);
logger.debug("Unknown stream {}", rstInfo);
rst(rstInfo);
}
else
{
processData(stream,frame,data);
processData(stream, frame, data);
}
}
finally
{
notifyIdle(idleListener,true);
notifyIdle(idleListener, true);
}
}
@ -374,9 +386,19 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
listener.onIdle(idle);
}
private void processData(IStream stream, DataFrame frame, ByteBuffer data)
private void processData(final IStream stream, DataFrame frame, ByteBuffer data)
{
stream.process(frame,data);
ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(data, frame.isClose(), frame.isCompress())
{
@Override
public void consume(int delta)
{
super.consume(delta);
flowControlStrategy.onDataConsumed(StandardSession.this, stream, this, delta);
}
};
flowControlStrategy.onDataReceived(this, stream, dataInfo);
stream.process(dataInfo);
updateLastStreamId(stream);
if (stream.isClosed())
removeStream(stream);
@ -452,7 +474,9 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
private IStream newStream(SynStreamFrame frame)
{
IStream associatedStream = streams.get(frame.getAssociatedStreamId());
return new StandardStream(frame, this, windowSize, associatedStream);
IStream stream = new StandardStream(frame, this, associatedStream);
flowControlStrategy.onNewStream(this, stream);
return stream;
}
private void notifyStreamCreated(IStream stream)
@ -547,15 +571,12 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
Settings.Setting windowSizeSetting = frame.getSettings().get(Settings.ID.INITIAL_WINDOW_SIZE);
if (windowSizeSetting != null)
{
int prevWindowSize = windowSize;
windowSize = windowSizeSetting.value();
for (IStream stream : streams.values())
stream.updateWindowSize(windowSize - prevWindowSize);
logger.debug("Updated window size to {}",windowSize);
int windowSize = windowSizeSetting.value();
setWindowSize(windowSize);
logger.debug("Updated session window size to {}", windowSize);
}
SettingsInfo settingsInfo = new SettingsInfo(frame.getSettings(),frame.isClearPersisted());
notifyOnSettings(listener,settingsInfo);
SettingsInfo settingsInfo = new SettingsInfo(frame.getSettings(), frame.isClearPersisted());
notifyOnSettings(listener, settingsInfo);
flush();
}
@ -615,8 +636,14 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
{
int streamId = frame.getStreamId();
IStream stream = streams.get(streamId);
if (stream != null)
stream.process(frame);
flowControlStrategy.onWindowUpdate(this, stream, frame.getWindowDelta());
flush();
}
private void onCredential(CredentialFrame frame)
{
logger.warn("{} frame not yet supported", ControlFrameType.CREDENTIAL);
flush();
}
protected void close()
@ -735,11 +762,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
try
{
if (stream != null)
{
updateLastStreamId(stream);
if (stream.isClosed())
removeStream(stream);
}
// Synchronization is necessary, since we may have concurrent replies
// and those needs to be generated and enqueued atomically in order
@ -747,10 +770,10 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
synchronized (this)
{
ByteBuffer buffer = generator.control(frame);
logger.debug("Queuing {} on {}",frame,stream);
ControlFrameBytes<C> frameBytes = new ControlFrameBytes<>(stream,handler,context,frame,buffer);
logger.debug("Queuing {} on {}", frame, stream);
ControlFrameBytes<C> frameBytes = new ControlFrameBytes<>(stream, handler, context, frame, buffer);
if (timeout > 0)
frameBytes.task = scheduler.schedule(frameBytes,timeout,unit);
frameBytes.task = scheduler.schedule(frameBytes, timeout, unit);
// Special handling for PING frames, they must be sent as soon as possible
if (ControlFrameType.PING == frame.getType())
@ -759,9 +782,9 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
append(frameBytes);
}
}
catch (Throwable x)
catch (Exception x)
{
notifyHandlerFailed(handler, x);
notifyHandlerFailed(handler, context, x);
}
}
@ -787,9 +810,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
logger.debug("Queuing {} on {}",dataInfo,stream);
DataFrameBytes<C> frameBytes = new DataFrameBytes<>(stream,handler,context,dataInfo);
if (timeout > 0)
{
frameBytes.task = scheduler.schedule(frameBytes,timeout,unit);
}
append(frameBytes);
flush();
}
@ -822,9 +843,11 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
if (buffer != null)
{
queue.remove(i);
// TODO: stream.isUniDirectional() check here is only needed for pushStreams which send a syn with close=true --> find a better solution
if (stream != null && !streams.containsValue(stream) && !stream.isUnidirectional())
if (stream != null && stream.isReset())
{
frameBytes.fail(new StreamException(stream.getId(),StreamStatus.INVALID_STREAM));
return;
}
break;
}
@ -847,34 +870,50 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
private void append(FrameBytes frameBytes)
{
boolean fail;
synchronized (queue)
{
int index = queue.size();
while (index > 0)
fail = failed;
if (!fail)
{
FrameBytes element = queue.get(index - 1);
if (element.compareTo(frameBytes) >= 0)
break;
--index;
int index = queue.size();
while (index > 0)
{
FrameBytes element = queue.get(index - 1);
if (element.compareTo(frameBytes) >= 0)
break;
--index;
}
queue.add(index,frameBytes);
}
queue.add(index,frameBytes);
}
if (fail)
frameBytes.fail(new SPDYException("Session failed"));
}
private void prepend(FrameBytes frameBytes)
{
boolean fail;
synchronized (queue)
{
int index = 0;
while (index < queue.size())
fail = failed;
if (!fail)
{
FrameBytes element = queue.get(index);
if (element.compareTo(frameBytes) <= 0)
break;
++index;
int index = 0;
while (index < queue.size())
{
FrameBytes element = queue.get(index);
if (element.compareTo(frameBytes) <= 0)
break;
++index;
}
queue.add(index,frameBytes);
}
queue.add(index,frameBytes);
}
if (fail)
frameBytes.fail(new SPDYException("Session failed"));
}
@Override
@ -889,9 +928,23 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
}
@Override
public void failed(Throwable x)
public void failed(FrameBytes frameBytes, Throwable x)
{
throw new SPDYException(x);
List<FrameBytes> frameBytesToFail = new ArrayList<>();
frameBytesToFail.add(frameBytes);
synchronized (queue)
{
failed = true;
String logMessage = String.format("Failed write of %s, failing all %d frame(s) in queue",frameBytes,queue.size());
logger.debug(logMessage,x);
frameBytesToFail.addAll(queue);
queue.clear();
flushing = false;
}
for (FrameBytes fb : frameBytesToFail)
fb.fail(x);
}
protected void write(ByteBuffer buffer, Handler<FrameBytes> handler, FrameBytes frameBytes)
@ -951,12 +1004,12 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
}
}
private <C> void notifyHandlerFailed(Handler<C> handler, Throwable x)
private <C> void notifyHandlerFailed(Handler<C> handler, C context, Throwable x)
{
try
{
if (handler != null)
handler.failed(x);
handler.failed(context, x);
}
catch (Exception xx)
{
@ -964,6 +1017,16 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
}
}
public int getWindowSize()
{
return flowControlStrategy.getWindowSize(this);
}
public void setWindowSize(int initialWindowSize)
{
flowControlStrategy.setWindowSize(this, initialWindowSize);
}
public interface FrameBytes extends Comparable<FrameBytes>
{
public IStream getStream();
@ -998,8 +1061,16 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
@Override
public int compareTo(FrameBytes that)
{
// If this.stream.priority > that.stream.priority => -1 (this.stream has less priority than that.stream)
return that.getStream().getPriority() - getStream().getPriority();
// FrameBytes may have or not have a related stream (for example, PING do not have a related stream)
// FrameBytes without related streams have higher priority
IStream thisStream = getStream();
IStream thatStream = that.getStream();
if (thisStream == null)
return thatStream == null ? 0 : -1;
if (thatStream == null)
return 1;
// If this.stream.priority > that.stream.priority => this.stream has less priority than that.stream
return thatStream.getPriority() - thisStream.getPriority();
}
@Override
@ -1013,7 +1084,8 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
public void fail(Throwable x)
{
cancelTask();
notifyHandlerFailed(handler,x);
notifyHandlerFailed(handler,context,x);
StandardSession.this.flush();
}
private void cancelTask()
@ -1062,6 +1134,9 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
// Recipients will know the last good stream id and act accordingly.
close();
}
IStream stream = getStream();
if (stream != null && stream.isClosed())
removeStream(stream);
}
@Override
@ -1112,14 +1187,14 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
{
bufferPool.release(buffer);
IStream stream = getStream();
stream.updateWindowSize(-size);
flowControlStrategy.updateWindow(StandardSession.this, stream, -size);
if (dataInfo.available() > 0)
{
// We have written a frame out of this DataInfo, but there is more to write.
// We need to keep the correct ordering of frames, to avoid that another
// DataInfo for the same stream is written before this one is finished.
prepend(this);
flush();
}
else
{

View File

@ -16,7 +16,6 @@
package org.eclipse.jetty.spdy;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
@ -25,7 +24,6 @@ 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;
@ -37,11 +35,9 @@ import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.StreamStatus;
import org.eclipse.jetty.spdy.api.SynInfo;
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.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -52,18 +48,17 @@ public class StandardStream implements IStream
private final IStream associatedStream;
private final SynStreamFrame frame;
private final ISession session;
private final AtomicInteger windowSize;
private final AtomicInteger windowSize = new AtomicInteger();
private final Set<Stream> pushedStreams = Collections.newSetFromMap(new ConcurrentHashMap<Stream, Boolean>());
private volatile StreamFrameListener listener;
private volatile OpenState openState = OpenState.SYN_SENT;
private volatile CloseState closeState = CloseState.OPENED;
private volatile boolean reset = false;
public StandardStream(SynStreamFrame frame, ISession session, int windowSize, IStream associatedStream)
public StandardStream(SynStreamFrame frame, ISession session, IStream associatedStream)
{
this.frame = frame;
this.session = session;
this.windowSize = new AtomicInteger(windowSize);
this.associatedStream = associatedStream;
}
@ -113,7 +108,7 @@ public class StandardStream implements IStream
public void updateWindowSize(int delta)
{
int size = windowSize.addAndGet(delta);
logger.debug("Updated window size by {}, new window size {}",delta,size);
logger.debug("Updated window size {} -> {} for {}", size - delta, size, this);
}
@Override
@ -209,12 +204,6 @@ public class StandardStream implements IStream
notifyOnHeaders(headersInfo);
break;
}
case WINDOW_UPDATE:
{
WindowUpdateFrame windowUpdate = (WindowUpdateFrame)frame;
updateWindowSize(windowUpdate.getWindowDelta());
break;
}
case RST_STREAM:
{
reset = true;
@ -229,57 +218,28 @@ public class StandardStream implements IStream
}
@Override
public void process(DataFrame frame, ByteBuffer data)
public void process(DataInfo dataInfo)
{
// TODO: in v3 we need to send a rst instead of just ignoring
// ignore data frame if this stream is remotelyClosed already
if (isHalfClosed() && !isLocallyClosed())
if (isRemotelyClosed())
{
logger.debug("Ignoring received dataFrame as this stream is remotely closed: " + frame);
logger.debug("Stream is remotely closed, ignoring {}", dataInfo);
return;
}
if (!canReceive())
{
logger.debug("Can't receive. Sending rst: " + frame);
session.rst(new RstInfo(getId(),StreamStatus.PROTOCOL_ERROR));
logger.debug("Protocol error receiving {}, resetting" + dataInfo);
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR));
return;
}
updateCloseState(frame.isClose(),false);
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());
}
};
updateCloseState(dataInfo.isClose(), false);
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;
@ -305,7 +265,7 @@ public class StandardStream implements IStream
if (listener != null)
{
logger.debug("Invoking headers callback with {} on listener {}",frame,listener);
listener.onHeaders(this,headersInfo);
listener.onHeaders(this, headersInfo);
}
}
catch (Exception x)
@ -322,8 +282,8 @@ public class StandardStream implements IStream
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);
listener.onData(this, dataInfo);
logger.debug("Invoked data callback with {} on listener {}", dataInfo, listener);
}
}
catch (Exception x)
@ -345,7 +305,7 @@ public class StandardStream implements IStream
{
if (isClosed() || isReset())
{
handler.failed(new StreamException(getId(),StreamStatus.STREAM_ALREADY_CLOSED));
handler.failed(this, new StreamException(getId(),StreamStatus.STREAM_ALREADY_CLOSED));
return;
}
PushSynInfo pushSynInfo = new PushSynInfo(getId(),synInfo);
@ -456,6 +416,12 @@ public class StandardStream implements IStream
return closeState == CloseState.LOCALLY_CLOSED || closeState == CloseState.CLOSED;
}
private boolean isRemotelyClosed()
{
CloseState closeState = this.closeState;
return closeState == CloseState.REMOTELY_CLOSED || closeState == CloseState.CLOSED;
}
@Override
public String toString()
{

View File

@ -23,39 +23,44 @@ import java.nio.ByteBuffer;
*/
public class BytesDataInfo extends DataInfo
{
private byte[] bytes;
private int offset;
private final byte[] bytes;
private final int offset;
private final int length;
private int index;
public BytesDataInfo(byte[] bytes, boolean close)
{
this(bytes, close, false);
this(bytes, 0, bytes.length, close);
}
public BytesDataInfo(byte[] bytes, boolean close, boolean compress)
public BytesDataInfo(byte[] bytes, int offset, int length, boolean close)
{
super(close, compress);
super(close, false);
this.bytes = bytes;
this.offset = offset;
this.length = length;
this.index = offset;
}
@Override
public int length()
{
return bytes.length;
return length;
}
@Override
public int available()
{
return length() - offset;
return length - index + 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;
int chunk = Math.min(available(), space);
output.put(bytes, index, chunk);
index += chunk;
return chunk;
}
}

View File

@ -28,16 +28,16 @@ public interface Handler<C>
* <p>Callback invoked when the operation completes.</p>
*
* @param context the context
* @see #failed(Throwable)
* @see #failed(Object, Throwable)
*/
public abstract void completed(C context);
/**
* <p>Callback invoked when the operation fails.</p>
*
* @param context the context
* @param x the reason for the operation failure
*/
public void failed(Throwable x);
public void failed(C context, Throwable x);
/**
* <p>Empty implementation of {@link Handler}</p>
@ -52,9 +52,8 @@ public interface Handler<C>
}
@Override
public void failed(Throwable x)
public void failed(C context, Throwable x)
{
throw new SPDYException(x);
}
}
}

View File

@ -25,11 +25,6 @@ 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);
super(string.getBytes(Charset.forName("UTF-8")), close);
}
}

View File

@ -29,7 +29,8 @@ public enum ControlFrameType
PING((short)6),
GO_AWAY((short)7),
HEADERS((short)8),
WINDOW_UPDATE((short)9);
WINDOW_UPDATE((short)9),
CREDENTIAL((short)10);
public static ControlFrameType from(short code)
{

View File

@ -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 java.security.cert.Certificate;
public class CredentialFrame extends ControlFrame
{
private final short slot;
private final byte[] proof;
private final Certificate[] certificateChain;
public CredentialFrame(short version, short slot, byte[] proof, Certificate[] certificateChain)
{
super(version, ControlFrameType.CREDENTIAL, (byte)0);
this.slot = slot;
this.proof = proof;
this.certificateChain = certificateChain;
}
public short getSlot()
{
return slot;
}
public byte[] getProof()
{
return proof;
}
public Certificate[] getCertificateChain()
{
return certificateChain;
}
}

View File

@ -25,14 +25,16 @@ public class SynStreamFrame extends ControlFrame
private final int streamId;
private final int associatedStreamId;
private final byte priority;
private final short slot;
private final Headers headers;
public SynStreamFrame(short version, byte flags, int streamId, int associatedStreamId, byte priority, Headers headers)
public SynStreamFrame(short version, byte flags, int streamId, int associatedStreamId, byte priority, short slot, Headers headers)
{
super(version, ControlFrameType.SYN_STREAM, flags);
this.streamId = streamId;
this.associatedStreamId = associatedStreamId;
this.priority = priority;
this.slot = slot;
this.headers = headers;
}
@ -51,6 +53,11 @@ public class SynStreamFrame extends ControlFrame
return priority;
}
public short getSlot()
{
return slot;
}
public Headers getHeaders()
{
return headers;

View File

@ -0,0 +1,83 @@
/*
* 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.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.ArrayList;
import java.util.List;
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.CredentialFrame;
public class CredentialGenerator extends ControlFrameGenerator
{
public CredentialGenerator(ByteBufferPool bufferPool)
{
super(bufferPool);
}
@Override
public ByteBuffer generate(ControlFrame frame)
{
CredentialFrame credential = (CredentialFrame)frame;
byte[] proof = credential.getProof();
List<byte[]> certificates = serializeCertificates(credential.getCertificateChain());
int certificatesLength = 0;
for (byte[] certificate : certificates)
certificatesLength += certificate.length;
int frameBodyLength = 2 + 4 + proof.length + certificates.size() * 4 + certificatesLength;
int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
generateControlFrameHeader(credential, frameBodyLength, buffer);
buffer.putShort(credential.getSlot());
buffer.putInt(proof.length);
buffer.put(proof);
for (byte[] certificate : certificates)
{
buffer.putInt(certificate.length);
buffer.put(certificate);
}
buffer.flip();
return buffer;
}
private List<byte[]> serializeCertificates(Certificate[] certificates)
{
try
{
List<byte[]> result = new ArrayList<>(certificates.length);
for (Certificate certificate : certificates)
result.add(certificate.getEncoded());
return result;
}
catch (CertificateEncodingException x)
{
throw new SessionException(SessionStatus.PROTOCOL_ERROR, x);
}
}
}

View File

@ -42,6 +42,7 @@ public class Generator
generators.put(ControlFrameType.GO_AWAY, new GoAwayGenerator(bufferPool));
generators.put(ControlFrameType.HEADERS, new HeadersGenerator(bufferPool, headersBlockGenerator));
generators.put(ControlFrameType.WINDOW_UPDATE, new WindowUpdateGenerator(bufferPool));
generators.put(ControlFrameType.CREDENTIAL, new CredentialGenerator(bufferPool));
dataFrameGenerator = new DataFrameGenerator(bufferPool);
}

View File

@ -20,6 +20,7 @@ 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.HeadersFrame;
@ -43,6 +44,8 @@ public class HeadersGenerator extends ControlFrameGenerator
ByteBuffer headersBuffer = headersBlockGenerator.generate(version, headers.getHeaders());
int frameBodyLength = 4;
if (frame.getVersion() == SPDY.V2)
frameBodyLength += 2;
int frameLength = frameBodyLength + headersBuffer.remaining();
if (frameLength > 0xFF_FF_FF)
@ -58,6 +61,8 @@ public class HeadersGenerator extends ControlFrameGenerator
generateControlFrameHeader(headers, frameLength, buffer);
buffer.putInt(headers.getStreamId() & 0x7F_FF_FF_FF);
if (frame.getVersion() == SPDY.V2)
buffer.putShort((short)0);
buffer.put(headersBuffer);

View File

@ -64,6 +64,7 @@ public class SynStreamGenerator extends ControlFrameGenerator
buffer.putInt(streamId & 0x7F_FF_FF_FF);
buffer.putInt(synStream.getAssociatedStreamId() & 0x7F_FF_FF_FF);
writePriority(streamId, version, synStream.getPriority(), buffer);
buffer.put((byte)synStream.getSlot());
buffer.put(headersBuffer);
@ -85,6 +86,5 @@ public class SynStreamGenerator extends ControlFrameGenerator
throw new StreamException(streamId, StreamStatus.UNSUPPORTED_VERSION);
}
buffer.put(priority);
buffer.put((byte)0);
}
}

View File

@ -46,6 +46,7 @@ public abstract class ControlFrameParser
parsers.put(ControlFrameType.GO_AWAY, new GoAwayBodyParser(this));
parsers.put(ControlFrameType.HEADERS, new HeadersBodyParser(decompressor, this));
parsers.put(ControlFrameType.WINDOW_UPDATE, new WindowUpdateBodyParser(this));
parsers.put(ControlFrameType.CREDENTIAL, new CredentialBodyParser(this));
}
public short getVersion()

View File

@ -0,0 +1,272 @@
/*
* 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.io.ByteArrayInputStream;
import java.nio.ByteBuffer;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jetty.spdy.SessionException;
import org.eclipse.jetty.spdy.api.SessionStatus;
import org.eclipse.jetty.spdy.frames.ControlFrameType;
import org.eclipse.jetty.spdy.frames.CredentialFrame;
public class CredentialBodyParser extends ControlFrameBodyParser
{
private final List<Certificate> certificates = new ArrayList<>();
private final ControlFrameParser controlFrameParser;
private State state = State.SLOT;
private int totalLength;
private int cursor;
private short slot;
private int proofLength;
private byte[] proof;
private int certificateLength;
private byte[] certificate;
public CredentialBodyParser(ControlFrameParser controlFrameParser)
{
this.controlFrameParser = controlFrameParser;
}
@Override
public boolean parse(ByteBuffer buffer)
{
while (buffer.hasRemaining())
{
switch (state)
{
case SLOT:
{
if (buffer.remaining() >= 2)
{
slot = buffer.getShort();
checkSlotValid();
state = State.PROOF_LENGTH;
}
else
{
state = State.SLOT_BYTES;
cursor = 2;
}
break;
}
case SLOT_BYTES:
{
byte currByte = buffer.get();
--cursor;
slot += (currByte & 0xFF) << 8 * cursor;
if (cursor == 0)
{
checkSlotValid();
state = State.PROOF_LENGTH;
}
break;
}
case PROOF_LENGTH:
{
if (buffer.remaining() >= 4)
{
proofLength = buffer.getInt() & 0x7F_FF_FF_FF;
state = State.PROOF;
}
else
{
state = State.PROOF_LENGTH_BYTES;
cursor = 4;
}
break;
}
case PROOF_LENGTH_BYTES:
{
byte currByte = buffer.get();
--cursor;
proofLength += (currByte & 0xFF) << 8 * cursor;
if (cursor == 0)
{
proofLength &= 0x7F_FF_FF_FF;
state = State.PROOF;
}
break;
}
case PROOF:
{
totalLength = controlFrameParser.getLength() - 2 - 4 - proofLength;
proof = new byte[proofLength];
if (buffer.remaining() >= proofLength)
{
buffer.get(proof);
state = State.CERTIFICATE_LENGTH;
if (totalLength == 0)
{
onCredential();
return true;
}
}
else
{
state = State.PROOF_BYTES;
cursor = proofLength;
}
break;
}
case PROOF_BYTES:
{
proof[proofLength - cursor] = buffer.get();
--cursor;
if (cursor == 0)
{
state = State.CERTIFICATE_LENGTH;
if (totalLength == 0)
{
onCredential();
return true;
}
}
break;
}
case CERTIFICATE_LENGTH:
{
if (buffer.remaining() >= 4)
{
certificateLength = buffer.getInt() & 0x7F_FF_FF_FF;
state = State.CERTIFICATE;
}
else
{
state = State.CERTIFICATE_LENGTH_BYTES;
cursor = 4;
}
break;
}
case CERTIFICATE_LENGTH_BYTES:
{
byte currByte = buffer.get();
--cursor;
certificateLength += (currByte & 0xFF) << 8 * cursor;
if (cursor == 0)
{
certificateLength &= 0x7F_FF_FF_FF;
state = State.CERTIFICATE;
}
break;
}
case CERTIFICATE:
{
totalLength -= 4 + certificateLength;
certificate = new byte[certificateLength];
if (buffer.remaining() >= certificateLength)
{
buffer.get(certificate);
if (onCertificate())
return true;
}
else
{
state = State.CERTIFICATE_BYTES;
cursor = certificateLength;
}
break;
}
case CERTIFICATE_BYTES:
{
certificate[certificateLength - cursor] = buffer.get();
--cursor;
if (cursor == 0)
{
if (onCertificate())
return true;
}
break;
}
default:
{
throw new IllegalStateException();
}
}
}
return false;
}
private void checkSlotValid()
{
if (slot <= 0)
throw new SessionException(SessionStatus.PROTOCOL_ERROR,
"Invalid slot " + slot + " for " + ControlFrameType.CREDENTIAL + " frame");
}
private boolean onCertificate()
{
certificates.add(deserializeCertificate(certificate));
if (totalLength == 0)
{
onCredential();
return true;
}
else
{
certificateLength = 0;
state = State.CERTIFICATE_LENGTH;
}
return false;
}
private Certificate deserializeCertificate(byte[] bytes)
{
try
{
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
return certificateFactory.generateCertificate(new ByteArrayInputStream(bytes));
}
catch (CertificateException x)
{
throw new SessionException(SessionStatus.PROTOCOL_ERROR, x);
}
}
private void onCredential()
{
CredentialFrame frame = new CredentialFrame(controlFrameParser.getVersion(), slot,
Arrays.copyOf(proof, proof.length), certificates.toArray(new Certificate[certificates.size()]));
controlFrameParser.onControlFrame(frame);
reset();
}
private void reset()
{
state = State.SLOT;
totalLength = 0;
cursor = 0;
slot = 0;
proofLength = 0;
proof = null;
certificateLength = 0;
certificate = null;
certificates.clear();
}
public enum State
{
SLOT, SLOT_BYTES, PROOF_LENGTH, PROOF_LENGTH_BYTES, PROOF, PROOF_BYTES,
CERTIFICATE_LENGTH, CERTIFICATE_LENGTH_BYTES, CERTIFICATE, CERTIFICATE_BYTES
}
}

View File

@ -24,7 +24,7 @@ import org.eclipse.jetty.spdy.frames.GoAwayFrame;
public class GoAwayBodyParser extends ControlFrameBodyParser
{
private final ControlFrameParser controlFrameParser;
private State state = State.LAST_STREAM_ID;
private State state = State.LAST_GOOD_STREAM_ID;
private int cursor;
private int lastStreamId;
private int statusCode;
@ -41,7 +41,7 @@ public class GoAwayBodyParser extends ControlFrameBodyParser
{
switch (state)
{
case LAST_STREAM_ID:
case LAST_GOOD_STREAM_ID:
{
if (buffer.remaining() >= 4)
{
@ -66,12 +66,12 @@ public class GoAwayBodyParser extends ControlFrameBodyParser
}
else
{
state = State.LAST_STREAM_ID_BYTES;
state = State.LAST_GOOD_STREAM_ID_BYTES;
cursor = 4;
}
break;
}
case LAST_STREAM_ID_BYTES:
case LAST_GOOD_STREAM_ID_BYTES:
{
byte currByte = buffer.get();
--cursor;
@ -144,7 +144,7 @@ public class GoAwayBodyParser extends ControlFrameBodyParser
private void reset()
{
state = State.LAST_STREAM_ID;
state = State.LAST_GOOD_STREAM_ID;
cursor = 0;
lastStreamId = 0;
statusCode = 0;
@ -152,6 +152,6 @@ public class GoAwayBodyParser extends ControlFrameBodyParser
private enum State
{
LAST_STREAM_ID, LAST_STREAM_ID_BYTES, STATUS_CODE, STATUS_CODE_BYTES
LAST_GOOD_STREAM_ID, LAST_GOOD_STREAM_ID_BYTES, STATUS_CODE, STATUS_CODE_BYTES
}
}

View File

@ -21,6 +21,7 @@ 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.api.SPDY;
import org.eclipse.jetty.spdy.frames.ControlFrameType;
import org.eclipse.jetty.spdy.frames.HeadersFrame;
@ -51,7 +52,7 @@ public class HeadersBodyParser extends ControlFrameBodyParser
if (buffer.remaining() >= 4)
{
streamId = buffer.getInt() & 0x7F_FF_FF_FF;
state = State.HEADERS;
state = State.ADDITIONAL;
}
else
{
@ -68,14 +69,55 @@ public class HeadersBodyParser extends ControlFrameBodyParser
if (cursor == 0)
{
streamId &= 0x7F_FF_FF_FF;
state = State.HEADERS;
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() - 4;
if (version == SPDY.V2)
length -= 2;
if (headersBlockParser.parse(streamId, version, length, buffer))
{
byte flags = controlFrameParser.getFlags();
@ -109,7 +151,7 @@ public class HeadersBodyParser extends ControlFrameBodyParser
private enum State
{
STREAM_ID, STREAM_ID_BYTES, HEADERS
STREAM_ID, STREAM_ID_BYTES, ADDITIONAL, ADDITIONAL_BYTES, HEADERS
}
private class HeadersHeadersBlockParser extends HeadersBlockParser

View File

@ -38,6 +38,7 @@ public class SynStreamBodyParser extends ControlFrameBodyParser
private int streamId;
private int associatedStreamId;
private byte priority;
private short slot;
public SynStreamBodyParser(CompressionFactory.Decompressor decompressor, ControlFrameParser controlFrameParser)
{
@ -118,7 +119,9 @@ public class SynStreamBodyParser extends ControlFrameBodyParser
}
else
{
// Unused byte after priority, skip it
slot = (short)(currByte & 0xFF);
if (slot < 0)
throw new StreamException(streamId, StreamStatus.INVALID_CREDENTIALS);
cursor = 0;
state = State.HEADERS;
}
@ -134,7 +137,7 @@ public class SynStreamBodyParser extends ControlFrameBodyParser
if (flags > (SynInfo.FLAG_CLOSE | PushSynInfo.FLAG_UNIDIRECTIONAL))
throw new IllegalArgumentException("Invalid flag " + flags + " for frame " + ControlFrameType.SYN_STREAM);
SynStreamFrame frame = new SynStreamFrame(version, flags, streamId, associatedStreamId, priority, new Headers(headers, true));
SynStreamFrame frame = new SynStreamFrame(version, flags, streamId, associatedStreamId, priority, slot, new Headers(headers, true));
controlFrameParser.onControlFrame(frame);
reset();

View File

@ -46,7 +46,7 @@ public class AsyncTimeoutTest
Executor threadPool = Executors.newCachedThreadPool();
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
Session session = new StandardSession(SPDY.V2, bufferPool, threadPool, scheduler, new TestController(), null, 1, null, generator)
Session session = new StandardSession(SPDY.V2, bufferPool, threadPool, scheduler, new TestController(), null, 1, null, generator, new FlowControlStrategy.None())
{
@Override
public void flush()
@ -72,7 +72,7 @@ public class AsyncTimeoutTest
}
@Override
public void failed(Throwable x)
public void failed(Stream stream, Throwable x)
{
failedLatch.countDown();
}
@ -91,7 +91,7 @@ public class AsyncTimeoutTest
Executor threadPool = Executors.newCachedThreadPool();
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
Session session = new StandardSession(SPDY.V2, bufferPool, threadPool, scheduler, new TestController(), null, 1, null, generator)
Session session = new StandardSession(SPDY.V2, bufferPool, threadPool, scheduler, new TestController(), null, 1, null, generator, new FlowControlStrategy.None())
{
@Override
protected void write(ByteBuffer buffer, Handler<FrameBytes> handler, FrameBytes frameBytes)
@ -120,7 +120,7 @@ public class AsyncTimeoutTest
}
@Override
public void failed(Throwable x)
public void failed(Void context, Throwable x)
{
failedLatch.countDown();
}

View File

@ -16,16 +16,8 @@
package org.eclipse.jetty.spdy;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@ -34,6 +26,8 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.spdy.StandardSession.FrameBytes;
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.Headers;
@ -55,13 +49,25 @@ import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class StandardSessionTest
{
@Mock
private ISession sessionMock;
private Controller<FrameBytes> controller;
private ByteBufferPool bufferPool;
private Executor threadPool;
private StandardSession session;
@ -76,13 +82,36 @@ public class StandardSessionTest
threadPool = Executors.newCachedThreadPool();
scheduler = Executors.newSingleThreadScheduledExecutor();
generator = new Generator(new StandardByteBufferPool(),new StandardCompressionFactory.StandardCompressor());
session = new StandardSession(SPDY.V2,bufferPool,threadPool,scheduler,new TestController(),null,1,null,generator);
session = new StandardSession(SPDY.V2,bufferPool,threadPool,scheduler,controller,null,1,null,generator,new FlowControlStrategy.None());
headers = new Headers();
}
@SuppressWarnings("unchecked")
private void setControllerWriteExpectationToFail(final boolean fail)
{
when(controller.write(any(ByteBuffer.class),any(Handler.class),any(StandardSession.FrameBytes.class))).thenAnswer(new Answer<Integer>()
{
public Integer answer(InvocationOnMock invocation)
{
Object[] args = invocation.getArguments();
Handler<StandardSession.FrameBytes> handler = (Handler<FrameBytes>)args[1];
FrameBytes context = (FrameBytes)args[2];
if (fail)
handler.failed(context,new ClosedChannelException());
else
handler.completed(context);
return 0;
}
});
}
@Test
public void testStreamIsRemovedFromSessionWhenReset() throws InterruptedException, ExecutionException, TimeoutException
{
setControllerWriteExpectationToFail(false);
IStream stream = createStream();
assertThatStreamIsInSession(stream);
assertThat("stream is not reset",stream.isReset(),is(false));
@ -94,6 +123,8 @@ public class StandardSessionTest
@Test
public void testStreamIsAddedAndRemovedFromSession() throws InterruptedException, ExecutionException, TimeoutException
{
setControllerWriteExpectationToFail(false);
IStream stream = createStream();
assertThatStreamIsInSession(stream);
stream.updateCloseState(true,true);
@ -105,6 +136,8 @@ public class StandardSessionTest
@Test
public void testStreamIsRemovedWhenHeadersWithCloseFlagAreSent() throws InterruptedException, ExecutionException, TimeoutException
{
setControllerWriteExpectationToFail(false);
IStream stream = createStream();
assertThatStreamIsInSession(stream);
stream.updateCloseState(true,false);
@ -116,6 +149,8 @@ public class StandardSessionTest
@Test
public void testStreamIsUnidirectional() throws InterruptedException, ExecutionException, TimeoutException
{
setControllerWriteExpectationToFail(false);
IStream stream = createStream();
assertThat("stream is not unidirectional",stream.isUnidirectional(),not(true));
Stream pushStream = createPushStream(stream);
@ -125,6 +160,8 @@ public class StandardSessionTest
@Test
public void testPushStreamCreation() throws InterruptedException, ExecutionException, TimeoutException
{
setControllerWriteExpectationToFail(false);
Stream stream = createStream();
IStream pushStream = createPushStream(stream);
assertThat("Push stream must be associated to the first stream created",pushStream.getAssociatedStream().getId(),is(stream.getId()));
@ -134,6 +171,8 @@ public class StandardSessionTest
@Test
public void testPushStreamIsNotClosedWhenAssociatedStreamIsClosed() throws InterruptedException, ExecutionException, TimeoutException
{
setControllerWriteExpectationToFail(false);
IStream stream = createStream();
Stream pushStream = createPushStream(stream);
assertThatStreamIsNotHalfClosed(stream);
@ -155,6 +194,8 @@ public class StandardSessionTest
@Test
public void testCreatePushStreamOnClosedStream() throws InterruptedException, ExecutionException, TimeoutException
{
setControllerWriteExpectationToFail(false);
IStream stream = createStream();
stream.updateCloseState(true,true);
assertThatStreamIsHalfClosed(stream);
@ -167,15 +208,10 @@ public class StandardSessionTest
{
final CountDownLatch failedLatch = new CountDownLatch(1);
SynInfo synInfo = new SynInfo(headers,false,stream.getPriority());
stream.syn(synInfo,5,TimeUnit.SECONDS,new Handler<Stream>()
stream.syn(synInfo,5,TimeUnit.SECONDS,new Handler.Adapter<Stream>()
{
@Override
public void completed(Stream context)
{
}
@Override
public void failed(Throwable x)
public void failed(Stream stream, Throwable x)
{
failedLatch.countDown();
}
@ -186,6 +222,8 @@ public class StandardSessionTest
@Test
public void testPushStreamIsAddedAndRemovedFromParentAndSessionWhenClosed() throws InterruptedException, ExecutionException, TimeoutException
{
setControllerWriteExpectationToFail(false);
IStream stream = createStream();
IStream pushStream = createPushStream(stream);
assertThatPushStreamIsHalfClosed(pushStream);
@ -200,6 +238,8 @@ public class StandardSessionTest
@Test
public void testPushStreamIsRemovedWhenReset() throws InterruptedException, ExecutionException, TimeoutException
{
setControllerWriteExpectationToFail(false);
IStream stream = createStream();
IStream pushStream = (IStream)stream.syn(new SynInfo(false)).get();
assertThatPushStreamIsInSession(pushStream);
@ -212,6 +252,8 @@ public class StandardSessionTest
@Test
public void testPushStreamWithSynInfoClosedTrue() throws InterruptedException, ExecutionException, TimeoutException
{
setControllerWriteExpectationToFail(false);
IStream stream = createStream();
SynInfo synInfo = new SynInfo(headers,true,stream.getPriority());
IStream pushStream = (IStream)stream.syn(synInfo).get(5,TimeUnit.SECONDS);
@ -225,6 +267,8 @@ public class StandardSessionTest
public void testPushStreamSendHeadersWithCloseFlagIsRemovedFromSessionAndDisassociateFromParent() throws InterruptedException, ExecutionException,
TimeoutException
{
setControllerWriteExpectationToFail(false);
IStream stream = createStream();
SynInfo synInfo = new SynInfo(headers,false,stream.getPriority());
IStream pushStream = (IStream)stream.syn(synInfo).get(5,TimeUnit.SECONDS);
@ -240,6 +284,8 @@ public class StandardSessionTest
@Test
public void testCreatedAndClosedListenersAreCalledForNewStream() throws InterruptedException, ExecutionException, TimeoutException
{
setControllerWriteExpectationToFail(false);
final CountDownLatch createdListenerCalledLatch = new CountDownLatch(1);
final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
session.addListener(new TestStreamListener(createdListenerCalledLatch,closedListenerCalledLatch));
@ -253,6 +299,8 @@ public class StandardSessionTest
@Test
public void testListenerIsCalledForResetStream() throws InterruptedException, ExecutionException, TimeoutException
{
setControllerWriteExpectationToFail(false);
final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
session.addListener(new TestStreamListener(null,closedListenerCalledLatch));
IStream stream = createStream();
@ -263,6 +311,8 @@ public class StandardSessionTest
@Test
public void testCreatedAndClosedListenersAreCalledForNewPushStream() throws InterruptedException, ExecutionException, TimeoutException
{
setControllerWriteExpectationToFail(false);
final CountDownLatch createdListenerCalledLatch = new CountDownLatch(2);
final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
session.addListener(new TestStreamListener(createdListenerCalledLatch,closedListenerCalledLatch));
@ -277,6 +327,8 @@ public class StandardSessionTest
@Test
public void testListenerIsCalledForResetPushStream() throws InterruptedException, ExecutionException, TimeoutException
{
setControllerWriteExpectationToFail(false);
final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
session.addListener(new TestStreamListener(null,closedListenerCalledLatch));
IStream stream = createStream();
@ -313,31 +365,23 @@ public class StandardSessionTest
}
}
@SuppressWarnings("unchecked")
@Test(expected = IllegalStateException.class)
public void testSendDataOnHalfClosedStream() throws InterruptedException, ExecutionException, TimeoutException
{
SynStreamFrame synStreamFrame = new SynStreamFrame(SPDY.V2,SynInfo.FLAG_CLOSE,1,0,(byte)0,null);
IStream stream = new StandardStream(synStreamFrame,sessionMock,8184,null);
stream.updateCloseState(synStreamFrame.isClose(),true);
assertThat("stream is half closed",stream.isHalfClosed(),is(true));
stream.data(new StringDataInfo("data on half closed stream",true));
verify(sessionMock,never()).data(any(IStream.class),any(DataInfo.class),anyInt(),any(TimeUnit.class),any(Handler.class),any(void.class));
}
@Test
@Ignore("In V3 we need to rst the stream if we receive data on a remotely half closed stream.")
public void receiveDataOnRemotelyHalfClosedStreamResetsStreamInV3() throws InterruptedException, ExecutionException
{
setControllerWriteExpectationToFail(false);
IStream stream = (IStream)session.syn(new SynInfo(false),new StreamFrameListener.Adapter()).get();
stream.updateCloseState(true,false);
assertThat("stream is half closed from remote side",stream.isHalfClosed(),is(true));
stream.process(new DataFrame(stream.getId(),(byte)0,256),ByteBuffer.allocate(256));
stream.process(new ByteBufferDataInfo(ByteBuffer.allocate(256), true));
}
@Test
public void testReceiveDataOnRemotelyClosedStreamIsIgnored() throws InterruptedException, ExecutionException, TimeoutException
{
setControllerWriteExpectationToFail(false);
final CountDownLatch onDataCalledLatch = new CountDownLatch(1);
Stream stream = session.syn(new SynInfo(false),new StreamFrameListener.Adapter()
{
@ -353,10 +397,38 @@ public class StandardSessionTest
assertThat("onData is never called",onDataCalledLatch.await(1,TimeUnit.SECONDS),not(true));
}
@SuppressWarnings("unchecked")
@Test
public void testControllerWriteFailsInEndPointFlush() throws InterruptedException
{
setControllerWriteExpectationToFail(true);
final CountDownLatch failedCalledLatch = new CountDownLatch(2);
SynStreamFrame synStreamFrame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, null);
IStream stream = new StandardStream(synStreamFrame, session, null);
stream.updateWindowSize(8192);
Handler.Adapter<Void> handler = new Handler.Adapter<Void>()
{
@Override
public void failed(Void context, Throwable x)
{
failedCalledLatch.countDown();
}
};
// first data frame should fail on controller.write()
stream.data(new StringDataInfo("data", false), 5, TimeUnit.SECONDS, handler);
// second data frame should fail without controller.writer() as the connection is expected to be broken after first controller.write() call failed.
stream.data(new StringDataInfo("data", false), 5, TimeUnit.SECONDS, handler);
verify(controller, times(1)).write(any(ByteBuffer.class), any(Handler.class), any(FrameBytes.class));
assertThat("Handler.failed has been called twice", failedCalledLatch.await(5, TimeUnit.SECONDS), is(true));
}
private IStream createStream() throws InterruptedException, ExecutionException, TimeoutException
{
SynInfo synInfo = new SynInfo(headers,false,(byte)0);
return (IStream)session.syn(synInfo,new StreamFrameListener.Adapter()).get(5,TimeUnit.SECONDS);
return (IStream)session.syn(synInfo,new StreamFrameListener.Adapter()).get(50,TimeUnit.SECONDS);
}
private IStream createPushStream(Stream stream) throws InterruptedException, ExecutionException, TimeoutException
@ -365,21 +437,6 @@ public class StandardSessionTest
return (IStream)stream.syn(synInfo).get(5,TimeUnit.SECONDS);
}
private static class TestController implements Controller<StandardSession.FrameBytes>
{
@Override
public int write(ByteBuffer buffer, Handler<StandardSession.FrameBytes> handler, StandardSession.FrameBytes context)
{
handler.completed(context);
return buffer.remaining();
}
@Override
public void close(boolean onlyOutput)
{
}
}
private void assertThatStreamIsClosed(IStream stream)
{
assertThat("stream is closed",stream.isClosed(),is(true));

View File

@ -4,33 +4,29 @@
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.spdy;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.Handler;
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.StringDataInfo;
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.frames.SynStreamFrame;
import org.junit.Test;
@ -39,6 +35,17 @@ import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/* ------------------------------------------------------------ */
/**
@ -48,7 +55,7 @@ public class StandardStreamTest
{
@Mock private ISession session;
@Mock private SynStreamFrame synStreamFrame;
/**
* Test method for {@link org.eclipse.jetty.spdy.StandardStream#syn(org.eclipse.jetty.spdy.api.SynInfo)}.
*/
@ -56,7 +63,7 @@ public class StandardStreamTest
@Test
public void testSyn()
{
Stream stream = new StandardStream(synStreamFrame,session,0,null);
Stream stream = new StandardStream(synStreamFrame,session,null);
Set<Stream> streams = new HashSet<>();
streams.add(stream);
when(synStreamFrame.isClose()).thenReturn(false);
@ -65,11 +72,11 @@ public class StandardStreamTest
stream.syn(synInfo);
verify(session).syn(argThat(new PushSynInfoMatcher(stream.getId(),synInfo)),any(StreamFrameListener.class),anyLong(),any(TimeUnit.class),any(Handler.class));
}
private class PushSynInfoMatcher extends ArgumentMatcher<PushSynInfo>{
int associatedStreamId;
SynInfo synInfo;
public PushSynInfoMatcher(int associatedStreamId, SynInfo synInfo)
{
this.associatedStreamId = associatedStreamId;
@ -93,7 +100,7 @@ public class StandardStreamTest
@Test
public void testSynOnClosedStream(){
IStream stream = new StandardStream(synStreamFrame,session,0,null);
IStream stream = new StandardStream(synStreamFrame,session,null);
stream.updateCloseState(true,true);
stream.updateCloseState(true,false);
assertThat("stream expected to be closed",stream.isClosed(),is(true));
@ -101,7 +108,7 @@ public class StandardStreamTest
stream.syn(new SynInfo(false),1,TimeUnit.SECONDS,new Handler.Adapter<Stream>()
{
@Override
public void failed(Throwable x)
public void failed(Stream stream, Throwable x)
{
failedLatch.countDown();
}
@ -109,4 +116,16 @@ public class StandardStreamTest
assertThat("PushStream creation failed", failedLatch.getCount(), equalTo(0L));
}
@SuppressWarnings("unchecked")
@Test(expected = IllegalStateException.class)
public void testSendDataOnHalfClosedStream() throws InterruptedException, ExecutionException, TimeoutException
{
SynStreamFrame synStreamFrame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, null);
IStream stream = new StandardStream(synStreamFrame, session, null);
stream.updateWindowSize(8192);
stream.updateCloseState(synStreamFrame.isClose(), true);
assertThat("stream is half closed", stream.isHalfClosed(), is(true));
stream.data(new StringDataInfo("data on half closed stream", true));
verify(session, never()).data(any(IStream.class), any(DataInfo.class), anyInt(), any(TimeUnit.class), any(Handler.class), any(void.class));
}
}

View File

@ -29,7 +29,7 @@ public class ClientUsageTest
@Test
public void testClientRequestResponseNoBody() throws Exception
{
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null);
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null);
session.syn(new SynInfo(true), new StreamFrameListener.Adapter()
{
@ -48,7 +48,7 @@ public class ClientUsageTest
@Test
public void testClientRequestWithBodyResponseNoBody() throws Exception
{
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null);
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null);
Stream stream = session.syn(new SynInfo(false), new StreamFrameListener.Adapter()
{
@ -69,7 +69,7 @@ public class ClientUsageTest
@Test
public void testAsyncClientRequestWithBodyResponseNoBody() throws Exception
{
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null);
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null);
final String context = "context";
session.syn(new SynInfo(false), new StreamFrameListener.Adapter()
@ -104,7 +104,7 @@ public class ClientUsageTest
@Test
public void testAsyncClientRequestWithBodyAndResponseWithBody() throws Exception
{
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null);
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null);
session.syn(new SynInfo(false), new StreamFrameListener.Adapter()
{

View File

@ -0,0 +1,102 @@
/*
* 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.io.InputStream;
import java.nio.ByteBuffer;
import java.security.KeyStore;
import java.security.cert.Certificate;
import org.eclipse.jetty.spdy.StandardByteBufferPool;
import org.eclipse.jetty.spdy.StandardCompressionFactory;
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.generator.Generator;
import org.eclipse.jetty.spdy.parser.Parser;
import org.eclipse.jetty.util.resource.Resource;
import org.junit.Assert;
import org.junit.Test;
public class CredentialGenerateParseTest
{
@Test
public void testGenerateParse() throws Exception
{
short slot = 1;
byte[] proof = new byte[]{0, 1, 2};
Certificate[] temp = loadCertificates();
Certificate[] certificates = new Certificate[temp.length * 2];
System.arraycopy(temp, 0, certificates, 0, temp.length);
System.arraycopy(temp, 0, certificates, temp.length, temp.length);
CredentialFrame frame1 = new CredentialFrame(SPDY.V3, slot, proof, certificates);
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
ByteBuffer buffer = generator.control(frame1);
Assert.assertNotNull(buffer);
TestSPDYParserListener listener = new TestSPDYParserListener();
Parser parser = new Parser(new StandardCompressionFactory().newDecompressor());
parser.addListener(listener);
parser.parse(buffer);
ControlFrame frame2 = listener.getControlFrame();
Assert.assertNotNull(frame2);
Assert.assertEquals(ControlFrameType.CREDENTIAL, frame2.getType());
CredentialFrame credential = (CredentialFrame)frame2;
Assert.assertEquals(SPDY.V3, credential.getVersion());
Assert.assertEquals(0, credential.getFlags());
Assert.assertEquals(slot, credential.getSlot());
Assert.assertArrayEquals(proof, credential.getProof());
Assert.assertArrayEquals(certificates, credential.getCertificateChain());
}
@Test
public void testGenerateParseOneByteAtATime() throws Exception
{
short slot = 1;
byte[] proof = new byte[]{0, 1, 2};
Certificate[] certificates = loadCertificates();
CredentialFrame frame1 = new CredentialFrame(SPDY.V3, slot, proof, certificates);
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
ByteBuffer buffer = generator.control(frame1);
Assert.assertNotNull(buffer);
TestSPDYParserListener listener = new TestSPDYParserListener();
Parser parser = new Parser(new StandardCompressionFactory().newDecompressor());
parser.addListener(listener);
while (buffer.hasRemaining())
parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()}));
ControlFrame frame2 = listener.getControlFrame();
Assert.assertNotNull(frame2);
Assert.assertEquals(ControlFrameType.CREDENTIAL, frame2.getType());
CredentialFrame credential = (CredentialFrame)frame2;
Assert.assertEquals(SPDY.V3, credential.getVersion());
Assert.assertEquals(0, credential.getFlags());
Assert.assertEquals(slot, credential.getSlot());
Assert.assertArrayEquals(proof, credential.getProof());
Assert.assertArrayEquals(certificates, credential.getCertificateChain());
}
private Certificate[] loadCertificates() throws Exception
{
KeyStore keyStore = KeyStore.getInstance("JKS");
InputStream keyStoreStream = Resource.newResource("src/test/resources/keystore.jks").getInputStream();
keyStore.load(keyStoreStream, "storepwd".toCharArray());
return keyStore.getCertificateChain("mykey");
}
}

View File

@ -37,10 +37,11 @@ public class SynStreamGenerateParseTest
int streamId = 13;
int associatedStreamId = 11;
byte priority = 3;
short slot = 5;
Headers headers = new Headers();
headers.put("a", "b");
headers.put("c", "d");
SynStreamFrame frame1 = new SynStreamFrame(SPDY.V2, flags, streamId, associatedStreamId, priority, headers);
SynStreamFrame frame1 = new SynStreamFrame(SPDY.V2, flags, streamId, associatedStreamId, priority, slot, headers);
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
ByteBuffer buffer = generator.control(frame1);
@ -60,6 +61,7 @@ public class SynStreamGenerateParseTest
Assert.assertEquals(associatedStreamId, synStream.getAssociatedStreamId());
Assert.assertEquals(flags, synStream.getFlags());
Assert.assertEquals(priority, synStream.getPriority());
Assert.assertEquals(slot, synStream.getSlot());
Assert.assertEquals(headers, synStream.getHeaders());
}
@ -70,10 +72,11 @@ public class SynStreamGenerateParseTest
int streamId = 13;
int associatedStreamId = 11;
byte priority = 3;
short slot = 5;
Headers headers = new Headers();
headers.put("a", "b");
headers.put("c", "d");
SynStreamFrame frame1 = new SynStreamFrame(SPDY.V2, flags, streamId, associatedStreamId, priority, headers);
SynStreamFrame frame1 = new SynStreamFrame(SPDY.V2, flags, streamId, associatedStreamId, priority, slot, headers);
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
ByteBuffer buffer = generator.control(frame1);
@ -94,6 +97,7 @@ public class SynStreamGenerateParseTest
Assert.assertEquals(associatedStreamId, synStream.getAssociatedStreamId());
Assert.assertEquals(flags, synStream.getFlags());
Assert.assertEquals(priority, synStream.getPriority());
Assert.assertEquals(slot, synStream.getSlot());
Assert.assertEquals(headers, synStream.getHeaders());
}
}

View File

@ -23,7 +23,7 @@ public class UnknownControlFrameTest
@Test
public void testUnknownControlFrame() throws Exception
{
SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, new Headers());
SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, new Headers());
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
ByteBuffer buffer = generator.control(frame);
// Change the frame type to unknown

Binary file not shown.

Binary file not shown.

View File

@ -39,7 +39,7 @@
<stopKey>quit</stopKey>
<jvmArgs>
-Dlog4j.configuration=file://${basedir}/src/main/resources/log4j.properties
-Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${project.version}/npn-boot-${project.version}.jar
-Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${npn.version}/npn-boot-${npn.version}.jar
</jvmArgs>
<jettyXml>${basedir}/src/main/config/etc/jetty-spdy.xml</jettyXml>
<contextPath>/</contextPath>

View File

@ -11,6 +11,8 @@
<Set name="protocol">TLSv1</Set>
</New>
<!--<Set class="org.eclipse.jetty.npn.NextProtoNego" name="debug" type="boolean">true</Set>-->
<Call name="addConnector">
<Arg>
<New class="org.eclipse.jetty.spdy.http.HTTPSPDYServerConnector">

View File

@ -0,0 +1,75 @@
/*
* 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.http;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jetty.spdy.api.SPDY;
public enum HTTPSPDYHeader
{
METHOD("method", ":method"),
URI("url", ":path"),
VERSION("version", ":version"),
SCHEME("scheme", ":scheme"),
HOST("host", ":host"),
STATUS("status", ":status");
public static HTTPSPDYHeader from(short version, String name)
{
switch (version)
{
case SPDY.V2:
return Names.v2Names.get(name);
case SPDY.V3:
return Names.v3Names.get(name);
default:
throw new IllegalStateException();
}
}
private final String v2Name;
private final String v3Name;
private HTTPSPDYHeader(String v2Name, String v3Name)
{
this.v2Name = v2Name;
Names.v2Names.put(v2Name, this);
this.v3Name = v3Name;
Names.v3Names.put(v3Name, this);
}
public String name(short version)
{
switch (version)
{
case SPDY.V2:
return v2Name;
case SPDY.V3:
return v3Name;
default:
throw new IllegalStateException();
}
}
private static class Names
{
private static final Map<String, HTTPSPDYHeader> v2Names = new HashMap<>();
private static final Map<String, HTTPSPDYHeader> v3Names = new HashMap<>();
}
}

View File

@ -21,42 +21,40 @@ import java.io.IOException;
import org.eclipse.jetty.http.HttpSchemes;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.spdy.AsyncConnectionFactory;
import org.eclipse.jetty.spdy.SPDYServerConnector;
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.util.ssl.SslContextFactory;
public class HTTPSPDYServerConnector extends SPDYServerConnector
{
private final AsyncConnectionFactory defaultConnectionFactory;
private final PushStrategy pushStrategy = new PushStrategy.None();
public HTTPSPDYServerConnector()
{
this(null);
this(null, new PushStrategy.None());
}
public HTTPSPDYServerConnector(PushStrategy pushStrategy)
{
this(null, pushStrategy);
}
public HTTPSPDYServerConnector(SslContextFactory sslContextFactory)
{
super(null, sslContextFactory);
// Override the default connection factory for non-SSL connections
defaultConnectionFactory = new ServerHTTPAsyncConnectionFactory(this);
this(sslContextFactory, new PushStrategy.None());
}
@Override
protected void doStart() throws Exception
public HTTPSPDYServerConnector(SslContextFactory sslContextFactory, PushStrategy pushStrategy)
{
super.doStart();
// We pass a null ServerSessionFrameListener because for
// HTTP over SPDY we need one that references the endPoint
super(null, sslContextFactory);
// Override the "spdy/3" protocol by handling HTTP over SPDY
putAsyncConnectionFactory("spdy/3", new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V3, getByteBufferPool(), getExecutor(), getScheduler(), this, pushStrategy));
// Override the "spdy/2" protocol by handling HTTP over SPDY
putAsyncConnectionFactory("spdy/2", new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), getScheduler(), this, pushStrategy));
// Add the "http/1.1" protocol for browsers that do not support NPN
// Add the "http/1.1" protocol for browsers that support NPN but not SPDY
putAsyncConnectionFactory("http/1.1", new ServerHTTPAsyncConnectionFactory(this));
}
@Override
protected AsyncConnectionFactory getDefaultAsyncConnectionFactory()
{
return defaultConnectionFactory;
// Override the default connection factory for non-SSL connections to speak plain HTTP
setDefaultAsyncConnectionFactory(getAsyncConnectionFactory("http/1.1"));
}
@Override

View File

@ -24,8 +24,10 @@ import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.http.HttpException;
import org.eclipse.jetty.http.HttpFields;
@ -47,6 +49,7 @@ import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.spdy.SPDYAsyncConnection;
import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
import org.eclipse.jetty.spdy.api.BytesDataInfo;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.Handler;
import org.eclipse.jetty.spdy.api.Headers;
@ -66,6 +69,7 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
private final Queue<Runnable> tasks = new LinkedList<>();
private final BlockingQueue<DataInfo> dataInfos = new LinkedBlockingQueue<>();
private final short version;
private final SPDYAsyncConnection connection;
private final PushStrategy pushStrategy;
private final Stream stream;
@ -75,9 +79,10 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
private volatile State state = State.INITIAL;
private boolean dispatched; // Guarded by synchronization on tasks
public ServerHTTPSPDYAsyncConnection(Connector connector, AsyncEndPoint endPoint, Server server, SPDYAsyncConnection connection, PushStrategy pushStrategy, Stream stream)
public ServerHTTPSPDYAsyncConnection(Connector connector, AsyncEndPoint endPoint, Server server, short version, SPDYAsyncConnection connection, PushStrategy pushStrategy, Stream stream)
{
super(connector, endPoint, server);
this.version = version;
this.connection = connection;
this.pushStrategy = pushStrategy;
this.stream = stream;
@ -159,9 +164,9 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
}
case REQUEST:
{
Headers.Header method = headers.get("method");
Headers.Header uri = headers.get("url");
Headers.Header version = headers.get("version");
Headers.Header method = headers.get(HTTPSPDYHeader.METHOD.name(version));
Headers.Header uri = headers.get(HTTPSPDYHeader.URI.name(version));
Headers.Header version = headers.get(HTTPSPDYHeader.VERSION.name(this.version));
if (method == null || uri == null || version == null)
throw new HttpException(HttpStatus.BAD_REQUEST_400);
@ -181,15 +186,19 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
for (Headers.Header header : headers)
{
String name = header.name();
// Skip special SPDY headers, unless it's the "host" header
HTTPSPDYHeader specialHeader = HTTPSPDYHeader.from(version, name);
if (specialHeader != null)
{
if (specialHeader == HTTPSPDYHeader.HOST)
name = "host";
else
continue;
}
switch (name)
{
case "method":
case "version":
case "url":
{
// Skip request line headers
continue;
}
case "connection":
case "keep-alive":
case "proxy-connection":
@ -264,8 +273,8 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
else
{
Headers headers = new Headers();
headers.put("status", String.valueOf(status));
headers.put("version", "HTTP/1.1");
headers.put(HTTPSPDYHeader.STATUS.name(version), String.valueOf(status));
headers.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
stream.reply(new ReplyInfo(headers, true));
}
}
@ -393,21 +402,22 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
{
if (!stream.isUnidirectional())
stream.reply(replyInfo);
if (replyInfo.getHeaders().get("status").value().startsWith("200") && !stream.isClosed() && !isIfModifiedSinceHeaderPresent())
if (replyInfo.getHeaders().get(HTTPSPDYHeader.STATUS.name(version)).value().startsWith("200") &&
!stream.isClosed() && !isIfModifiedSinceHeaderPresent())
{
// We have a 200 OK with some content to send
Headers.Header scheme = headers.get("scheme");
Headers.Header host = headers.get("host");
Headers.Header url = headers.get("url");
Set<String> pushResources = pushStrategy.apply(stream, this.headers, replyInfo.getHeaders());
String referrer = new StringBuilder(scheme.value()).append("://").append(host.value()).append(url.value()).toString();
Headers.Header scheme = headers.get(HTTPSPDYHeader.SCHEME.name(version));
Headers.Header host = headers.get(HTTPSPDYHeader.HOST.name(version));
Headers.Header uri = headers.get(HTTPSPDYHeader.URI.name(version));
Set<String> pushResources = pushStrategy.apply(stream, headers, replyInfo.getHeaders());
String referrer = new StringBuilder(scheme.value()).append("://").append(host.value()).append(uri.value()).toString();
for (String pushURL : pushResources)
{
final Headers pushHeaders = new Headers();
pushHeaders.put("method", "GET");
pushHeaders.put("url", pushURL);
pushHeaders.put("version", "HTTP/1.1");
pushHeaders.put(HTTPSPDYHeader.METHOD.name(version), "GET");
pushHeaders.put(HTTPSPDYHeader.URI.name(version), pushURL);
pushHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
pushHeaders.put(scheme);
pushHeaders.put(host);
pushHeaders.put("referer", referrer);
@ -418,7 +428,7 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
@Override
public void completed(Stream pushStream)
{
Synchronous pushConnection = new Synchronous(getConnector(), getEndPoint(), getServer(), connection, pushStrategy, pushStream);
Synchronous pushConnection = new Synchronous(getConnector(), getEndPoint(), getServer(), version, connection, pushStrategy, pushStream);
pushConnection.beginRequest(pushHeaders, true);
}
});
@ -427,12 +437,10 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
}
private boolean isIfModifiedSinceHeaderPresent()
{
if (headers.get("if-modified-since") != null)
return true;
return false;
}
{
return headers.get("if-modified-since") != null;
}
private Buffer consumeContent(long maxIdleTime) throws IOException, InterruptedException
{
while (true)
@ -614,11 +622,11 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
{
Headers headers = new Headers();
String version = "HTTP/1.1";
headers.put("version", version);
headers.put(HTTPSPDYHeader.VERSION.name(ServerHTTPSPDYAsyncConnection.this.version), version);
StringBuilder status = new StringBuilder().append(_status);
if (_reason != null)
status.append(" ").append(_reason.toString("UTF-8"));
headers.put("status", status.toString());
headers.put(HTTPSPDYHeader.STATUS.name(ServerHTTPSPDYAsyncConnection.this.version), status.toString());
logger.debug("HTTP < {} {}", version, status);
if (fields != null)
@ -634,18 +642,13 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
}
// We have to query the HttpGenerator and its buffers to know
// whether there is content buffered; if so, send the data frame
// whether there is content buffered and update the generator state
Buffer content = getContentBuffer();
reply(stream, new ReplyInfo(headers, content == null));
if (content != null)
{
closed = allContentAdded || isAllContentWritten();
ByteBuffer buffer = ByteBuffer.wrap(content.asArray());
logger.debug("HTTP < {} bytes of content", buffer.remaining());
// Send the data frame
stream.data(new ByteBufferDataInfo(buffer, closed));
// Update HttpGenerator fields so that they remain consistent
content.clear();
_state = closed ? HttpGenerator.STATE_END : HttpGenerator.STATE_CONTENT;
}
else
@ -660,7 +663,7 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
{
if (_buffer != null && _buffer.length() > 0)
return _buffer;
if (_bypass && _content != null && _content.length() > 0)
if (_content != null && _content.length() > 0)
return _content;
return null;
}
@ -685,22 +688,46 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
@Override
public void flush(long maxIdleTime) throws IOException
{
while (_content != null && _content.length() > 0)
try
{
_content.skip(_buffer.put(_content));
ByteBuffer buffer = ByteBuffer.wrap(_buffer.asArray());
logger.debug("HTTP < {} bytes of content", buffer.remaining());
_buffer.clear();
closed = _content.length() == 0 && _last;
stream.data(new ByteBufferDataInfo(buffer, closed));
boolean expired = !connection.getEndPoint().blockWritable(maxIdleTime);
if (expired)
Buffer content = getContentBuffer();
if (content != null)
{
stream.getSession().goAway();
throw new EOFException("write timeout");
DataInfo dataInfo = toDataInfo(content, closed);
logger.debug("HTTP < {} bytes of content", dataInfo.length());
stream.data(dataInfo).get(maxIdleTime, TimeUnit.MILLISECONDS);
content.clear();
}
}
catch (TimeoutException x)
{
stream.getSession().goAway();
throw new EOFException("write timeout");
}
catch (InterruptedException x)
{
throw new InterruptedIOException();
}
catch (ExecutionException x)
{
throw new IOException(x.getCause());
}
}
private DataInfo toDataInfo(Buffer buffer, boolean close)
{
if (buffer instanceof ByteArrayBuffer)
return new BytesDataInfo(buffer.array(), buffer.getIndex(), buffer.length(), close);
if (buffer instanceof NIOBuffer)
{
ByteBuffer byteBuffer = ((NIOBuffer)buffer).getByteBuffer();
byteBuffer.limit(buffer.putIndex());
byteBuffer.position(buffer.getIndex());
return new ByteBufferDataInfo(byteBuffer, close);
}
return new BytesDataInfo(buffer.asArray(), close);
}
@Override
@ -727,19 +754,15 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
Buffer content = getContentBuffer();
if (content != null)
{
ByteBuffer buffer = ByteBuffer.wrap(content.asArray());
logger.debug("HTTP < {} bytes of content", buffer.remaining());
// Update HttpGenerator fields so that they remain consistent
content.clear();
closed = true;
_state = STATE_END;
// Send the data frame
stream.data(new ByteBufferDataInfo(buffer, true));
flush(getMaxIdleTime());
}
else if (!closed)
{
closed = true;
_state = STATE_END;
// Send the data frame
// Send the last, empty, data frame
stream.data(new ByteBufferDataInfo(ZERO_BYTES, true));
}
}
@ -747,9 +770,9 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
private static class Synchronous extends ServerHTTPSPDYAsyncConnection
{
private Synchronous(Connector connector, AsyncEndPoint endPoint, Server server, SPDYAsyncConnection connection, PushStrategy pushStrategy, Stream stream)
private Synchronous(Connector connector, AsyncEndPoint endPoint, Server server, short version, SPDYAsyncConnection connection, PushStrategy pushStrategy, Stream stream)
{
super(connector, endPoint, server, connection, pushStrategy, stream);
super(connector, endPoint, server, version, connection, pushStrategy, stream);
}
@Override

View File

@ -52,7 +52,7 @@ public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnect
}
@Override
protected ServerSessionFrameListener newServerSessionFrameListener(AsyncEndPoint endPoint, Object attachment)
protected ServerSessionFrameListener provideServerSessionFrameListener(AsyncEndPoint endPoint, Object attachment)
{
return new HTTPServerFrameListener(endPoint);
}
@ -78,8 +78,8 @@ public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnect
logger.debug("Received {} on {}", synInfo, stream);
HTTPSPDYAsyncEndPoint asyncEndPoint = new HTTPSPDYAsyncEndPoint(endPoint, stream);
ServerHTTPSPDYAsyncConnection connection = new ServerHTTPSPDYAsyncConnection(connector,
asyncEndPoint, connector.getServer(), (SPDYAsyncConnection)endPoint.getConnection(),
ServerHTTPSPDYAsyncConnection connection = new ServerHTTPSPDYAsyncConnection(connector, asyncEndPoint,
connector.getServer(), getVersion(), (SPDYAsyncConnection)endPoint.getConnection(),
pushStrategy, stream);
asyncEndPoint.setConnection(connection);
stream.setAttribute(CONNECTION_ATTRIBUTE, connection);

View File

@ -54,9 +54,14 @@ public abstract class AbstractHTTPSPDYTest
protected SPDYServerConnector connector;
protected InetSocketAddress startHTTPServer(Handler handler) throws Exception
{
return startHTTPServer(SPDY.V2, handler);
}
protected InetSocketAddress startHTTPServer(short version, Handler handler) throws Exception
{
server = new Server();
connector = newHTTPSPDYServerConnector();
connector = newHTTPSPDYServerConnector(version);
connector.setPort(0);
server.addConnector(connector);
server.setHandler(handler);
@ -64,20 +69,21 @@ public abstract class AbstractHTTPSPDYTest
return new InetSocketAddress("localhost", connector.getLocalPort());
}
protected SPDYServerConnector newHTTPSPDYServerConnector()
protected SPDYServerConnector newHTTPSPDYServerConnector(short version)
{
// For these tests, we need the connector to speak HTTP over SPDY even in non-SSL
return new HTTPSPDYServerConnector()
{
@Override
protected AsyncConnectionFactory getDefaultAsyncConnectionFactory()
{
return new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), getScheduler(), this, new PushStrategy.None());
}
};
SPDYServerConnector connector = new HTTPSPDYServerConnector();
AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version, connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, new PushStrategy.None());
connector.setDefaultAsyncConnectionFactory(defaultFactory);
return connector;
}
protected Session startClient(InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception
{
return startClient(SPDY.V2, socketAddress, listener);
}
protected Session startClient(short version, InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception
{
if (clientFactory == null)
{
@ -86,7 +92,7 @@ public abstract class AbstractHTTPSPDYTest
clientFactory = newSPDYClientFactory(threadPool);
clientFactory.start();
}
return clientFactory.newSPDYClient(SPDY.V2).connect(socketAddress, listener).get(5, TimeUnit.SECONDS);
return clientFactory.newSPDYClient(version).connect(socketAddress, listener).get(5, TimeUnit.SECONDS);
}
protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)

View File

@ -27,7 +27,6 @@ import javax.net.ssl.SSLSocket;
import org.eclipse.jetty.npn.NextProtoNego;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.spdy.AsyncConnectionFactory;
import org.eclipse.jetty.spdy.SPDYServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
@ -109,9 +108,8 @@ public class ProtocolNegotiationTest
public String selectProtocol(List<String> strings)
{
Assert.assertNotNull(strings);
Assert.assertEquals(1, strings.size());
String protocol = strings.get(0);
Assert.assertEquals("http/1.1", protocol);
String protocol = "http/1.1";
Assert.assertTrue(strings.contains(protocol));
return protocol;
}
});
@ -166,11 +164,11 @@ public class ProtocolNegotiationTest
public String selectProtocol(List<String> strings)
{
Assert.assertNotNull(strings);
Assert.assertEquals(2, strings.size());
String spdyProtocol = strings.get(0);
Assert.assertEquals("spdy/2", spdyProtocol);
String httpProtocol = strings.get(1);
Assert.assertEquals("http/1.1", httpProtocol);
String spdyProtocol = "spdy/2";
Assert.assertTrue(strings.contains(spdyProtocol));
String httpProtocol = "http/1.1";
Assert.assertTrue(strings.contains(httpProtocol));
Assert.assertTrue(strings.indexOf(spdyProtocol) < strings.indexOf(httpProtocol));
return httpProtocol;
}
});
@ -198,14 +196,9 @@ public class ProtocolNegotiationTest
@Test
public void testServerAdvertisingSPDYAndHTTPSpeaksDefaultProtocolWhenNPNMissing() throws Exception
{
InetSocketAddress address = startServer(new SPDYServerConnector(null, newSslContextFactory())
{
@Override
protected AsyncConnectionFactory getDefaultAsyncConnectionFactory()
{
return new ServerHTTPAsyncConnectionFactory(connector);
}
});
SPDYServerConnector connector = new SPDYServerConnector(null, newSslContextFactory());
connector.setDefaultAsyncConnectionFactory(new ServerHTTPAsyncConnectionFactory(connector));
InetSocketAddress address = startServer(connector);
connector.putAsyncConnectionFactory("http/1.1", new ServerHTTPAsyncConnectionFactory(connector));
SslContextFactory sslContextFactory = newSslContextFactory();

View File

@ -16,7 +16,6 @@ import org.eclipse.jetty.spdy.SPDYServerConnector;
import org.eclipse.jetty.spdy.api.DataInfo;
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.api.Session;
import org.eclipse.jetty.spdy.api.SessionFrameListener;
import org.eclipse.jetty.spdy.api.Stream;
@ -28,19 +27,12 @@ import org.junit.Test;
public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
{
@Override
protected SPDYServerConnector newHTTPSPDYServerConnector()
protected SPDYServerConnector newHTTPSPDYServerConnector(short version)
{
return new HTTPSPDYServerConnector()
{
private final AsyncConnectionFactory defaultAsyncConnectionFactory =
new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), getScheduler(), this, new ReferrerPushStrategy());
@Override
protected AsyncConnectionFactory getDefaultAsyncConnectionFactory()
{
return defaultAsyncConnectionFactory;
}
};
SPDYServerConnector connector = super.newHTTPSPDYServerConnector(version);
AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version, connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, new ReferrerPushStrategy());
connector.setDefaultAsyncConnectionFactory(defaultFactory);
return connector;
}
@Test
@ -363,7 +355,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
Assert.assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
}
@Test
public void testRequestWithIfModifiedSinceHeaderPreventsPush() throws Exception
{

View File

@ -16,6 +16,7 @@
package org.eclipse.jetty.spdy.http;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@ -40,6 +41,7 @@ import org.eclipse.jetty.spdy.api.BytesDataInfo;
import org.eclipse.jetty.spdy.api.DataInfo;
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.api.Session;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
@ -48,14 +50,19 @@ import org.eclipse.jetty.spdy.api.SynInfo;
import org.junit.Assert;
import org.junit.Test;
public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
public class ServerHTTPSPDYv2Test extends AbstractHTTPSPDYTest
{
protected short version()
{
return SPDY.V2;
}
@Test
public void testSimpleGET() throws Exception
{
final String path = "/foo";
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -71,11 +78,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "GET");
headers.put("url", path);
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
headers.put(HTTPSPDYHeader.URI.name(version()), path);
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
{
@ -84,7 +91,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
Assert.assertTrue(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
});
@ -99,7 +106,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
final String query = "p=1";
final String uri = path + "?" + query;
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -115,11 +122,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "GET");
headers.put("url", uri);
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
headers.put(HTTPSPDYHeader.URI.name(version()), uri);
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
{
@ -128,7 +135,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
Assert.assertTrue(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
});
@ -141,7 +148,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
final String path = "/foo";
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -156,11 +163,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "HEAD");
headers.put("url", path);
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "HEAD");
headers.put(HTTPSPDYHeader.URI.name(version()), path);
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
{
@ -169,7 +176,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
Assert.assertTrue(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
});
@ -183,7 +190,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
final String path = "/foo";
final String data = "a=1&b=2";
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -206,11 +213,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "POST");
headers.put("url", path);
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST");
headers.put(HTTPSPDYHeader.URI.name(version()), path);
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
headers.put("content-type", "application/x-www-form-urlencoded");
final CountDownLatch replyLatch = new CountDownLatch(1);
Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
@ -220,7 +227,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
Assert.assertTrue(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
}).get(5, TimeUnit.SECONDS);
@ -237,7 +244,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
final String data1 = "a=1&";
final String data2 = "b=2";
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -252,11 +259,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "POST");
headers.put("url", path);
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST");
headers.put(HTTPSPDYHeader.URI.name(version()), path);
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
headers.put("content-type", "application/x-www-form-urlencoded");
final CountDownLatch replyLatch = new CountDownLatch(1);
Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
@ -266,7 +273,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
Assert.assertTrue(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
}).get(5, TimeUnit.SECONDS);
@ -286,7 +293,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
final String data1 = "a=1&";
final String data2 = "b=2";
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -301,11 +308,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "POST");
headers.put("url", path);
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST");
headers.put(HTTPSPDYHeader.URI.name(version()), path);
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
headers.put("content-type", "application/x-www-form-urlencoded");
final CountDownLatch replyLatch = new CountDownLatch(1);
Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
@ -315,7 +322,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
Assert.assertTrue(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.toString(), replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.toString(), replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
}).get(5, TimeUnit.SECONDS);
@ -332,7 +339,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
final String data = "0123456789ABCDEF";
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -347,11 +354,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "GET");
headers.put("url", "/foo");
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
@ -361,7 +368,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
Assert.assertFalse(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
@ -383,7 +390,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
final char data = 'x';
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -398,11 +405,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "GET");
headers.put("url", "/foo");
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
@ -412,7 +419,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
Assert.assertFalse(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
@ -437,7 +444,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
final String data1 = "0123456789ABCDEF";
final String data2 = "FEDCBA9876543210";
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -454,11 +461,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "GET");
headers.put("url", "/foo");
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(2);
session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
@ -472,7 +479,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
Assert.assertEquals(1, replyFrames.incrementAndGet());
Assert.assertFalse(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
@ -499,7 +506,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
final byte[] data = new byte[128 * 1024];
Arrays.fill(data, (byte)'x');
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -514,11 +521,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "GET");
headers.put("url", "/foo");
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
@ -530,7 +537,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
Assert.assertFalse(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
@ -556,7 +563,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
final byte[] data = new byte[128 * 1024];
Arrays.fill(data, (byte)'y');
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -572,11 +579,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "GET");
headers.put("url", "/foo");
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
@ -588,7 +595,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
Assert.assertFalse(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
@ -613,7 +620,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
final String data = "0123456789ABCDEF";
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -630,11 +637,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "GET");
headers.put("url", "/foo");
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
@ -646,7 +653,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
Assert.assertFalse(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
@ -674,7 +681,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
final String data1 = "0123456789ABCDEF";
final String data2 = "FEDCBA9876543210";
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -693,11 +700,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "GET");
headers.put("url", "/foo");
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
@ -709,7 +716,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
Assert.assertFalse(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
@ -736,7 +743,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
final String suffix = "/redirect";
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -751,11 +758,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "GET");
headers.put("url", "/foo");
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
{
@ -767,7 +774,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
Assert.assertEquals(1, replies.incrementAndGet());
Assert.assertTrue(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("302"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("302"));
Assert.assertTrue(replyHeaders.get("location").value().endsWith(suffix));
replyLatch.countDown();
}
@ -780,7 +787,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
public void testGETWithSendError() throws Exception
{
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -793,11 +800,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "GET");
headers.put("url", "/foo");
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
@ -810,7 +817,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
Assert.assertEquals(1, replies.incrementAndGet());
Assert.assertFalse(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("404"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("404"));
replyLatch.countDown();
}
@ -829,7 +836,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
@Test
public void testGETWithException() throws Exception
{
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -840,11 +847,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "GET");
headers.put("url", "/foo");
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
{
@ -856,7 +863,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
Assert.assertEquals(1, replies.incrementAndGet());
Assert.assertTrue(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("500"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("500"));
replyLatch.countDown();
}
});
@ -869,7 +876,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
final String pangram1 = "the quick brown fox jumps over the lazy dog";
final String pangram2 = "qualche vago ione tipo zolfo, bromo, sodio";
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -887,11 +894,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "GET");
headers.put("url", "/foo");
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(2);
session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
@ -905,7 +912,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
Assert.assertEquals(1, replyFrames.incrementAndGet());
Assert.assertFalse(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
Assert.assertTrue(replyHeaders.get("extra").value().contains("X"));
replyLatch.countDown();
}
@ -933,11 +940,30 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}
@Test
public void testGETWithMediumContentByPassed() throws Exception
public void testGETWithMediumContentAsInputStreamByPassed() throws Exception
{
byte[] data = new byte[2048];
testGETWithContentByPassed(new ByteArrayInputStream(data), data.length);
}
@Test
public void testGETWithBigContentAsInputStreamByPassed() throws Exception
{
byte[] data = new byte[128 * 1024];
testGETWithContentByPassed(new ByteArrayInputStream(data), data.length);
}
@Test
public void testGETWithMediumContentAsBufferByPassed() throws Exception
{
byte[] data = new byte[2048];
testGETWithContentByPassed(new ByteArrayBuffer(data), data.length);
}
private void testGETWithContentByPassed(final Object content, final int length) throws Exception
{
final byte[] data = new byte[2048];
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -947,23 +973,23 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
// We use this trick that's present in Jetty code: if we add a request attribute
// called "org.eclipse.jetty.server.sendContent", then it will trigger the
// content bypass that we want to test
request.setAttribute("org.eclipse.jetty.server.sendContent", new ByteArrayBuffer(data));
request.setAttribute("org.eclipse.jetty.server.sendContent", content);
handlerLatch.countDown();
}
}), null);
Headers headers = new Headers();
headers.put("method", "GET");
headers.put("url", "/foo");
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
{
private final AtomicInteger replyFrames = new AtomicInteger();
private final AtomicInteger dataFrames = new AtomicInteger();
private final AtomicInteger contentLength = new AtomicInteger();
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
@ -971,17 +997,19 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
Assert.assertEquals(1, replyFrames.incrementAndGet());
Assert.assertFalse(replyInfo.isClose());
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
@Override
public void onData(Stream stream, DataInfo dataInfo)
{
Assert.assertEquals(1, dataFrames.incrementAndGet());
Assert.assertTrue(dataInfo.isClose());
Assert.assertArrayEquals(data, dataInfo.asBytes(true));
dataLatch.countDown();
contentLength.addAndGet(dataInfo.asBytes(true).length);
if (dataInfo.isClose())
{
Assert.assertEquals(length, contentLength.get());
dataLatch.countDown();
}
}
});
Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
@ -994,7 +1022,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
final byte[] data = new byte[2000];
final CountDownLatch latch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -1030,11 +1058,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "POST");
headers.put("url", "/foo");
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST");
headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
{
@ -1042,7 +1070,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
}).get(5, TimeUnit.SECONDS);
@ -1057,7 +1085,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
final byte[] data = new byte[2000];
final CountDownLatch latch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -1093,11 +1121,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "POST");
headers.put("url", "/foo");
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST");
headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
{
@ -1105,7 +1133,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
replyLatch.countDown();
}
}).get(5, TimeUnit.SECONDS);
@ -1121,7 +1149,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
final byte[] data = new byte[1000];
final CountDownLatch latch = new CountDownLatch(1);
Session session = startClient(startHTTPServer(new AbstractHandler()
Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
@ -1166,11 +1194,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
}), null);
Headers headers = new Headers();
headers.put("method", "POST");
headers.put("url", "/foo");
headers.put("version", "HTTP/1.1");
headers.put("scheme", "http");
headers.put("host", "localhost:" + connector.getLocalPort());
headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST");
headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
final CountDownLatch responseLatch = new CountDownLatch(2);
Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
{
@ -1178,7 +1206,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Headers replyHeaders = replyInfo.getHeaders();
Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
responseLatch.countDown();
}

View File

@ -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.http;
import org.eclipse.jetty.spdy.api.SPDY;
public class ServerHTTPSPDYv3Test extends ServerHTTPSPDYv2Test
{
@Override
protected short version()
{
return SPDY.V3;
}
}

View File

@ -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;
import org.eclipse.jetty.spdy.api.SPDY;
public class FlowControlStrategyFactory
{
private FlowControlStrategyFactory()
{
}
public static FlowControlStrategy newFlowControlStrategy(short version)
{
switch (version)
{
case SPDY.V2:
return new FlowControlStrategy.None();
case SPDY.V3:
return new SPDYv3FlowControlStrategy();
default:
throw new IllegalStateException();
}
}
}

View File

@ -122,7 +122,8 @@ public class SPDYAsyncConnection extends AbstractConnection implements AsyncConn
catch (Exception x)
{
close(false);
handler.failed(x);
handler.failed(context, x);
return -1;
}
finally
{
@ -222,6 +223,7 @@ public class SPDYAsyncConnection extends AbstractConnection implements AsyncConn
@Override
public void onIdleExpired(long idleForMs)
{
logger.debug("Idle timeout expired for {}", getEndPoint());
session.goAway();
}

View File

@ -38,7 +38,6 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
@ -64,7 +63,8 @@ public class SPDYClient
private final short version;
private final Factory factory;
private SocketAddress bindAddress;
private long maxIdleTime;
private long maxIdleTime = -1;
private volatile int initialWindowSize = 65536;
protected SPDYClient(short version, Factory factory)
{
@ -119,6 +119,16 @@ public class SPDYClient
this.maxIdleTime = maxIdleTime;
}
public int getInitialWindowSize()
{
return initialWindowSize;
}
public void setInitialWindowSize(int initialWindowSize)
{
this.initialWindowSize = initialWindowSize;
}
protected String selectProtocol(List<String> serverProtocols)
{
if (serverProtocols == null)
@ -173,6 +183,11 @@ public class SPDYClient
return engine;
}
protected FlowControlStrategy newFlowControlStrategy()
{
return FlowControlStrategyFactory.newFlowControlStrategy(version);
}
public static class Factory extends AggregateLifeCycle
{
private final Map<String, AsyncConnectionFactory> factories = new ConcurrentHashMap<>();
@ -314,7 +329,7 @@ public class SPDYClient
}
@Override
public AsyncConnection newConnection(final SocketChannel channel, AsyncEndPoint endPoint, Object attachment)
public AsyncConnection newConnection(final SocketChannel channel, AsyncEndPoint endPoint, final Object attachment)
{
SessionPromise sessionPromise = (SessionPromise)attachment;
final SPDYClient client = sessionPromise.client;
@ -323,31 +338,18 @@ public class SPDYClient
{
if (sslContextFactory != null)
{
final AtomicReference<AsyncEndPoint> sslEndPointRef = new AtomicReference<>();
final AtomicReference<Object> attachmentRef = new AtomicReference<>(attachment);
SSLEngine engine = client.newSSLEngine(sslContextFactory, channel);
final SSLEngine engine = client.newSSLEngine(sslContextFactory, channel);
SslConnection sslConnection = new SslConnection(engine, endPoint)
{
@Override
public void onClose()
{
sslEndPointRef.set(null);
attachmentRef.set(null);
NextProtoNego.remove(engine);
super.onClose();
}
};
endPoint.setConnection(sslConnection);
AsyncEndPoint sslEndPoint = sslConnection.getSslEndPoint();
sslEndPointRef.set(sslEndPoint);
// Instances of the ClientProvider inner class strong reference the
// SslEndPoint (via lexical scoping), which strong references the SSLEngine.
// Since NextProtoNego stores in a WeakHashMap the SSLEngine as key
// and this instance as value, we are in the situation where the value
// of a WeakHashMap refers indirectly to the key, which is bad because
// the entry will never be removed from the WeakHashMap.
// We use AtomicReferences to be captured via lexical scoping,
// and we null them out above when the connection is closed.
final AsyncEndPoint sslEndPoint = sslConnection.getSslEndPoint();
NextProtoNego.put(engine, new NextProtoNego.ClientProvider()
{
@Override
@ -361,8 +363,7 @@ public class SPDYClient
{
// Server does not support NPN, but this is a SPDY client, so hardcode SPDY
ClientSPDYAsyncConnectionFactory connectionFactory = new ClientSPDYAsyncConnectionFactory();
AsyncEndPoint sslEndPoint = sslEndPointRef.get();
AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, attachmentRef.get());
AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, attachment);
sslEndPoint.setConnection(connection);
}
@ -374,8 +375,7 @@ public class SPDYClient
return null;
AsyncConnectionFactory connectionFactory = client.getAsyncConnectionFactory(protocol);
AsyncEndPoint sslEndPoint = sslEndPointRef.get();
AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, attachmentRef.get());
AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, attachment);
sslEndPoint.setConnection(connection);
return protocol;
}
@ -398,7 +398,7 @@ public class SPDYClient
}
catch (RuntimeException x)
{
sessionPromise.failed(x);
sessionPromise.failed(null,x);
throw x;
}
}
@ -435,7 +435,8 @@ public class SPDYClient
public AsyncConnection newAsyncConnection(SocketChannel channel, AsyncEndPoint endPoint, Object attachment)
{
SessionPromise sessionPromise = (SessionPromise)attachment;
Factory factory = sessionPromise.client.factory;
SPDYClient client = sessionPromise.client;
Factory factory = client.factory;
CompressionFactory compressionFactory = new StandardCompressionFactory();
Parser parser = new Parser(compressionFactory.newDecompressor());
@ -444,7 +445,10 @@ public class SPDYClient
SPDYAsyncConnection connection = new ClientSPDYAsyncConnection(endPoint, factory.bufferPool, parser, factory);
endPoint.setConnection(connection);
StandardSession session = new StandardSession(sessionPromise.client.version, factory.bufferPool, factory.threadPool, factory.scheduler, connection, connection, 1, sessionPromise.listener, generator);
FlowControlStrategy flowControlStrategy = client.newFlowControlStrategy();
StandardSession session = new StandardSession(client.version, factory.bufferPool, factory.threadPool, factory.scheduler, connection, connection, 1, sessionPromise.listener, generator, flowControlStrategy);
session.setWindowSize(client.getInitialWindowSize());
parser.addListener(session);
sessionPromise.completed(session);
connection.setSession(session);

View File

@ -27,9 +27,9 @@ import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
@ -54,10 +54,12 @@ public class SPDYServerConnector extends SelectChannelConnector
private final Map<String, AsyncConnectionFactory> factories = new LinkedHashMap<>();
private final Queue<Session> sessions = new ConcurrentLinkedQueue<>();
private final ByteBufferPool bufferPool = new StandardByteBufferPool();
private final Executor executor = new LazyExecutor();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final ServerSessionFrameListener listener;
private final SslContextFactory sslContextFactory;
private AsyncConnectionFactory defaultConnectionFactory;
private volatile AsyncConnectionFactory defaultConnectionFactory;
private volatile int initialWindowSize = 65536;
public SPDYServerConnector(ServerSessionFrameListener listener)
{
@ -70,6 +72,9 @@ public class SPDYServerConnector extends SelectChannelConnector
this.sslContextFactory = sslContextFactory;
if (sslContextFactory != null)
addBean(sslContextFactory);
putAsyncConnectionFactory("spdy/3", new ServerSPDYAsyncConnectionFactory(SPDY.V3, bufferPool, executor, scheduler, listener));
putAsyncConnectionFactory("spdy/2", new ServerSPDYAsyncConnectionFactory(SPDY.V2, bufferPool, executor, scheduler, listener));
setDefaultAsyncConnectionFactory(getAsyncConnectionFactory("spdy/2"));
}
public ByteBufferPool getByteBufferPool()
@ -79,17 +84,7 @@ public class SPDYServerConnector extends SelectChannelConnector
public Executor getExecutor()
{
final ThreadPool threadPool = getThreadPool();
if (threadPool instanceof Executor)
return (Executor)threadPool;
return new Executor()
{
@Override
public void execute(Runnable command)
{
threadPool.dispatch(command);
}
};
return executor;
}
public ScheduledExecutorService getScheduler()
@ -97,6 +92,11 @@ public class SPDYServerConnector extends SelectChannelConnector
return scheduler;
}
public ServerSessionFrameListener getServerSessionFrameListener()
{
return listener;
}
public SslContextFactory getSslContextFactory()
{
return sslContextFactory;
@ -106,8 +106,6 @@ public class SPDYServerConnector extends SelectChannelConnector
protected void doStart() throws Exception
{
super.doStart();
defaultConnectionFactory = new ServerSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), scheduler, listener);
putAsyncConnectionFactory("spdy/2", defaultConnectionFactory);
logger.info("SPDY support is experimental. Please report feedback at jetty-dev@eclipse.org");
}
@ -166,46 +164,39 @@ public class SPDYServerConnector extends SelectChannelConnector
}
}
protected AsyncConnectionFactory getDefaultAsyncConnectionFactory()
public AsyncConnectionFactory getDefaultAsyncConnectionFactory()
{
return defaultConnectionFactory;
}
public void setDefaultAsyncConnectionFactory(AsyncConnectionFactory defaultConnectionFactory)
{
this.defaultConnectionFactory = defaultConnectionFactory;
}
@Override
protected AsyncConnection newConnection(final SocketChannel channel, AsyncEndPoint endPoint)
{
if (sslContextFactory != null)
{
SSLEngine engine = newSSLEngine(sslContextFactory, channel);
final AtomicReference<AsyncEndPoint> sslEndPointRef = new AtomicReference<>();
final SSLEngine engine = newSSLEngine(sslContextFactory, channel);
SslConnection sslConnection = new SslConnection(engine, endPoint)
{
@Override
public void onClose()
{
sslEndPointRef.set(null);
NextProtoNego.remove(engine);
super.onClose();
}
};
endPoint.setConnection(sslConnection);
AsyncEndPoint sslEndPoint = sslConnection.getSslEndPoint();
sslEndPointRef.set(sslEndPoint);
// Instances of the ServerProvider inner class strong reference the
// SslEndPoint (via lexical scoping), which strong references the SSLEngine.
// Since NextProtoNego stores in a WeakHashMap the SSLEngine as key
// and this instance as value, we are in the situation where the value
// of a WeakHashMap refers indirectly to the key, which is bad because
// the entry will never be removed from the WeakHashMap.
// We use AtomicReferences to be captured via lexical scoping,
// and we null them out above when the connection is closed.
final AsyncEndPoint sslEndPoint = sslConnection.getSslEndPoint();
NextProtoNego.put(engine, new NextProtoNego.ServerProvider()
{
@Override
public void unsupported()
{
AsyncConnectionFactory connectionFactory = getDefaultAsyncConnectionFactory();
AsyncEndPoint sslEndPoint = sslEndPointRef.get();
AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, SPDYServerConnector.this);
sslEndPoint.setConnection(connection);
}
@ -220,7 +211,6 @@ public class SPDYServerConnector extends SelectChannelConnector
public void protocolSelected(String protocol)
{
AsyncConnectionFactory connectionFactory = getAsyncConnectionFactory(protocol);
AsyncEndPoint sslEndPoint = sslEndPointRef.get();
AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, SPDYServerConnector.this);
sslEndPoint.setConnection(connection);
}
@ -242,6 +232,11 @@ public class SPDYServerConnector extends SelectChannelConnector
}
}
protected FlowControlStrategy newFlowControlStrategy(short version)
{
return FlowControlStrategyFactory.newFlowControlStrategy(version);
}
protected SSLEngine newSSLEngine(SslContextFactory sslContextFactory, SocketChannel channel)
{
String peerHost = channel.socket().getInetAddress().getHostAddress();
@ -287,4 +282,26 @@ public class SPDYServerConnector extends SelectChannelConnector
{
return Collections.unmodifiableCollection(sessions);
}
public int getInitialWindowSize()
{
return initialWindowSize;
}
public void setInitialWindowSize(int initialWindowSize)
{
this.initialWindowSize = initialWindowSize;
}
private class LazyExecutor implements Executor
{
@Override
public void execute(Runnable command)
{
ThreadPool threadPool = getThreadPool();
if (threadPool == null)
throw new RejectedExecutionException();
threadPool.dispatch(command);
}
}
}

View File

@ -50,6 +50,11 @@ public class ServerSPDYAsyncConnectionFactory implements AsyncConnectionFactory
this.listener = listener;
}
public short getVersion()
{
return version;
}
@Override
public AsyncConnection newAsyncConnection(SocketChannel channel, AsyncEndPoint endPoint, Object attachment)
{
@ -57,16 +62,16 @@ public class ServerSPDYAsyncConnectionFactory implements AsyncConnectionFactory
Parser parser = new Parser(compressionFactory.newDecompressor());
Generator generator = new Generator(bufferPool, compressionFactory.newCompressor());
ServerSessionFrameListener listener = this.listener;
if (listener == null)
listener = newServerSessionFrameListener(endPoint, attachment);
SPDYServerConnector connector = (SPDYServerConnector)attachment;
ServerSessionFrameListener listener = provideServerSessionFrameListener(endPoint, attachment);
SPDYAsyncConnection connection = new ServerSPDYAsyncConnection(endPoint, bufferPool, parser, listener, connector);
endPoint.setConnection(connection);
final StandardSession session = new StandardSession(version, bufferPool, threadPool, scheduler, connection, connection, 2, listener, generator);
FlowControlStrategy flowControlStrategy = connector.newFlowControlStrategy(version);
StandardSession session = new StandardSession(version, bufferPool, threadPool, scheduler, connection, connection, 2, listener, generator, flowControlStrategy);
session.setWindowSize(connector.getInitialWindowSize());
parser.addListener(session);
connection.setSession(session);
@ -75,7 +80,7 @@ public class ServerSPDYAsyncConnectionFactory implements AsyncConnectionFactory
return connection;
}
protected ServerSessionFrameListener newServerSessionFrameListener(AsyncEndPoint endPoint, Object attachment)
protected ServerSessionFrameListener provideServerSessionFrameListener(AsyncEndPoint endPoint, Object attachment)
{
return listener;
}

View File

@ -52,9 +52,17 @@ public abstract class AbstractTest
protected SPDYServerConnector connector;
protected InetSocketAddress startServer(ServerSessionFrameListener listener) throws Exception
{
return startServer(SPDY.V2, listener);
}
protected InetSocketAddress startServer(short version, ServerSessionFrameListener listener) throws Exception
{
if (connector == null)
connector = newSPDYServerConnector(listener);
if (listener == null)
listener = connector.getServerSessionFrameListener();
connector.setDefaultAsyncConnectionFactory(new ServerSPDYAsyncConnectionFactory(version, connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), listener));
connector.setPort(0);
server = new Server();
server.addConnector(connector);
@ -68,6 +76,11 @@ public abstract class AbstractTest
}
protected Session startClient(InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception
{
return startClient(SPDY.V2, socketAddress, listener);
}
protected Session startClient(short version, InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception
{
if (clientFactory == null)
{
@ -76,7 +89,7 @@ public abstract class AbstractTest
clientFactory = newSPDYClientFactory(threadPool);
clientFactory.start();
}
return clientFactory.newSPDYClient(SPDY.V2).connect(socketAddress, listener).get(5, TimeUnit.SECONDS);
return clientFactory.newSPDYClient(version).connect(socketAddress, listener).get(5, TimeUnit.SECONDS);
}
protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)

View File

@ -38,7 +38,6 @@ import org.eclipse.jetty.spdy.api.StringDataInfo;
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
import org.eclipse.jetty.spdy.frames.ControlFrame;
import org.eclipse.jetty.spdy.frames.DataFrame;
import org.eclipse.jetty.spdy.frames.GoAwayFrame;
import org.eclipse.jetty.spdy.frames.RstStreamFrame;
import org.eclipse.jetty.spdy.frames.SynReplyFrame;
@ -145,14 +144,12 @@ public class ClosedStreamTest extends AbstractTest
public void onReply(Stream stream, ReplyInfo replyInfo)
{
replyReceivedLatch.countDown();
super.onReply(stream,replyInfo);
}
@Override
public void onData(Stream stream, DataInfo dataInfo)
{
clientReceivedDataLatch.countDown();
super.onData(stream,dataInfo);
}
}).get();
assertThat("reply has been received by client",replyReceivedLatch.await(5,TimeUnit.SECONDS),is(true));
@ -204,7 +201,6 @@ public class ClosedStreamTest extends AbstractTest
public void onData(Stream stream, DataInfo dataInfo)
{
serverDataReceivedLatch.countDown();
super.onData(stream,dataInfo);
}
};
}
@ -217,7 +213,7 @@ public class ClosedStreamTest extends AbstractTest
final Generator generator = new Generator(new StandardByteBufferPool(),new StandardCompressionFactory().newCompressor());
int streamId = 1;
ByteBuffer synData = generator.control(new SynStreamFrame(version,SynInfo.FLAG_CLOSE, streamId,0,(byte)0,new Headers()));
ByteBuffer synData = generator.control(new SynStreamFrame(version,SynInfo.FLAG_CLOSE, streamId,0,(byte)0,(short)0,new Headers()));
final SocketChannel socketChannel = SocketChannel.open(startServer);
socketChannel.write(synData);
@ -250,13 +246,6 @@ public class ClosedStreamTest extends AbstractTest
{
clientResetReceivedLatch.countDown();
}
super.onControlFrame(frame);
}
@Override
public void onDataFrame(DataFrame frame, ByteBuffer data)
{
super.onDataFrame(frame,data);
}
});
ByteBuffer response = ByteBuffer.allocate(28);
@ -272,7 +261,7 @@ public class ClosedStreamTest extends AbstractTest
Assert.assertThat(buffer.hasRemaining(), is(false));
assertThat("GoAway frame is received by server", goAwayReceivedLatch.await(5,TimeUnit.SECONDS), is(true));
socketChannel.close();
}
}

View File

@ -25,9 +25,11 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
import org.eclipse.jetty.spdy.api.BytesDataInfo;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.api.SPDYException;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.SessionFrameListener;
@ -40,6 +42,9 @@ import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
import org.junit.Assert;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public class FlowControlTest extends AbstractTest
{
@Test
@ -53,7 +58,7 @@ public class FlowControlTest extends AbstractTest
final AtomicReference<DataInfo> dataInfoRef = new AtomicReference<>();
final CountDownLatch dataLatch = new CountDownLatch(2);
final CountDownLatch settingsLatch = new CountDownLatch(1);
Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()
Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
@ -112,7 +117,7 @@ public class FlowControlTest extends AbstractTest
final int windowSize = 1536;
final int length = 5 * windowSize;
final CountDownLatch settingsLatch = new CountDownLatch(1);
Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()
Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter()
{
@Override
public void onSettings(Session session, SettingsInfo settingsInfo)
@ -183,43 +188,22 @@ public class FlowControlTest extends AbstractTest
});
DataInfo dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
// Check that we are flow control stalled
expectException(TimeoutException.class, new Callable<DataInfo>()
{
@Override
public DataInfo call() throws Exception
{
return exchanger.exchange(null, 1, TimeUnit.SECONDS);
}
});
checkThatWeAreFlowControlStalled(exchanger);
Assert.assertEquals(windowSize, dataInfo.available());
Assert.assertEquals(0, dataInfo.consumed());
dataInfo.asByteBuffer(true);
dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
// Check that we are flow control stalled
expectException(TimeoutException.class, new Callable<DataInfo>()
{
@Override
public DataInfo call() throws Exception
{
return exchanger.exchange(null, 1, TimeUnit.SECONDS);
}
});
checkThatWeAreFlowControlStalled(exchanger);
Assert.assertEquals(0, dataInfo.available());
Assert.assertEquals(0, dataInfo.consumed());
dataInfo.consume(dataInfo.length());
dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
// Check that we are flow control stalled
expectException(TimeoutException.class, new Callable<DataInfo>()
{
@Override
public DataInfo call() throws Exception
{
return exchanger.exchange(null, 1, TimeUnit.SECONDS);
}
});
checkThatWeAreFlowControlStalled(exchanger);
Assert.assertEquals(dataInfo.length() / 2, dataInfo.consumed());
dataInfo.asByteBuffer(true);
@ -236,7 +220,7 @@ public class FlowControlTest extends AbstractTest
final int windowSize = 1536;
final Exchanger<DataInfo> exchanger = new Exchanger<>();
final CountDownLatch settingsLatch = new CountDownLatch(1);
Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()
Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter()
{
@Override
public void onConnect(Session session)
@ -312,43 +296,22 @@ public class FlowControlTest extends AbstractTest
stream.data(new BytesDataInfo(new byte[length], true));
DataInfo dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
// Check that we are flow control stalled
expectException(TimeoutException.class, new Callable<DataInfo>()
{
@Override
public DataInfo call() throws Exception
{
return exchanger.exchange(null, 1, TimeUnit.SECONDS);
}
});
checkThatWeAreFlowControlStalled(exchanger);
Assert.assertEquals(windowSize, dataInfo.available());
Assert.assertEquals(0, dataInfo.consumed());
dataInfo.asByteBuffer(true);
dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
// Check that we are flow control stalled
expectException(TimeoutException.class, new Callable<DataInfo>()
{
@Override
public DataInfo call() throws Exception
{
return exchanger.exchange(null, 1, TimeUnit.SECONDS);
}
});
checkThatWeAreFlowControlStalled(exchanger);
Assert.assertEquals(0, dataInfo.available());
Assert.assertEquals(0, dataInfo.consumed());
dataInfo.consume(dataInfo.length());
dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
// Check that we are flow control stalled
expectException(TimeoutException.class, new Callable<DataInfo>()
{
@Override
public DataInfo call() throws Exception
{
return exchanger.exchange(null, 1, TimeUnit.SECONDS);
}
});
checkThatWeAreFlowControlStalled(exchanger);
Assert.assertEquals(dataInfo.length() / 2, dataInfo.consumed());
dataInfo.asByteBuffer(true);
@ -364,7 +327,7 @@ public class FlowControlTest extends AbstractTest
{
final int windowSize = 1024;
final CountDownLatch settingsLatch = new CountDownLatch(1);
Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()
Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter()
{
@Override
public void onSettings(Session session, SettingsInfo settingsInfo)
@ -451,6 +414,64 @@ public class FlowControlTest extends AbstractTest
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void testSendBigFileWithoutFlowControl() throws Exception
{
testSendBigFile(SPDY.V2);
}
@Test
public void testSendBigFileWithFlowControl() throws Exception
{
testSendBigFile(SPDY.V3);
}
private void testSendBigFile(short version) throws Exception
{
final int dataSize = 1024 * 1024;
final ByteBufferDataInfo bigByteBufferDataInfo = new ByteBufferDataInfo(ByteBuffer.allocate(dataSize),false);
final CountDownLatch allDataReceivedLatch = new CountDownLatch(1);
Session session = startClient(version, startServer(version, new ServerSessionFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
{
stream.reply(new ReplyInfo(false));
stream.data(bigByteBufferDataInfo);
return null;
}
}),new SessionFrameListener.Adapter());
session.syn(new SynInfo(false),new StreamFrameListener.Adapter()
{
private int dataBytesReceived;
@Override
public void onData(Stream stream, DataInfo dataInfo)
{
dataBytesReceived = dataBytesReceived + dataInfo.length();
dataInfo.consume(dataInfo.length());
if (dataBytesReceived == dataSize)
allDataReceivedLatch.countDown();
}
});
assertThat("all data bytes have been received by the client", allDataReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
}
private void checkThatWeAreFlowControlStalled(final Exchanger<DataInfo> exchanger)
{
expectException(TimeoutException.class, new Callable<DataInfo>()
{
@Override
public DataInfo call() throws Exception
{
return exchanger.exchange(null, 1, TimeUnit.SECONDS);
}
});
}
private void expectException(Class<? extends Exception> exception, Callable<DataInfo> command)
{
try

View File

@ -20,7 +20,6 @@ import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.spdy.api.GoAwayInfo;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.SPDY;
@ -39,7 +38,6 @@ public class IdleTimeoutTest extends AbstractTest
@Test
public void testServerEnforcingIdleTimeout() throws Exception
{
server = new Server();
connector = newSPDYServerConnector(new ServerSessionFrameListener.Adapter()
{
@Override
@ -49,13 +47,11 @@ public class IdleTimeoutTest extends AbstractTest
return null;
}
});
server.addConnector(connector);
int maxIdleTime = 1000;
connector.setMaxIdleTime(maxIdleTime);
server.start();
final CountDownLatch latch = new CountDownLatch(1);
Session session = startClient(new InetSocketAddress("localhost", connector.getLocalPort()), new SessionFrameListener.Adapter()
Session session = startClient(startServer(null), new SessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayInfo goAwayInfo)
@ -72,15 +68,12 @@ public class IdleTimeoutTest extends AbstractTest
@Test
public void testServerEnforcingIdleTimeoutWithUnrespondedStream() throws Exception
{
server = new Server();
connector = newSPDYServerConnector(null);
server.addConnector(connector);
int maxIdleTime = 1000;
connector.setMaxIdleTime(maxIdleTime);
server.start();
final CountDownLatch latch = new CountDownLatch(1);
Session session = startClient(new InetSocketAddress("localhost", connector.getLocalPort()), new SessionFrameListener.Adapter()
Session session = startClient(startServer(null), new SessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayInfo goAwayInfo)
@ -99,7 +92,6 @@ public class IdleTimeoutTest extends AbstractTest
public void testServerNotEnforcingIdleTimeoutWithPendingStream() throws Exception
{
final int maxIdleTime = 1000;
server = new Server();
connector = newSPDYServerConnector(new ServerSessionFrameListener.Adapter()
{
@Override
@ -118,12 +110,10 @@ public class IdleTimeoutTest extends AbstractTest
}
}
});
server.addConnector(connector);
connector.setMaxIdleTime(maxIdleTime);
server.start();
final CountDownLatch latch = new CountDownLatch(1);
Session session = startClient(new InetSocketAddress("localhost", connector.getLocalPort()), new SessionFrameListener.Adapter()
Session session = startClient(startServer(null), new SessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayInfo goAwayInfo)

View File

@ -16,26 +16,47 @@
package org.eclipse.jetty.spdy;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.spdy.api.BytesDataInfo;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.GoAwayInfo;
import org.eclipse.jetty.spdy.api.Handler;
import org.eclipse.jetty.spdy.api.Headers;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.RstInfo;
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.SessionFrameListener;
import org.eclipse.jetty.spdy.api.SessionStatus;
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.api.StringDataInfo;
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
import org.eclipse.jetty.spdy.frames.ControlFrame;
import org.eclipse.jetty.spdy.frames.DataFrame;
import org.eclipse.jetty.spdy.frames.GoAwayFrame;
import org.eclipse.jetty.spdy.frames.RstStreamFrame;
import org.eclipse.jetty.spdy.frames.SynStreamFrame;
import org.eclipse.jetty.spdy.frames.WindowUpdateFrame;
import org.eclipse.jetty.spdy.generator.Generator;
import org.eclipse.jetty.spdy.parser.Parser;
import org.eclipse.jetty.spdy.parser.Parser.Listener;
import org.junit.Assert;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
@ -66,10 +87,10 @@ public class PushStreamTest extends AbstractTest
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
{
assertThat("streamId is even", stream.getId() % 2, is(0));
assertThat("stream is unidirectional", stream.isUnidirectional(), is(true));
assertThat("stream is closed", stream.isClosed(), is(true));
assertThat("stream has associated stream", stream.getAssociatedStream(), notNullValue());
assertThat("streamId is even",stream.getId() % 2,is(0));
assertThat("stream is unidirectional",stream.isUnidirectional(),is(true));
assertThat("stream is closed",stream.isClosed(),is(true));
assertThat("stream has associated stream",stream.getAssociatedStream(),notNullValue());
try
{
stream.reply(new ReplyInfo(false));
@ -85,10 +106,10 @@ public class PushStreamTest extends AbstractTest
}
});
Stream stream = clientSession.syn(new SynInfo(true), null).get();
assertThat("onSyn has been called", pushStreamLatch.await(5, TimeUnit.SECONDS), is(true));
Stream stream = clientSession.syn(new SynInfo(true),null).get();
assertThat("onSyn has been called",pushStreamLatch.await(5,TimeUnit.SECONDS),is(true));
Stream pushStream = pushStreamRef.get();
assertThat("main stream and associated stream are the same", stream, sameInstance(pushStream.getAssociatedStream()));
assertThat("main stream and associated stream are the same",stream,sameInstance(pushStream.getAssociatedStream()));
}
@Test
@ -221,7 +242,7 @@ public class PushStreamTest extends AbstractTest
stream.syn(new SynInfo(false),1,TimeUnit.SECONDS,new Handler.Adapter<Stream>()
{
@Override
public void failed(Throwable x)
public void failed(Stream stream, Throwable x)
{
pushStreamFailedLatch.countDown();
}
@ -321,6 +342,170 @@ public class PushStreamTest extends AbstractTest
return bytes;
}
@Test
public void testClientResetsStreamAfterPushSynDoesPreventSendingDataFramesWithFlowControl() throws Exception
{
final boolean flowControl = true;
testNoMoreFramesAreSentOnPushStreamAfterClientResetsThePushStream(flowControl);
}
@Test
public void testClientResetsStreamAfterPushSynDoesPreventSendingDataFramesWithoutFlowControl() throws Exception
{
final boolean flowControl = false;
testNoMoreFramesAreSentOnPushStreamAfterClientResetsThePushStream(flowControl);
}
private volatile boolean read = true;
private void testNoMoreFramesAreSentOnPushStreamAfterClientResetsThePushStream(final boolean flowControl) throws Exception, IOException, InterruptedException
{
final short version = SPDY.V3;
final AtomicBoolean unexpectedExceptionOccured = new AtomicBoolean(false);
final CountDownLatch resetReceivedLatch = new CountDownLatch(1);
final CountDownLatch allDataFramesReceivedLatch = new CountDownLatch(1);
final CountDownLatch goAwayReceivedLatch = new CountDownLatch(1);
final int dataSizeInBytes = 1024 * 256;
final byte[] transferBytes = createHugeByteArray(dataSizeInBytes);
InetSocketAddress serverAddress = startServer(new ServerSessionFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(final Stream stream, SynInfo synInfo)
{
new Thread(new Runnable()
{
@Override
public void run()
{
Stream pushStream=null;
try
{
stream.reply(new ReplyInfo(false));
pushStream = stream.syn(new SynInfo(false)).get();
resetReceivedLatch.await(5,TimeUnit.SECONDS);
}
catch (InterruptedException | ExecutionException e)
{
e.printStackTrace();
unexpectedExceptionOccured.set(true);
}
pushStream.data(new BytesDataInfo(transferBytes,true));
stream.data(new StringDataInfo("close",true));
}
}).start();
return null;
}
@Override
public void onRst(Session session, RstInfo rstInfo)
{
resetReceivedLatch.countDown();
}
@Override
public void onGoAway(Session session, GoAwayInfo goAwayInfo)
{
goAwayReceivedLatch.countDown();
}
}/*TODO, flowControl*/);
final SocketChannel channel = SocketChannel.open(serverAddress);
final Generator generator = new Generator(new StandardByteBufferPool(),new StandardCompressionFactory.StandardCompressor());
int streamId = 1;
ByteBuffer writeBuffer = generator.control(new SynStreamFrame(version,(byte)0,streamId,0,(byte)0,(short)0,new Headers()));
channel.write(writeBuffer);
assertThat("writeBuffer is fully written",writeBuffer.hasRemaining(), is(false));
final Parser parser = new Parser(new StandardCompressionFactory.StandardDecompressor());
parser.addListener(new Listener.Adapter()
{
int bytesRead = 0;
@Override
public void onControlFrame(ControlFrame frame)
{
if(frame instanceof SynStreamFrame){
int pushStreamId = ((SynStreamFrame)frame).getStreamId();
ByteBuffer writeBuffer = generator.control(new RstStreamFrame(version,pushStreamId,StreamStatus.CANCEL_STREAM.getCode(version)));
try
{
channel.write(writeBuffer);
}
catch (IOException e)
{
e.printStackTrace();
unexpectedExceptionOccured.set(true);
}
}
}
@Override
public void onDataFrame(DataFrame frame, ByteBuffer data)
{
if(frame.getStreamId() == 2)
bytesRead = bytesRead + frame.getLength();
if(bytesRead == dataSizeInBytes){
allDataFramesReceivedLatch.countDown();
return;
}
if (flowControl)
{
ByteBuffer writeBuffer = generator.control(new WindowUpdateFrame(version,frame.getStreamId(),frame.getLength()));
try
{
channel.write(writeBuffer);
}
catch (IOException e)
{
e.printStackTrace();
unexpectedExceptionOccured.set(true);
}
}
}
});
Thread reader = new Thread(new Runnable()
{
@Override
public void run()
{
ByteBuffer readBuffer = ByteBuffer.allocate(dataSizeInBytes*2);
while (read)
{
try
{
channel.read(readBuffer);
}
catch (IOException e)
{
e.printStackTrace();
unexpectedExceptionOccured.set(true);
}
readBuffer.flip();
parser.parse(readBuffer);
readBuffer.clear();
}
}
});
reader.start();
read = false;
assertThat("no unexpected exceptions occured", unexpectedExceptionOccured.get(), is(false));
assertThat("not all dataframes have been received as the pushstream has been reset by the client.",allDataFramesReceivedLatch.await(streamId,TimeUnit.SECONDS),is(false));
ByteBuffer buffer = generator.control(new GoAwayFrame(version, streamId, SessionStatus.OK.getCode()));
channel.write(buffer);
Assert.assertThat(buffer.hasRemaining(), is(false));
assertThat("GoAway frame is received by server", goAwayReceivedLatch.await(5,TimeUnit.SECONDS), is(true));
channel.shutdownOutput();
channel.close();
}
@Test
public void testOddEvenStreamIds() throws Exception
{
@ -367,6 +552,6 @@ public class PushStreamTest extends AbstractTest
private void assertThatNoExceptionOccured(final CountDownLatch exceptionCountDownLatch) throws InterruptedException
{
assertThat("No exception occured", exceptionCountDownLatch.await(1,TimeUnit.SECONDS),is(false));
assertThat("No exception occured",exceptionCountDownLatch.await(1,TimeUnit.SECONDS),is(false));
}
}

View File

@ -1,11 +1,5 @@
package org.eclipse.jetty.spdy;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@ -23,12 +17,18 @@ import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
public class ResetStreamTest extends AbstractTest
{
@Test
public void testResetStreamIsRemoved() throws Exception
{
Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()),null);
Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()/*TODO, true*/),null);
Stream stream = session.syn(new SynInfo(false),null).get(5,TimeUnit.SECONDS);
session.rst(new RstInfo(stream.getId(),StreamStatus.CANCEL_STREAM)).get(5,TimeUnit.SECONDS);
@ -169,7 +169,7 @@ public class ResetStreamTest extends AbstractTest
stream.data(new StringDataInfo("2nd dataframe",false),5L,TimeUnit.SECONDS,new Handler.Adapter<Void>()
{
@Override
public void failed(Throwable x)
public void failed(Void context, Throwable x)
{
failLatch.countDown();
}

View File

@ -44,7 +44,7 @@ public class UnsupportedVersionTest extends AbstractTest
}
});
SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, new Headers());
SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, new Headers());
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
ByteBuffer buffer = generator.control(frame);
// Replace the version byte with an unsupported version

View File

@ -31,6 +31,7 @@ import java.lang.reflect.Method;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -53,6 +54,9 @@ import java.util.Set;
*/
public class Main
{
private static final String START_LOG_FILENAME = "start.log";
private static final SimpleDateFormat START_LOG_ROLLOVER_DATEFORMAT = new SimpleDateFormat("yyyy_MM_dd-HHmmSSSSS.'" + START_LOG_FILENAME + "'");
private static final int EXIT_USAGE = 1;
private static final int ERR_LOGGING = -1;
private static final int ERR_INVOKE_MAIN = -2;
@ -71,7 +75,7 @@ public class Main
private String _jettyHome;
public static void main(String[] args)
public static void main(String[] args)
{
try
{
@ -107,7 +111,6 @@ public class Main
if (arg.length() > 6)
{
arguments.addAll(loadStartIni(new File(arg.substring(6))));
continue;
}
}
else if (arg.startsWith("--config="))
@ -152,7 +155,7 @@ public class Main
}
return ini_args;
}
public List<String> processCommandLine(List<String> arguments) throws Exception
{
// The XML Configuration Files to initialize with
@ -212,7 +215,9 @@ public class Main
File startDir = new File(System.getProperty("jetty.logs","logs"));
if (!startDir.exists() || !startDir.canWrite())
startDir = new File(".");
File startLog = new File(startDir,"start.log");
File startLog = new File(startDir, START_LOG_ROLLOVER_DATEFORMAT.format(new Date()));
if (!startLog.exists() && !startLog.createNewFile())
{
// Output about error is lost in majority of cases.
@ -231,7 +236,7 @@ public class Main
PrintStream logger = new PrintStream(new FileOutputStream(startLog,false));
System.setOut(logger);
System.setErr(logger);
System.out.println("Establishing start.log on " + new Date());
System.out.println("Establishing "+ START_LOG_FILENAME + " on " + new Date());
continue;
}
@ -475,7 +480,7 @@ public class Main
}
/* ------------------------------------------------------------ */
public void start(List<String> xmls) throws FileNotFoundException, IOException, InterruptedException
public void start(List<String> xmls) throws IOException, InterruptedException
{
// Setup Start / Stop Monitoring
int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
@ -528,7 +533,7 @@ public class Main
// Show all options with version information
if (_listOptions)
{
showAllOptionsWithVersions(classpath);
showAllOptionsWithVersions();
return;
}
@ -550,7 +555,7 @@ public class Main
if (_exec)
{
CommandLineBuilder cmd = buildCommandLine(classpath,configuredXmls);
ProcessBuilder pbuilder = new ProcessBuilder(cmd.getArgs());
final Process process = pbuilder.start();
Runtime.getRuntime().addShutdownHook(new Thread()
@ -562,13 +567,13 @@ public class Main
process.destroy();
}
});
copyInThread(process.getErrorStream(),System.err);
copyInThread(process.getInputStream(),System.out);
copyInThread(System.in,process.getOutputStream());
monitor.setProcess(process);
process.waitFor();
return;
}
@ -734,7 +739,7 @@ public class Main
return exe;
}
private void showAllOptionsWithVersions(Classpath classpath)
private void showAllOptionsWithVersions()
{
Set<String> sectionIds = _config.getSectionIds();
@ -1046,19 +1051,15 @@ public class Main
System.err.println(" java -jar start.jar --help # for more information");
System.exit(exit);
}
/**
* Convert a start.ini format file into an argument list.
*/
static List<String> loadStartIni(File ini)
{
File startIniFile = ini;
if (!startIniFile.exists())
if (!ini.exists())
{
if (ini != null)
{
System.err.println("Warning - can't find ini file: " + ini);
}
System.err.println("Warning - can't find ini file: " + ini);
// No start.ini found, skip load.
return Collections.emptyList();
}

View File

@ -99,7 +99,10 @@ public class Monitor extends Thread
new LineNumberReader(new InputStreamReader(socket.getInputStream()));
String key=lin.readLine();
if (!_key.equals(key))
{
System.err.println("Ignoring command with incorrect key");
continue;
}
String cmd=lin.readLine();
Config.debug("command=" + cmd);

View File

@ -121,8 +121,11 @@ public class ShutdownThread extends Thread
{
try
{
lifeCycle.stop();
LOG.debug("Stopped {}",lifeCycle);
if (lifeCycle.isStarted())
{
lifeCycle.stop();
LOG.debug("Stopped {}",lifeCycle);
}
}
catch (Exception ex)
{

View File

@ -76,7 +76,9 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
public final static String SERVER_CONFIG = "org.eclipse.jetty.webapp.configuration";
public final static String SERVER_SYS_CLASSES = "org.eclipse.jetty.webapp.systemClasses";
public final static String SERVER_SRV_CLASSES = "org.eclipse.jetty.webapp.serverClasses";
private String[] __dftProtectedTargets = {"/web-inf", "/meta-inf"};
private static String[] __dftConfigurationClasses =
{
"org.eclipse.jetty.webapp.WebInfConfiguration",
@ -151,6 +153,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
private boolean _configurationsSet=false;
private boolean _allowDuplicateFragmentNames = false;
private boolean _throwUnavailableOnStartupException = false;
private MetaData _metadata=new MetaData();
@ -172,6 +176,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
super(SESSIONS|SECURITY);
_scontext=new Context();
setErrorHandler(new ErrorPageErrorHandler());
setProtectedTargets(__dftProtectedTargets);
}
/* ------------------------------------------------------------ */
@ -186,6 +191,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
setContextPath(contextPath);
setWar(webApp);
setErrorHandler(new ErrorPageErrorHandler());
setProtectedTargets(__dftProtectedTargets);
}
/* ------------------------------------------------------------ */
@ -200,6 +206,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
_scontext=new Context();
setWar(webApp);
setErrorHandler(new ErrorPageErrorHandler());
setProtectedTargets(__dftProtectedTargets);
}
/* ------------------------------------------------------------ */
@ -216,6 +223,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
super(null, sessionHandler, securityHandler, servletHandler, errorHandler);
_scontext = new Context();
setErrorHandler(errorHandler != null ? errorHandler : new ErrorPageErrorHandler());
setProtectedTargets(__dftProtectedTargets);
}
/* ------------------------------------------------------------ */
@ -833,16 +841,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
}
}
/* ------------------------------------------------------------ */
@Override
protected boolean isProtectedTarget(String target)
{
while (target.startsWith("//"))
target=URIUtil.compactPath(target);
return StringUtil.startsWithIgnoreCase(target, "/web-inf") || StringUtil.startsWithIgnoreCase(target, "/meta-inf");
}
/* ------------------------------------------------------------ */
@Override

View File

@ -12,6 +12,7 @@
// ========================================================================
package org.eclipse.jetty.webapp;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@ -168,6 +169,16 @@ public class WebAppContextTest
}
@Test
public void testIsProtected() throws Exception
{
WebAppContext context = new WebAppContext();
assertTrue(context.isProtectedTarget("/web-inf/lib/foo.jar"));
assertTrue(context.isProtectedTarget("/meta-inf/readme.txt"));
assertFalse(context.isProtectedTarget("/something-else/web-inf"));
}
class ServletA extends GenericServlet
{
@Override

View File

@ -16,6 +16,7 @@ package org.eclipse.jetty.xml;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@ -25,20 +26,21 @@ import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.ArrayQueue;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.TypeUtil;
@ -46,6 +48,7 @@ import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlParser.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
@ -69,8 +72,14 @@ public class XmlConfiguration
private static final Class<?>[] __primitiveHolders =
{ Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class };
private static final Integer ZERO = new Integer(0);
private static final Class<?>[] __supportedCollections =
{ ArrayList.class,ArrayQueue.class,HashSet.class,Queue.class,List.class,Set.class,Collection.class,};
private static final Iterable<?> __factoryLoader;
private static final XmlParser __parser = initParser();
static
{
Iterable<?> loader=null;
@ -93,46 +102,41 @@ public class XmlConfiguration
}
/* ------------------------------------------------------------ */
private static XmlParser __parser;
private URL _url;
private XmlParser.Node _config;
private String _dtd;
private ConfigurationProcessor _processor;
private final Map<String, Object> _idMap = new HashMap<String, Object>();
private final Map<String, String> _propertyMap = new HashMap<String, String>();
/* ------------------------------------------------------------ */
private synchronized static void initParser() throws IOException
private synchronized static XmlParser initParser()
{
if (__parser != null)
return;
__parser = new XmlParser();
XmlParser parser = new XmlParser();
try
{
URL config60 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_6_0.dtd",true);
URL config76 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_7_6.dtd",true);
__parser.redirectEntity("configure.dtd",config76);
__parser.redirectEntity("configure_1_0.dtd",config60);
__parser.redirectEntity("configure_1_1.dtd",config60);
__parser.redirectEntity("configure_1_2.dtd",config60);
__parser.redirectEntity("configure_1_3.dtd",config60);
__parser.redirectEntity("configure_6_0.dtd",config60);
__parser.redirectEntity("configure_7_6.dtd",config76);
parser.redirectEntity("configure.dtd",config76);
parser.redirectEntity("configure_1_0.dtd",config60);
parser.redirectEntity("configure_1_1.dtd",config60);
parser.redirectEntity("configure_1_2.dtd",config60);
parser.redirectEntity("configure_1_3.dtd",config60);
parser.redirectEntity("configure_6_0.dtd",config60);
parser.redirectEntity("configure_7_6.dtd",config76);
__parser.redirectEntity("http://jetty.mortbay.org/configure.dtd",config76);
__parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",config76);
__parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",config76);
parser.redirectEntity("http://jetty.mortbay.org/configure.dtd",config76);
parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",config76);
parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",config76);
__parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN",config76);
__parser.redirectEntity("-//Jetty//Configure//EN",config76);
parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN",config76);
parser.redirectEntity("-//Jetty//Configure//EN",config76);
}
catch (ClassNotFoundException e)
{
LOG.warn(e.toString());
LOG.debug(e);
}
return parser;
}
/* ------------------------------------------------------------ */
@ -143,7 +147,6 @@ public class XmlConfiguration
*/
public XmlConfiguration(URL configuration) throws SAXException, IOException
{
initParser();
synchronized (__parser)
{
_url=configuration;
@ -163,7 +166,6 @@ public class XmlConfiguration
*/
public XmlConfiguration(String configuration) throws SAXException, IOException
{
initParser();
configuration = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n<!DOCTYPE Configure PUBLIC \"-//Mort Bay Consulting//DTD Configure 1.2//EN\" \"http://jetty.eclipse.org/configure_1_2.dtd\">"
+ configuration;
InputSource source = new InputSource(new StringReader(configuration));
@ -185,7 +187,6 @@ public class XmlConfiguration
*/
public XmlConfiguration(InputStream configuration) throws SAXException, IOException
{
initParser();
InputSource source = new InputSource(configuration);
synchronized (__parser)
{
@ -197,7 +198,6 @@ public class XmlConfiguration
/* ------------------------------------------------------------ */
private void setConfig(XmlParser.Node config)
{
_config=config;
if ("Configure".equals(config.getTag()))
{
_processor=new JettyXmlConfiguration();
@ -228,7 +228,7 @@ public class XmlConfiguration
{
throw new IllegalArgumentException("Unknown XML tag:"+config.getTag());
}
_processor.init(_url,_config,_idMap, _propertyMap);
_processor.init(_url,config,_idMap, _propertyMap);
}
@ -242,6 +242,7 @@ public class XmlConfiguration
/**
* @deprecated use {@link #getIdMap()}.put(...)
*/
@Deprecated
public void setIdMap(Map<String, Object> map)
{
_idMap.clear();
@ -252,6 +253,7 @@ public class XmlConfiguration
/**
* @deprecated use {@link #getProperties()}.putAll(...)
*/
@Deprecated
public void setProperties(Map<String, String> map)
{
_propertyMap.clear();
@ -311,7 +313,7 @@ public class XmlConfiguration
public Object configure(Object obj) throws Exception
{
// Check the class of the object
Class<?> oClass = (Class<?>)nodeClass(_config);
Class<?> oClass = nodeClass(_config);
if (oClass != null && !oClass.isInstance(obj))
{
String loaders = (oClass.getClassLoader()==obj.getClass().getClassLoader())?"":"Object Class and type Class are from different loaders.";
@ -324,7 +326,7 @@ public class XmlConfiguration
/* ------------------------------------------------------------ */
public Object configure() throws Exception
{
Class<?> oClass = (Class<?>)nodeClass(_config);
Class<?> oClass = nodeClass(_config);
String id = _config.getAttribute("id");
Object obj = id == null?null:_idMap.get(id);
@ -340,7 +342,7 @@ public class XmlConfiguration
}
/* ------------------------------------------------------------ */
private Class<?> nodeClass(XmlParser.Node node) throws ClassNotFoundException
private static Class<?> nodeClass(XmlParser.Node node) throws ClassNotFoundException
{
String className = node.getAttribute("class");
if (className == null)
@ -389,7 +391,7 @@ public class XmlConfiguration
else if ("Ref".equals(tag))
refObj(obj,node);
else if ("Property".equals(tag))
propertyObj(obj,node);
propertyObj(node);
else
throw new IllegalStateException("Unknown tag: " + tag);
}
@ -417,13 +419,13 @@ public class XmlConfiguration
Object[] arg =
{ value };
Class oClass = nodeClass(node);
Class<?> oClass = nodeClass(node);
if (oClass != null)
obj = null;
else
oClass = obj.getClass();
Class[] vClass =
Class<?>[] vClass =
{ Object.class };
if (value != null)
vClass[0] = value.getClass();
@ -455,7 +457,7 @@ public class XmlConfiguration
try
{
Field type = vClass[0].getField("TYPE");
vClass[0] = (Class)type.get(null);
vClass[0] = (Class<?>)type.get(null);
Method set = oClass.getMethod(name,vClass);
set.invoke(obj,arg);
return;
@ -497,7 +499,6 @@ public class XmlConfiguration
Method set = null;
for (int s = 0; sets != null && s < sets.length; s++)
{
Class<?>[] paramTypes = sets[s].getParameterTypes();
if (name.equals(sets[s].getName()) && paramTypes.length == 1)
{
@ -518,27 +519,18 @@ public class XmlConfiguration
LOG.ignore(e);
}
// Can we convert to a collection
if (paramTypes[0].isAssignableFrom(Collection.class) && value.getClass().isArray())
try
{
try
{
if (paramTypes[0].isAssignableFrom(Set.class))
sets[s].invoke(obj,new Object[]
{ new HashSet<Object>(Arrays.asList((Object[])value)) });
else
sets[s].invoke(obj,new Object[]
{ Arrays.asList((Object[])value) });
return;
}
catch (IllegalArgumentException e)
{
LOG.ignore(e);
}
catch (IllegalAccessException e)
{
LOG.ignore(e);
}
for (Class<?> c : __supportedCollections)
if (paramTypes[0].isAssignableFrom(c))
{
sets[s].invoke(obj,convertArrayToCollection(value,c));
return;
}
}
catch (IllegalAccessException e)
{
LOG.ignore(e);
}
}
}
@ -548,7 +540,7 @@ public class XmlConfiguration
{
try
{
Class sClass = set.getParameterTypes()[0];
Class<?> sClass = set.getParameterTypes()[0];
if (sClass.isPrimitive())
{
for (int t = 0; t < __primitives.length; t++)
@ -560,7 +552,7 @@ public class XmlConfiguration
}
}
}
Constructor cons = sClass.getConstructor(vClass);
Constructor<?> cons = sClass.getConstructor(vClass);
arg[0] = cons.newInstance(arg);
set.invoke(obj,arg);
return;
@ -583,6 +575,40 @@ public class XmlConfiguration
throw new NoSuchMethodException(oClass + "." + name + "(" + vClass[0] + ")");
}
/**
* @return a collection if compareValueToClass is a Set or List. null if that's not the case or value can't be converted to a Collection
*/
private static Collection<?> convertArrayToCollection(Object array, Class<?> collectionType)
{
Collection<?> collection = null;
if (array.getClass().isArray())
{
if (collectionType.isAssignableFrom(ArrayList.class))
collection = convertArrayToArrayList(array);
else if (collectionType.isAssignableFrom(HashSet.class))
collection = new HashSet<Object>(convertArrayToArrayList(array));
else if (collectionType.isAssignableFrom(ArrayQueue.class))
{
ArrayQueue<Object> q= new ArrayQueue<Object>();
q.addAll(convertArrayToArrayList(array));
collection=q;
}
}
if (collection==null)
throw new IllegalArgumentException("Can't convert \"" + array.getClass() + "\" to " + collectionType);
return collection;
}
/* ------------------------------------------------------------ */
private static ArrayList<Object> convertArrayToArrayList(Object array)
{
int length = Array.getLength(array);
ArrayList<Object> list = new ArrayList<Object>(length);
for (int i = 0; i < length; i++)
list.add(Array.get(array,i));
return list;
}
/* ------------------------------------------------------------ */
/*
* Call a put method.
@ -593,6 +619,7 @@ public class XmlConfiguration
{
if (!(obj instanceof Map))
throw new IllegalArgumentException("Object for put is not a Map: " + obj);
@SuppressWarnings("unchecked")
Map<Object, Object> map = (Map<Object, Object>)obj;
String name = node.getAttribute("name");
@ -610,7 +637,7 @@ public class XmlConfiguration
*/
private Object get(Object obj, XmlParser.Node node) throws Exception
{
Class oClass = nodeClass(node);
Class<?> oClass = nodeClass(node);
if (oClass != null)
obj = null;
else
@ -657,7 +684,7 @@ public class XmlConfiguration
private Object call(Object obj, XmlParser.Node node) throws Exception
{
String id = node.getAttribute("id");
Class oClass = nodeClass(node);
Class<?> oClass = nodeClass(node);
if (oClass != null)
obj = null;
else if (obj != null)
@ -718,7 +745,7 @@ public class XmlConfiguration
*/
private Object newObj(Object obj, XmlParser.Node node) throws Exception
{
Class oClass = nodeClass(node);
Class<?> oClass = nodeClass(node);
String id = node.getAttribute("id");
int size = 0;
int argi = node.size();
@ -748,7 +775,7 @@ public class XmlConfiguration
LOG.debug("XML new " + oClass);
// Lets just try all constructors for now
Constructor[] constructors = oClass.getConstructors();
Constructor<?>[] constructors = oClass.getConstructors();
for (int c = 0; constructors != null && c < constructors.length; c++)
{
if (constructors[c].getParameterTypes().length != size)
@ -809,7 +836,7 @@ public class XmlConfiguration
{
// Get the type
Class aClass = java.lang.Object.class;
Class<?> aClass = java.lang.Object.class;
String type = node.getAttribute("type");
final String id = node.getAttribute("id");
if (type != null)
@ -830,10 +857,9 @@ public class XmlConfiguration
Object al = null;
Iterator iter = node.iterator("Item");
while (iter.hasNext())
for (Object nodeObject : node)
{
XmlParser.Node item = (XmlParser.Node)iter.next();
XmlParser.Node item = (Node)nodeObject;
String nid = item.getAttribute("id");
Object v = value(obj,item);
al = LazyList.add(al,(v == null && aClass.isPrimitive())?ZERO:v);
@ -859,9 +885,8 @@ public class XmlConfiguration
if (id != null)
_idMap.put(id,map);
for (int i = 0; i < node.size(); i++)
for (Object o : node)
{
Object o = node.get(i);
if (o instanceof String)
continue;
XmlParser.Node entry = (XmlParser.Node)o;
@ -871,12 +896,11 @@ public class XmlConfiguration
XmlParser.Node key = null;
XmlParser.Node value = null;
for (int j = 0; j < entry.size(); j++)
for (Object object : node)
{
o = entry.get(j);
if (o instanceof String)
if (object instanceof String)
continue;
XmlParser.Node item = (XmlParser.Node)o;
XmlParser.Node item = (XmlParser.Node)object;
if (!item.getTag().equals("Item"))
throw new IllegalStateException("Not an Item");
if (key == null)
@ -907,9 +931,11 @@ public class XmlConfiguration
/*
* Get a Property.
*
* @param obj @param node @return @exception Exception
* @param node
* @return
* @exception Exception
*/
private Object propertyObj(Object obj, XmlParser.Node node) throws Exception
private Object propertyObj(XmlParser.Node node) throws Exception
{
String id = node.getAttribute("id");
String name = node.getAttribute("name");
@ -1019,19 +1045,19 @@ public class XmlConfiguration
// Try to type the object
if (type == null)
{
if (value != null && value instanceof String)
if (value instanceof String)
return ((String)value).trim();
return value;
}
if ("String".equals(type) || "java.lang.String".equals(type))
if (isTypeMatchingClass(type,String.class))
return value.toString();
Class<?> pClass = TypeUtil.fromName(type);
if (pClass != null)
return TypeUtil.valueOf(pClass,value.toString());
if ("URL".equals(type) || "java.net.URL".equals(type))
if (isTypeMatchingClass(type,URL.class))
{
if (value instanceof URL)
return value;
@ -1045,7 +1071,7 @@ public class XmlConfiguration
}
}
if ("InetAddress".equals(type) || "java.net.InetAddress".equals(type))
if (isTypeMatchingClass(type,InetAddress.class))
{
if (value instanceof InetAddress)
return value;
@ -1058,9 +1084,22 @@ public class XmlConfiguration
throw new InvocationTargetException(e);
}
}
for (Class<?> collectionClass : __supportedCollections)
{
if (isTypeMatchingClass(type,collectionClass))
return convertArrayToCollection(value,collectionClass);
}
throw new IllegalStateException("Unknown type " + type);
}
/* ------------------------------------------------------------ */
private static boolean isTypeMatchingClass(String type, Class<?> classToMatch)
{
boolean match = classToMatch.getSimpleName().equalsIgnoreCase(type) || classToMatch.getName().equals(type);
return match;
}
/* ------------------------------------------------------------ */
/*
@ -1087,7 +1126,7 @@ public class XmlConfiguration
if ("Map".equals(tag))
return newMap(obj,node);
if ("Property".equals(tag))
return propertyObj(obj,node);
return propertyObj(node);
if ("SystemProperty".equals(tag))
{
@ -1129,7 +1168,6 @@ public class XmlConfiguration
* @param args
* array of property and xml configuration filenames or {@link Resource}s.
*/
@SuppressWarnings("unchecked")
public static void main(final String[] args) throws Exception
{
@ -1198,7 +1236,7 @@ public class XmlConfiguration
{
props.put(key.toString(),String.valueOf(properties.get(key)));
}
configuration.setProperties(props);
configuration.getProperties().putAll(props);
}
obj[i] = configuration.configure();
last = configuration;

View File

@ -0,0 +1,77 @@
// ========================================================================
// Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.xml;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/* ------------------------------------------------------------ */
/**
*/
public class ConstructorArgTestClass
{
@SuppressWarnings("rawtypes")
private List list;
@SuppressWarnings("rawtypes")
private ArrayList arrayList;
@SuppressWarnings("rawtypes")
private Set set;
@SuppressWarnings("rawtypes")
public ConstructorArgTestClass(LinkedList list)
{
// not supported yet
}
@SuppressWarnings("rawtypes")
public ConstructorArgTestClass(ArrayList arrayList, List list)
{
this.arrayList = arrayList;
this.list = list;
}
@SuppressWarnings("rawtypes")
public ConstructorArgTestClass(ArrayList list)
{
this.list = list;
}
@SuppressWarnings("rawtypes")
public ConstructorArgTestClass(Set set)
{
this.set = set;
}
@SuppressWarnings("rawtypes")
public List getList()
{
return list;
}
@SuppressWarnings("rawtypes")
public ArrayList getArrayList()
{
return arrayList;
}
@SuppressWarnings("rawtypes")
public Set getSet()
{
return set;
}
}

View File

@ -14,7 +14,11 @@
package org.eclipse.jetty.xml;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.junit.Ignore;
@ -33,6 +37,11 @@ public class TestConfiguration extends HashMap<String,Object>
public int testField1;
public int testField2;
public int propValue;
@SuppressWarnings("rawtypes")
private List list;
@SuppressWarnings("rawtypes")
private Set set;
private ConstructorArgTestClass constructorArgTestClass;
public void setTest(Object value)
{
@ -87,4 +96,49 @@ public class TestConfiguration extends HashMap<String,Object>
{
this.ia=ia;
}
@SuppressWarnings("rawtypes")
public List getList()
{
if (constructorArgTestClass != null)
return constructorArgTestClass.getList();
return list;
}
@SuppressWarnings("rawtypes")
public void setList(List list)
{
this.list = list;
}
@SuppressWarnings("rawtypes")
public void setLinkedList(LinkedList list)
{
this.list = list;
}
@SuppressWarnings("rawtypes")
public void setArrayList(ArrayList list)
{
this.list = list;
}
@SuppressWarnings("rawtypes")
public Set getSet()
{
if (constructorArgTestClass != null)
return constructorArgTestClass.getSet();
return set;
}
@SuppressWarnings("rawtypes")
public void setSet(Set set)
{
this.set = set;
}
public void setConstructorArgTestClass(ConstructorArgTestClass constructorArgTestClass)
{
this.constructorArgTestClass = constructorArgTestClass;
}
}

View File

@ -14,7 +14,8 @@
package org.eclipse.jetty.xml;
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import java.net.URL;
import java.util.HashMap;
@ -25,13 +26,16 @@ import org.junit.Test;
public class XmlConfigurationTest
{
protected String _configure="org/eclipse/jetty/xml/configure.xml";
private static final String STRING_ARRAY_XML = "<Array type=\"String\"><Item type=\"String\">String1</Item><Item type=\"String\">String2</Item></Array>";
private static final String INT_ARRAY_XML = "<Array type=\"int\"><Item type=\"int\">1</Item><Item type=\"int\">2</Item></Array>";
@Test
public void testMortBay() throws Exception
{
URL url = XmlConfigurationTest.class.getClassLoader().getResource("org/eclipse/jetty/xml/mortbay.xml");
XmlConfiguration configuration = new XmlConfiguration(url);
Object o=configuration.configure();
configuration.configure();
}
@Test
@ -185,6 +189,129 @@ public class XmlConfigurationTest
configuration.configure(tc);
assertEquals("Set String 3","SetValue",tc.testObject);
assertEquals("Set Type 3",2,tc.testInt);
}
@Test
public void testListConstructorArg() throws Exception
{
XmlConfiguration xmlConfiguration = new XmlConfiguration("<Configure class=\"org.eclipse.jetty.xml.TestConfiguration\">"
+ "<Set name=\"constructorArgTestClass\"><New class=\"org.eclipse.jetty.xml.ConstructorArgTestClass\"><Arg type=\"List\">"
+ STRING_ARRAY_XML + "</Arg></New></Set></Configure>");
TestConfiguration tc = new TestConfiguration();
assertThat("tc.getList() returns null as it's not configured yet",tc.getList(),is(nullValue()));
xmlConfiguration.configure(tc);
assertThat("tc.getList() returns not null",tc.getList(),not(nullValue()));
assertThat("tc.getList() has two entries as specified in the xml",tc.getList().size(),is(2));
}
@Test
public void testTwoArgumentListConstructorArg() throws Exception
{
XmlConfiguration xmlConfiguration = new XmlConfiguration("<Configure class=\"org.eclipse.jetty.xml.TestConfiguration\">"
+ "<Set name=\"constructorArgTestClass\"><New class=\"org.eclipse.jetty.xml.ConstructorArgTestClass\">"
+ "<Arg type=\"List\">" + STRING_ARRAY_XML + "</Arg>"
+ "<Arg type=\"List\">" + STRING_ARRAY_XML + "</Arg>"
+ "</New></Set></Configure>");
TestConfiguration tc = new TestConfiguration();
assertThat("tc.getList() returns null as it's not configured yet",tc.getList(),is(nullValue()));
xmlConfiguration.configure(tc);
assertThat("tc.getList() returns not null",tc.getList(),not(nullValue()));
assertThat("tc.getList() has two entries as specified in the xml",tc.getList().size(),is(2));
}
@Test(expected = IllegalArgumentException.class)
public void testListNotContainingArray() throws Exception
{
XmlConfiguration xmlConfiguration = new XmlConfiguration("<Configure class=\"org.eclipse.jetty.xml.TestConfiguration\">"
+ "<New class=\"org.eclipse.jetty.xml.ConstructorArgTestClass\"><Arg type=\"List\">Some String</Arg></New></Configure>");
TestConfiguration tc = new TestConfiguration();
xmlConfiguration.configure(tc);
}
@Test
public void testSetConstructorArg() throws Exception
{
XmlConfiguration xmlConfiguration = new XmlConfiguration("<Configure class=\"org.eclipse.jetty.xml.TestConfiguration\">"
+ "<Set name=\"constructorArgTestClass\"><New class=\"org.eclipse.jetty.xml.ConstructorArgTestClass\"><Arg type=\"Set\">"
+ STRING_ARRAY_XML + "</Arg></New></Set></Configure>");
TestConfiguration tc = new TestConfiguration();
assertThat("tc.getList() returns null as it's not configured yet",tc.getSet(),is(nullValue()));
xmlConfiguration.configure(tc);
assertThat("tc.getList() returns not null",tc.getSet(),not(nullValue()));
assertThat("tc.getList() has two entries as specified in the xml",tc.getSet().size(),is(2));
}
@Test(expected = IllegalArgumentException.class)
public void testSetNotContainingArray() throws Exception
{
XmlConfiguration xmlConfiguration = new XmlConfiguration("<Configure class=\"org.eclipse.jetty.xml.TestConfiguration\">"
+ "<New class=\"org.eclipse.jetty.xml.ConstructorArgTestClass\"><Arg type=\"Set\">Some String</Arg></New></Configure>");
TestConfiguration tc = new TestConfiguration();
xmlConfiguration.configure(tc);
}
@Test
public void testListSetterWithStringArray() throws Exception
{
XmlConfiguration xmlConfiguration = new XmlConfiguration("<Configure class=\"org.eclipse.jetty.xml.TestConfiguration\"><Set name=\"List\">"
+ STRING_ARRAY_XML + "</Set></Configure>");
TestConfiguration tc = new TestConfiguration();
assertThat("tc.getList() returns null as it's not configured yet",tc.getList(),is(nullValue()));
xmlConfiguration.configure(tc);
assertThat("tc.getList() has two entries as specified in the xml",tc.getList().size(),is(2));
}
@Test
public void testListSetterWithPrimitiveArray() throws Exception
{
XmlConfiguration xmlConfiguration = new XmlConfiguration("<Configure class=\"org.eclipse.jetty.xml.TestConfiguration\"><Set name=\"List\">"
+ INT_ARRAY_XML + "</Set></Configure>");
TestConfiguration tc = new TestConfiguration();
assertThat("tc.getList() returns null as it's not configured yet",tc.getList(),is(nullValue()));
xmlConfiguration.configure(tc);
assertThat("tc.getList() has two entries as specified in the xml",tc.getList().size(),is(2));
}
@Test(expected=NoSuchMethodException.class)
public void testNotSupportedLinkedListSetter() throws Exception
{
XmlConfiguration xmlConfiguration = new XmlConfiguration("<Configure class=\"org.eclipse.jetty.xml.TestConfiguration\"><Set name=\"LinkedList\">"
+ INT_ARRAY_XML + "</Set></Configure>");
TestConfiguration tc = new TestConfiguration();
assertThat("tc.getSet() returns null as it's not configured yet",tc.getList(),is(nullValue()));
xmlConfiguration.configure(tc);
}
@Test
public void testArrayListSetter() throws Exception
{
XmlConfiguration xmlConfiguration = new XmlConfiguration("<Configure class=\"org.eclipse.jetty.xml.TestConfiguration\"><Set name=\"ArrayList\">"
+ INT_ARRAY_XML + "</Set></Configure>");
TestConfiguration tc = new TestConfiguration();
assertThat("tc.getSet() returns null as it's not configured yet",tc.getList(),is(nullValue()));
xmlConfiguration.configure(tc);
assertThat("tc.getSet() has two entries as specified in the xml",tc.getList().size(),is(2));
}
@Test
public void testSetSetter() throws Exception
{
XmlConfiguration xmlConfiguration = new XmlConfiguration("<Configure class=\"org.eclipse.jetty.xml.TestConfiguration\"><Set name=\"Set\">"
+ STRING_ARRAY_XML + "</Set></Configure>");
TestConfiguration tc = new TestConfiguration();
assertThat("tc.getSet() returns null as it's not configured yet",tc.getSet(),is(nullValue()));
xmlConfiguration.configure(tc);
assertThat("tc.getSet() has two entries as specified in the xml",tc.getSet().size(),is(2));
}
@Test
public void testSetSetterWithPrimitiveArray() throws Exception
{
XmlConfiguration xmlConfiguration = new XmlConfiguration("<Configure class=\"org.eclipse.jetty.xml.TestConfiguration\"><Set name=\"Set\">"
+ INT_ARRAY_XML + "</Set></Configure>");
TestConfiguration tc = new TestConfiguration();
assertThat("tc.getSet() returns null as it's not configured yet",tc.getSet(),is(nullValue()));
xmlConfiguration.configure(tc);
assertThat("tc.getSet() has two entries as specified in the xml",tc.getSet().size(),is(2));
}
}

View File

@ -111,11 +111,11 @@ public abstract class AbstractSessionMigrationTest
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
HttpSession session = request.getSession(false);
if (session == null) session = request.getSession(true);
String action = request.getParameter("action");
if ("set".equals(action))
{
if (session == null) session = request.getSession(true);
int value = Integer.parseInt(request.getParameter("value"));
session.setAttribute("value", value);
PrintWriter writer = response.getWriter();
@ -125,6 +125,8 @@ public abstract class AbstractSessionMigrationTest
else if ("get".equals(action))
{
int value = (Integer)session.getAttribute("value");
int x = ((AbstractSession)session).getMaxInactiveInterval();
assertTrue(x > 0);
PrintWriter writer = response.getWriter();
writer.println(value);
writer.flush();