SPDY refactorings.
This commit is contained in:
parent
2b26d2f3ee
commit
f0421723b8
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-project</artifactId>
|
||||
<version>9.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.eclipse.jetty.spdy</groupId>
|
||||
<artifactId>spdy-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Jetty :: SPDY :: Parent</name>
|
||||
|
||||
<properties>
|
||||
<npn.version>1.0.0.v20120402</npn.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>spdy-core</module>
|
||||
<module>spdy-jetty</module>
|
||||
<module>spdy-jetty-http</module>
|
||||
<module>spdy-jetty-http-webapp</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>require-jdk7</id>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<requireJavaVersion>
|
||||
<version>[1.7,)</version>
|
||||
</requireJavaVersion>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-pmd-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.spdy</groupId>
|
||||
<artifactId>spdy-parent</artifactId>
|
||||
<version>9.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spdy-core</artifactId>
|
||||
<name>Jetty :: SPDY :: Core</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<version>${slf4j-version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
|
||||
public class CompressionDictionary
|
||||
{
|
||||
private static final byte[] DICTIONARY_V2 = ("" +
|
||||
"optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" +
|
||||
"languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" +
|
||||
"f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" +
|
||||
"-agent10010120020120220320420520630030130230330430530630740040140240340440" +
|
||||
"5406407408409410411412413414415416417500501502503504505accept-rangesageeta" +
|
||||
"glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic" +
|
||||
"ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran" +
|
||||
"sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati" +
|
||||
"oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo" +
|
||||
"ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe" +
|
||||
"pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" +
|
||||
"ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" +
|
||||
".1statusversionurl" +
|
||||
// Must be NUL terminated
|
||||
"\u0000").getBytes();
|
||||
|
||||
private static final byte[] DICTIONARY_V3 = ("" +
|
||||
"\u0000\u0000\u0000\u0007options\u0000\u0000\u0000\u0004head\u0000\u0000\u0000\u0004post" +
|
||||
"\u0000\u0000\u0000\u0003put\u0000\u0000\u0000\u0006delete\u0000\u0000\u0000\u0005trace" +
|
||||
"\u0000\u0000\u0000\u0006accept\u0000\u0000\u0000\u000Eaccept-charset" +
|
||||
"\u0000\u0000\u0000\u000Faccept-encoding\u0000\u0000\u0000\u000Faccept-language" +
|
||||
"\u0000\u0000\u0000\raccept-ranges\u0000\u0000\u0000\u0003age\u0000\u0000\u0000\u0005allow" +
|
||||
"\u0000\u0000\u0000\rauthorization\u0000\u0000\u0000\rcache-control" +
|
||||
"\u0000\u0000\u0000\nconnection\u0000\u0000\u0000\fcontent-base\u0000\u0000\u0000\u0010content-encoding" +
|
||||
"\u0000\u0000\u0000\u0010content-language\u0000\u0000\u0000\u000Econtent-length" +
|
||||
"\u0000\u0000\u0000\u0010content-location\u0000\u0000\u0000\u000Bcontent-md5" +
|
||||
"\u0000\u0000\u0000\rcontent-range\u0000\u0000\u0000\fcontent-type\u0000\u0000\u0000\u0004date" +
|
||||
"\u0000\u0000\u0000\u0004etag\u0000\u0000\u0000\u0006expect\u0000\u0000\u0000\u0007expires" +
|
||||
"\u0000\u0000\u0000\u0004from\u0000\u0000\u0000\u0004host\u0000\u0000\u0000\bif-match" +
|
||||
"\u0000\u0000\u0000\u0011if-modified-since\u0000\u0000\u0000\rif-none-match\u0000\u0000\u0000\bif-range" +
|
||||
"\u0000\u0000\u0000\u0013if-unmodified-since\u0000\u0000\u0000\rlast-modified" +
|
||||
"\u0000\u0000\u0000\blocation\u0000\u0000\u0000\fmax-forwards\u0000\u0000\u0000\u0006pragma" +
|
||||
"\u0000\u0000\u0000\u0012proxy-authenticate\u0000\u0000\u0000\u0013proxy-authorization" +
|
||||
"\u0000\u0000\u0000\u0005range\u0000\u0000\u0000\u0007referer\u0000\u0000\u0000\u000Bretry-after" +
|
||||
"\u0000\u0000\u0000\u0006server\u0000\u0000\u0000\u0002te\u0000\u0000\u0000\u0007trailer" +
|
||||
"\u0000\u0000\u0000\u0011transfer-encoding\u0000\u0000\u0000\u0007upgrade\u0000\u0000\u0000\nuser-agent" +
|
||||
"\u0000\u0000\u0000\u0004vary\u0000\u0000\u0000\u0003via\u0000\u0000\u0000\u0007warning" +
|
||||
"\u0000\u0000\u0000\u0010www-authenticate\u0000\u0000\u0000\u0006method\u0000\u0000\u0000\u0003get" +
|
||||
"\u0000\u0000\u0000\u0006status\u0000\u0000\u0000\u0006200 OK\u0000\u0000\u0000\u0007version" +
|
||||
"\u0000\u0000\u0000\bHTTP/1.1\u0000\u0000\u0000\u0003url\u0000\u0000\u0000\u0006public" +
|
||||
"\u0000\u0000\u0000\nset-cookie\u0000\u0000\u0000\nkeep-alive\u0000\u0000\u0000\u0006origin" +
|
||||
"100101201202205206300302303304305306307402405406407408409410411412413414415416417502504505" +
|
||||
"203 Non-Authoritative Information204 No Content301 Moved Permanently400 Bad Request401 Unauthorized" +
|
||||
"403 Forbidden404 Not Found500 Internal Server Error501 Not Implemented503 Service Unavailable" +
|
||||
"Jan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec 00:00:00 Mon, Tue, Wed, Thu, Fri, Sat, Sun, GMT" +
|
||||
"chunked,text/html,image/png,image/jpg,image/gif,application/xml,application/xhtml+xml,text/plain," +
|
||||
"text/javascript,publicprivatemax-age=gzip,deflate,sdchcharset=utf-8charset=iso-8859-1,utf-,*,enq=0.")
|
||||
.getBytes();
|
||||
|
||||
public static byte[] get(short version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
return DICTIONARY_V2;
|
||||
case SPDY.V3:
|
||||
return DICTIONARY_V3;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.util.zip.ZipException;
|
||||
|
||||
public interface CompressionFactory
|
||||
{
|
||||
public Compressor newCompressor();
|
||||
|
||||
public Decompressor newDecompressor();
|
||||
|
||||
public interface Compressor
|
||||
{
|
||||
public void setInput(byte[] input);
|
||||
|
||||
public void setDictionary(byte[] dictionary);
|
||||
|
||||
public int compress(byte[] output);
|
||||
}
|
||||
|
||||
public interface Decompressor
|
||||
{
|
||||
public void setDictionary(byte[] dictionary);
|
||||
|
||||
public void setInput(byte[] input);
|
||||
|
||||
public int decompress(byte[] output) throws ZipException;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
||||
public interface Controller<T>
|
||||
{
|
||||
public int write(ByteBuffer buffer, Callback<T> callback, T context);
|
||||
|
||||
public void close(boolean onlyOutput);
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.Session;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
||||
public interface ISession extends Session
|
||||
{
|
||||
/**
|
||||
* <p>Initiates the flush of data to the other peer.</p>
|
||||
* <p>Note that the flush may do nothing if, for example, there is nothing to flush, or
|
||||
* if the data to be flushed belong to streams that have their flow-control stalled.</p>
|
||||
*/
|
||||
public void flush();
|
||||
|
||||
public <C> void control(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Callback<C> callback, C context);
|
||||
|
||||
public <C> void data(IStream stream, DataInfo dataInfo, long timeout, TimeUnit unit, Callback<C> callback, C context);
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SessionFrameListener;
|
||||
import org.eclipse.jetty.spdy.api.Stream;
|
||||
import org.eclipse.jetty.spdy.api.StreamFrameListener;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.DataFrame;
|
||||
|
||||
/**
|
||||
* <p>The internal interface that represents a stream.</p>
|
||||
* <p>{@link IStream} contains additional methods used by a SPDY
|
||||
* implementation (and not by an application).</p>
|
||||
*/
|
||||
public interface IStream extends Stream
|
||||
{
|
||||
/**
|
||||
* <p>Senders of data frames need to know the current window size
|
||||
* to determine whether they can send more data.</p>
|
||||
*
|
||||
* @return the current window size for this stream.
|
||||
* @see #updateWindowSize(int)
|
||||
*/
|
||||
public int getWindowSize();
|
||||
|
||||
/**
|
||||
* <p>Updates the window size for this stream by the given amount,
|
||||
* that can be positive or negative.</p>
|
||||
* <p>Senders and recipients of data frames update the window size,
|
||||
* respectively, with negative values and positive values.</p>
|
||||
*
|
||||
* @param delta the signed amount the window size needs to be updated
|
||||
* @see #getWindowSize()
|
||||
*/
|
||||
public void updateWindowSize(int delta);
|
||||
|
||||
/**
|
||||
* @param listener the stream frame listener associated to this stream
|
||||
* as returned by {@link SessionFrameListener#onSyn(Stream, SynInfo)}
|
||||
*/
|
||||
public void setStreamFrameListener(StreamFrameListener listener);
|
||||
|
||||
/**
|
||||
* <p>A stream can be open, {@link #isHalfClosed() half closed} or
|
||||
* {@link #isClosed() closed} and this method updates the close state
|
||||
* of this stream.</p>
|
||||
* <p>If the stream is open, calling this method with a value of true
|
||||
* puts the stream into half closed state.</p>
|
||||
* <p>If the stream is half closed, calling this method with a value
|
||||
* of true puts the stream into closed state.</p>
|
||||
*
|
||||
* @param close whether the close state should be updated
|
||||
* @param local whether the close is local or remote
|
||||
*/
|
||||
public void updateCloseState(boolean close, boolean local);
|
||||
|
||||
/**
|
||||
* <p>Processes the given control frame,
|
||||
* for example by updating the stream's state or by calling listeners.</p>
|
||||
*
|
||||
* @param frame the control frame to process
|
||||
* @see #process(DataFrame, ByteBuffer)
|
||||
*/
|
||||
public void process(ControlFrame frame);
|
||||
|
||||
/**
|
||||
* <p>Processes the given data frame along with the given byte buffer,
|
||||
* for example by updating the stream's state or by calling listeners.</p>
|
||||
*
|
||||
* @param frame the data frame to process
|
||||
* @param data the byte buffer to process
|
||||
* @see #process(ControlFrame)
|
||||
*/
|
||||
public void process(DataFrame frame, ByteBuffer data);
|
||||
|
||||
/**
|
||||
* <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
|
||||
public IStream getAssociatedStream();
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
public interface IdleListener
|
||||
{
|
||||
public void onIdle(boolean idle);
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
||||
/**
|
||||
* <p>A {@link Promise} is a {@link Future} that allows a result or a failure to be set,
|
||||
* so that the {@link Future} will be {@link #isDone() done}.</p>
|
||||
*
|
||||
* @param <T> the type of the result object
|
||||
*/
|
||||
public class Promise<T> implements Callback<T>, Future<T>
|
||||
{
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
private boolean cancelled;
|
||||
private Throwable failure;
|
||||
private T promise;
|
||||
|
||||
@Override
|
||||
public void completed(T result)
|
||||
{
|
||||
this.promise = result;
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
this.failure = x;
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning)
|
||||
{
|
||||
cancelled = true;
|
||||
latch.countDown();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone()
|
||||
{
|
||||
return cancelled || latch.getCount() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() throws InterruptedException, ExecutionException
|
||||
{
|
||||
latch.await();
|
||||
return result();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
boolean elapsed = !latch.await(timeout, unit);
|
||||
if (elapsed)
|
||||
throw new TimeoutException();
|
||||
return result();
|
||||
}
|
||||
|
||||
private T result() throws ExecutionException
|
||||
{
|
||||
Throwable failure = this.failure;
|
||||
if (failure != null)
|
||||
throw new ExecutionException(failure);
|
||||
return promise;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* <p>A subclass container of {@link SynInfo} for unidirectional streams</p>
|
||||
*/
|
||||
public class PushSynInfo extends SynInfo
|
||||
{
|
||||
public static final byte FLAG_UNIDIRECTIONAL = 2;
|
||||
|
||||
private int associatedStreamId;
|
||||
|
||||
public PushSynInfo(int associatedStreamId, SynInfo synInfo){
|
||||
super(synInfo.getHeaders(), synInfo.isClose(), synInfo.getPriority());
|
||||
this.associatedStreamId = associatedStreamId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the close and unidirectional flags as integer
|
||||
* @see #FLAG_CLOSE
|
||||
* @see #FLAG_UNIDIRECTIONAL
|
||||
*/
|
||||
@Override
|
||||
public byte getFlags()
|
||||
{
|
||||
byte flags = super.getFlags();
|
||||
flags += FLAG_UNIDIRECTIONAL;
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the id of the associated stream
|
||||
*/
|
||||
public int getAssociatedStreamId()
|
||||
{
|
||||
return associatedStreamId;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
|
||||
public class SessionException extends RuntimeException
|
||||
{
|
||||
|
||||
private final SessionStatus sessionStatus;
|
||||
|
||||
public SessionException(SessionStatus sessionStatus)
|
||||
{
|
||||
this.sessionStatus = sessionStatus;
|
||||
}
|
||||
|
||||
public SessionException(SessionStatus sessionStatus, String message)
|
||||
{
|
||||
super(message);
|
||||
this.sessionStatus = sessionStatus;
|
||||
}
|
||||
|
||||
public SessionException(SessionStatus sessionStatus, Throwable cause)
|
||||
{
|
||||
super(cause);
|
||||
this.sessionStatus = sessionStatus;
|
||||
}
|
||||
|
||||
public SessionStatus getSessionStatus()
|
||||
{
|
||||
return sessionStatus;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.ZipException;
|
||||
|
||||
public class StandardCompressionFactory implements CompressionFactory
|
||||
{
|
||||
@Override
|
||||
public Compressor newCompressor()
|
||||
{
|
||||
return new StandardCompressor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Decompressor newDecompressor()
|
||||
{
|
||||
return new StandardDecompressor();
|
||||
}
|
||||
|
||||
public static class StandardCompressor implements Compressor
|
||||
{
|
||||
private final Deflater deflater = new Deflater();
|
||||
|
||||
@Override
|
||||
public void setInput(byte[] input)
|
||||
{
|
||||
deflater.setInput(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDictionary(byte[] dictionary)
|
||||
{
|
||||
deflater.setDictionary(dictionary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compress(byte[] output)
|
||||
{
|
||||
return deflater.deflate(output, 0, output.length, Deflater.SYNC_FLUSH);
|
||||
}
|
||||
}
|
||||
|
||||
public static class StandardDecompressor implements CompressionFactory.Decompressor
|
||||
{
|
||||
private final Inflater inflater = new Inflater();
|
||||
|
||||
@Override
|
||||
public void setDictionary(byte[] dictionary)
|
||||
{
|
||||
inflater.setDictionary(dictionary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInput(byte[] input)
|
||||
{
|
||||
inflater.setInput(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int decompress(byte[] output) throws ZipException
|
||||
{
|
||||
try
|
||||
{
|
||||
return inflater.inflate(output);
|
||||
}
|
||||
catch (DataFormatException x)
|
||||
{
|
||||
throw (ZipException)new ZipException().initCause(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,486 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.api.HeadersInfo;
|
||||
import org.eclipse.jetty.spdy.api.ReplyInfo;
|
||||
import org.eclipse.jetty.spdy.api.RstInfo;
|
||||
import org.eclipse.jetty.spdy.api.Session;
|
||||
import org.eclipse.jetty.spdy.api.Stream;
|
||||
import org.eclipse.jetty.spdy.api.StreamFrameListener;
|
||||
import org.eclipse.jetty.spdy.api.StreamStatus;
|
||||
import org.eclipse.jetty.spdy.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.Callback;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class StandardStream implements IStream
|
||||
{
|
||||
private static final Logger logger = Log.getLogger(Stream.class);
|
||||
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
|
||||
private final IStream associatedStream;
|
||||
private final SynStreamFrame frame;
|
||||
private final ISession session;
|
||||
private final AtomicInteger windowSize;
|
||||
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)
|
||||
{
|
||||
this.frame = frame;
|
||||
this.session = session;
|
||||
this.windowSize = new AtomicInteger(windowSize);
|
||||
this.associatedStream = associatedStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId()
|
||||
{
|
||||
return frame.getStreamId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IStream getAssociatedStream()
|
||||
{
|
||||
return associatedStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Stream> getPushedStreams()
|
||||
{
|
||||
return pushedStreams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void associate(IStream stream)
|
||||
{
|
||||
pushedStreams.add(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disassociate(IStream stream)
|
||||
{
|
||||
pushedStreams.remove(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getPriority()
|
||||
{
|
||||
return frame.getPriority();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWindowSize()
|
||||
{
|
||||
return windowSize.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateWindowSize(int delta)
|
||||
{
|
||||
int size = windowSize.addAndGet(delta);
|
||||
logger.debug("Updated window size by {}, new window size {}",delta,size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session getSession()
|
||||
{
|
||||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String key)
|
||||
{
|
||||
return attributes.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String key, Object value)
|
||||
{
|
||||
attributes.put(key,value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object removeAttribute(String key)
|
||||
{
|
||||
return attributes.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStreamFrameListener(StreamFrameListener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCloseState(boolean close, boolean local)
|
||||
{
|
||||
if (close)
|
||||
{
|
||||
switch (closeState)
|
||||
{
|
||||
case OPENED:
|
||||
{
|
||||
closeState = local?CloseState.LOCALLY_CLOSED:CloseState.REMOTELY_CLOSED;
|
||||
break;
|
||||
}
|
||||
case LOCALLY_CLOSED:
|
||||
{
|
||||
if (local)
|
||||
throw new IllegalStateException();
|
||||
else
|
||||
closeState = CloseState.CLOSED;
|
||||
break;
|
||||
}
|
||||
case REMOTELY_CLOSED:
|
||||
{
|
||||
if (local)
|
||||
closeState = CloseState.CLOSED;
|
||||
else
|
||||
throw new IllegalStateException();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ControlFrame frame)
|
||||
{
|
||||
switch (frame.getType())
|
||||
{
|
||||
case SYN_STREAM:
|
||||
{
|
||||
openState = OpenState.SYN_RECV;
|
||||
break;
|
||||
}
|
||||
case SYN_REPLY:
|
||||
{
|
||||
openState = OpenState.REPLY_RECV;
|
||||
SynReplyFrame synReply = (SynReplyFrame)frame;
|
||||
updateCloseState(synReply.isClose(),false);
|
||||
ReplyInfo replyInfo = new ReplyInfo(synReply.getHeaders(),synReply.isClose());
|
||||
notifyOnReply(replyInfo);
|
||||
break;
|
||||
}
|
||||
case HEADERS:
|
||||
{
|
||||
HeadersFrame headers = (HeadersFrame)frame;
|
||||
updateCloseState(headers.isClose(),false);
|
||||
HeadersInfo headersInfo = new HeadersInfo(headers.getHeaders(),headers.isClose(),headers.isResetCompression());
|
||||
notifyOnHeaders(headersInfo);
|
||||
break;
|
||||
}
|
||||
case WINDOW_UPDATE:
|
||||
{
|
||||
WindowUpdateFrame windowUpdate = (WindowUpdateFrame)frame;
|
||||
updateWindowSize(windowUpdate.getWindowDelta());
|
||||
break;
|
||||
}
|
||||
case RST_STREAM:
|
||||
{
|
||||
reset = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
session.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(DataFrame frame, ByteBuffer data)
|
||||
{
|
||||
// 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())
|
||||
{
|
||||
logger.debug("Ignoring received dataFrame as this stream is remotely closed: " + frame);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canReceive())
|
||||
{
|
||||
logger.debug("Can't receive. Sending rst: " + frame);
|
||||
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());
|
||||
}
|
||||
};
|
||||
notifyOnData(dataInfo);
|
||||
session.flush();
|
||||
}
|
||||
|
||||
private void windowUpdate(int delta)
|
||||
{
|
||||
if (delta > 0)
|
||||
{
|
||||
WindowUpdateFrame windowUpdateFrame = new WindowUpdateFrame(session.getVersion(),getId(),delta);
|
||||
session.control(this,windowUpdateFrame,0,TimeUnit.MILLISECONDS,null,null);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyOnReply(ReplyInfo replyInfo)
|
||||
{
|
||||
final StreamFrameListener listener = this.listener;
|
||||
try
|
||||
{
|
||||
if (listener != null)
|
||||
{
|
||||
logger.debug("Invoking reply callback with {} on listener {}",replyInfo,listener);
|
||||
listener.onReply(this,replyInfo);
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener,x);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyOnHeaders(HeadersInfo headersInfo)
|
||||
{
|
||||
final StreamFrameListener listener = this.listener;
|
||||
try
|
||||
{
|
||||
if (listener != null)
|
||||
{
|
||||
logger.debug("Invoking headers callback with {} on listener {}",frame,listener);
|
||||
listener.onHeaders(this,headersInfo);
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener,x);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyOnData(DataInfo dataInfo)
|
||||
{
|
||||
final StreamFrameListener listener = this.listener;
|
||||
try
|
||||
{
|
||||
if (listener != null)
|
||||
{
|
||||
logger.debug("Invoking data callback with {} on listener {}",dataInfo,listener);
|
||||
listener.onData(this,dataInfo);
|
||||
logger.debug("Invoked data callback with {} on listener {}",dataInfo,listener);
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener,x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Stream> syn(SynInfo synInfo)
|
||||
{
|
||||
Promise<Stream> result = new Promise<>();
|
||||
syn(synInfo,0,TimeUnit.MILLISECONDS,result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syn(SynInfo synInfo, long timeout, TimeUnit unit, Callback<Stream> callback)
|
||||
{
|
||||
if (isClosed() || isReset())
|
||||
{
|
||||
callback.failed(new StreamException(getId(),StreamStatus.STREAM_ALREADY_CLOSED));
|
||||
return;
|
||||
}
|
||||
PushSynInfo pushSynInfo = new PushSynInfo(getId(),synInfo);
|
||||
session.syn(pushSynInfo,null,timeout,unit, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Void> reply(ReplyInfo replyInfo)
|
||||
{
|
||||
Promise<Void> result = new Promise<>();
|
||||
reply(replyInfo,0,TimeUnit.MILLISECONDS,result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reply(ReplyInfo replyInfo, long timeout, TimeUnit unit, Callback<Void> callback)
|
||||
{
|
||||
if (isUnidirectional())
|
||||
throw new IllegalStateException("Protocol violation: cannot send SYN_REPLY frames in unidirectional streams");
|
||||
openState = OpenState.REPLY_SENT;
|
||||
updateCloseState(replyInfo.isClose(),true);
|
||||
SynReplyFrame frame = new SynReplyFrame(session.getVersion(),replyInfo.getFlags(),getId(),replyInfo.getHeaders());
|
||||
session.control(this,frame,timeout,unit, callback,null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Void> data(DataInfo dataInfo)
|
||||
{
|
||||
Promise<Void> result = new Promise<>();
|
||||
data(dataInfo,0,TimeUnit.MILLISECONDS,result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void data(DataInfo dataInfo, long timeout, TimeUnit unit, Callback<Void> callback)
|
||||
{
|
||||
if (!canSend())
|
||||
{
|
||||
session.rst(new RstInfo(getId(),StreamStatus.PROTOCOL_ERROR));
|
||||
throw new IllegalStateException("Protocol violation: cannot send a DATA frame before a SYN_REPLY frame");
|
||||
}
|
||||
if (isLocallyClosed())
|
||||
{
|
||||
session.rst(new RstInfo(getId(),StreamStatus.PROTOCOL_ERROR));
|
||||
throw new IllegalStateException("Protocol violation: cannot send a DATA frame on a closed stream");
|
||||
}
|
||||
|
||||
// Cannot update the close state here, because the data that we send may
|
||||
// be flow controlled, so we need the stream to update the window size.
|
||||
session.data(this,dataInfo,timeout,unit, callback,null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Void> headers(HeadersInfo headersInfo)
|
||||
{
|
||||
Promise<Void> result = new Promise<>();
|
||||
headers(headersInfo,0,TimeUnit.MILLISECONDS,result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void headers(HeadersInfo headersInfo, long timeout, TimeUnit unit, Callback<Void> callback)
|
||||
{
|
||||
if (!canSend())
|
||||
{
|
||||
session.rst(new RstInfo(getId(),StreamStatus.PROTOCOL_ERROR));
|
||||
throw new IllegalStateException("Protocol violation: cannot send a HEADERS frame before a SYN_REPLY frame");
|
||||
}
|
||||
if (isLocallyClosed())
|
||||
{
|
||||
session.rst(new RstInfo(getId(),StreamStatus.PROTOCOL_ERROR));
|
||||
throw new IllegalStateException("Protocol violation: cannot send a HEADERS frame on a closed stream");
|
||||
}
|
||||
|
||||
updateCloseState(headersInfo.isClose(),true);
|
||||
HeadersFrame frame = new HeadersFrame(session.getVersion(),headersInfo.getFlags(),getId(),headersInfo.getHeaders());
|
||||
session.control(this,frame,timeout,unit, callback,null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnidirectional()
|
||||
{
|
||||
return associatedStream != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReset()
|
||||
{
|
||||
return reset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHalfClosed()
|
||||
{
|
||||
CloseState closeState = this.closeState;
|
||||
return closeState == CloseState.LOCALLY_CLOSED || closeState == CloseState.REMOTELY_CLOSED || closeState == CloseState.CLOSED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed()
|
||||
{
|
||||
return closeState == CloseState.CLOSED;
|
||||
}
|
||||
|
||||
private boolean isLocallyClosed()
|
||||
{
|
||||
CloseState closeState = this.closeState;
|
||||
return closeState == CloseState.LOCALLY_CLOSED || closeState == CloseState.CLOSED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("stream=%d v%d %s",getId(),session.getVersion(),closeState);
|
||||
}
|
||||
|
||||
private boolean canSend()
|
||||
{
|
||||
OpenState openState = this.openState;
|
||||
return openState == OpenState.SYN_SENT || openState == OpenState.REPLY_RECV || openState == OpenState.REPLY_SENT;
|
||||
}
|
||||
|
||||
private boolean canReceive()
|
||||
{
|
||||
OpenState openState = this.openState;
|
||||
return openState == OpenState.SYN_RECV || openState == OpenState.REPLY_RECV || openState == OpenState.REPLY_SENT;
|
||||
}
|
||||
|
||||
private enum OpenState
|
||||
{
|
||||
SYN_SENT, SYN_RECV, REPLY_SENT, REPLY_RECV
|
||||
}
|
||||
|
||||
private enum CloseState
|
||||
{
|
||||
OPENED, LOCALLY_CLOSED, REMOTELY_CLOSED, CLOSED
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.StreamStatus;
|
||||
|
||||
public class StreamException extends RuntimeException
|
||||
{
|
||||
private final int streamId;
|
||||
private final StreamStatus streamStatus;
|
||||
|
||||
public StreamException(int streamId, StreamStatus streamStatus)
|
||||
{
|
||||
this.streamId = streamId;
|
||||
this.streamStatus = streamStatus;
|
||||
}
|
||||
|
||||
public StreamException(int streamId, StreamStatus streamStatus, String message)
|
||||
{
|
||||
super(message);
|
||||
this.streamId = streamId;
|
||||
this.streamStatus = streamStatus;
|
||||
}
|
||||
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public StreamStatus getStreamStatus()
|
||||
{
|
||||
return streamStatus;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* <p>Specialized {@link DataInfo} for {@link ByteBuffer} content.</p>
|
||||
*/
|
||||
public class ByteBufferDataInfo extends DataInfo
|
||||
{
|
||||
private final ByteBuffer buffer;
|
||||
private final int length;
|
||||
|
||||
public ByteBufferDataInfo(ByteBuffer buffer, boolean close)
|
||||
{
|
||||
this(buffer, close, false);
|
||||
}
|
||||
|
||||
public ByteBufferDataInfo(ByteBuffer buffer, boolean close, boolean compress)
|
||||
{
|
||||
super(close, compress);
|
||||
this.buffer = buffer;
|
||||
this.length = buffer.remaining();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length()
|
||||
{
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available()
|
||||
{
|
||||
return buffer.remaining();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInto(ByteBuffer output)
|
||||
{
|
||||
int space = output.remaining();
|
||||
if (available() > space)
|
||||
{
|
||||
int limit = buffer.limit();
|
||||
buffer.limit(buffer.position() + space);
|
||||
output.put(buffer);
|
||||
buffer.limit(limit);
|
||||
}
|
||||
else
|
||||
{
|
||||
space = buffer.remaining();
|
||||
output.put(buffer);
|
||||
}
|
||||
return space;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* <p>Specialized {@link DataInfo} for byte array content.</p>
|
||||
*/
|
||||
public class BytesDataInfo extends DataInfo
|
||||
{
|
||||
private byte[] bytes;
|
||||
private int offset;
|
||||
|
||||
public BytesDataInfo(byte[] bytes, boolean close)
|
||||
{
|
||||
this(bytes, close, false);
|
||||
}
|
||||
|
||||
public BytesDataInfo(byte[] bytes, boolean close, boolean compress)
|
||||
{
|
||||
super(close, compress);
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length()
|
||||
{
|
||||
return bytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available()
|
||||
{
|
||||
return length() - offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInto(ByteBuffer output)
|
||||
{
|
||||
int space = output.remaining();
|
||||
int length = Math.min(available(), space);
|
||||
output.put(bytes, offset, length);
|
||||
offset += length;
|
||||
return length;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* <p>A container for DATA frames metadata and content bytes.</p>
|
||||
* <p>Specialized subclasses (like {@link StringDataInfo}) may be used by applications
|
||||
* to send specific types of content.</p>
|
||||
* <p>Applications may send multiple instances of {@link DataInfo}, usually of the same
|
||||
* type, via {@link Stream#data(DataInfo)}. The last instance must have the
|
||||
* {@link #isClose() close flag} set, so that the client knows that no more content is
|
||||
* expected.</p>
|
||||
* <p>Receivers of {@link DataInfo} via {@link StreamFrameListener#onData(Stream, DataInfo)}
|
||||
* have two different APIs to read the data content bytes: a {@link #readInto(ByteBuffer) read}
|
||||
* API that does not interact with flow control, and a {@link #consumeInto(ByteBuffer) drain}
|
||||
* API that interacts with flow control.</p>
|
||||
* <p>Flow control is defined so that when the sender wants to sends a number of bytes larger
|
||||
* than the {@link Settings.ID#INITIAL_WINDOW_SIZE} value, it will stop sending as soon as it
|
||||
* has sent a number of bytes equal to the window size. The receiver has to <em>consume</em>
|
||||
* the data bytes that it received in order to tell the sender to send more bytes.</p>
|
||||
* <p>Consuming the data bytes can be done only via {@link #consumeInto(ByteBuffer)} or by a combination
|
||||
* of {@link #readInto(ByteBuffer)} and {@link #consume(int)} (possibly at different times).</p>
|
||||
*/
|
||||
public abstract class DataInfo
|
||||
{
|
||||
/**
|
||||
* <p>Flag that indicates that this {@link DataInfo} is the last frame in the stream.</p>
|
||||
*
|
||||
* @see #isClose()
|
||||
* @see #getFlags()
|
||||
*/
|
||||
public final static byte FLAG_CLOSE = 1;
|
||||
/**
|
||||
* <p>Flag that indicates that this {@link DataInfo}'s data is compressed.</p>
|
||||
*
|
||||
* @see #isCompress()
|
||||
* @see #getFlags()
|
||||
*/
|
||||
public final static byte FLAG_COMPRESS = 2;
|
||||
|
||||
private final AtomicInteger consumed = new AtomicInteger();
|
||||
private boolean close;
|
||||
private boolean compress;
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link DataInfo} with the given close flag and no compression flag.</p>
|
||||
*
|
||||
* @param close the value of the close flag
|
||||
*/
|
||||
public DataInfo(boolean close)
|
||||
{
|
||||
setClose(close);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link DataInfo} with the given close flag and given compression flag.</p>
|
||||
*
|
||||
* @param close the close flag
|
||||
* @param compress the compress flag
|
||||
*/
|
||||
public DataInfo(boolean close, boolean compress)
|
||||
{
|
||||
setClose(close);
|
||||
setCompress(compress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of the compress flag
|
||||
* @see #setCompress(boolean)
|
||||
*/
|
||||
public boolean isCompress()
|
||||
{
|
||||
return compress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param compress the value of the compress flag
|
||||
* @see #isCompress()
|
||||
*/
|
||||
public void setCompress(boolean compress)
|
||||
{
|
||||
this.compress = compress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of the close flag
|
||||
* @see #setClose(boolean)
|
||||
*/
|
||||
public boolean isClose()
|
||||
{
|
||||
return close;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param close the value of the close flag
|
||||
* @see #isClose()
|
||||
*/
|
||||
public void setClose(boolean close)
|
||||
{
|
||||
this.close = close;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the close and compress flags as integer
|
||||
* @see #FLAG_CLOSE
|
||||
* @see #FLAG_COMPRESS
|
||||
*/
|
||||
public byte getFlags()
|
||||
{
|
||||
byte flags = isClose() ? FLAG_CLOSE : 0;
|
||||
flags |= isCompress() ? FLAG_COMPRESS : 0;
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the total number of content bytes
|
||||
* @see #available()
|
||||
*/
|
||||
public abstract int length();
|
||||
|
||||
/**
|
||||
* <p>Returns the available content bytes that can be read via {@link #readInto(ByteBuffer)}.</p>
|
||||
* <p>Each invocation to {@link #readInto(ByteBuffer)} modifies the value returned by this method,
|
||||
* until no more content bytes are available.</p>
|
||||
*
|
||||
* @return the available content bytes
|
||||
* @see #readInto(ByteBuffer)
|
||||
*/
|
||||
public abstract int available();
|
||||
|
||||
/**
|
||||
* <p>Copies the content bytes of this {@link DataInfo} into the given {@link ByteBuffer}.</p>
|
||||
* <p>If the given {@link ByteBuffer} cannot contain the whole content of this {@link DataInfo}
|
||||
* then after the read {@link #available()} will return a positive value, and further content
|
||||
* may be retrieved by invoking again this method with a new output buffer.</p>
|
||||
*
|
||||
* @param output the {@link ByteBuffer} to copy to bytes into
|
||||
* @return the number of bytes copied
|
||||
* @see #available()
|
||||
* @see #consumeInto(ByteBuffer)
|
||||
*/
|
||||
public abstract int readInto(ByteBuffer output);
|
||||
|
||||
/**
|
||||
* <p>Reads and consumes the content bytes of this {@link DataInfo} into the given {@link ByteBuffer}.</p>
|
||||
*
|
||||
* @param output the {@link ByteBuffer} to copy the bytes into
|
||||
* @return the number of bytes copied
|
||||
* @see #consume(int)
|
||||
*/
|
||||
public int consumeInto(ByteBuffer output)
|
||||
{
|
||||
int read = readInto(output);
|
||||
consume(read);
|
||||
return read;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Consumes the given number of bytes from this {@link DataInfo}.</p>
|
||||
*
|
||||
* @param delta the number of bytes consumed
|
||||
*/
|
||||
public void consume(int delta)
|
||||
{
|
||||
if (delta < 0)
|
||||
throw new IllegalArgumentException();
|
||||
int read = length() - available();
|
||||
int newConsumed = consumed() + delta;
|
||||
// if (newConsumed > read)
|
||||
// throw new IllegalStateException("Consuming without reading: consumed " + newConsumed + " but only read " + read);
|
||||
consumed.addAndGet(delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of bytes consumed
|
||||
*/
|
||||
public int consumed()
|
||||
{
|
||||
return consumed.get();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param charset the charset used to convert the bytes
|
||||
* @param consume whether to consume the content
|
||||
* @return a String with the content of this {@link DataInfo}
|
||||
*/
|
||||
public String asString(String charset, boolean consume)
|
||||
{
|
||||
ByteBuffer buffer = asByteBuffer(consume);
|
||||
return Charset.forName(charset).decode(buffer).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a byte array with the content of this {@link DataInfo}
|
||||
* @param consume whether to consume the content
|
||||
*/
|
||||
public byte[] asBytes(boolean consume)
|
||||
{
|
||||
ByteBuffer buffer = asByteBuffer(consume);
|
||||
byte[] result = new byte[buffer.remaining()];
|
||||
buffer.get(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a {@link ByteBuffer} with the content of this {@link DataInfo}
|
||||
* @param consume whether to consume the content
|
||||
*/
|
||||
public ByteBuffer asByteBuffer(boolean consume)
|
||||
{
|
||||
ByteBuffer buffer = allocate(available());
|
||||
if (consume)
|
||||
consumeInto(buffer);
|
||||
else
|
||||
readInto(buffer);
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
protected ByteBuffer allocate(int size)
|
||||
{
|
||||
return ByteBuffer.allocate(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("DATA @%x available=%d consumed=%d close=%b compress=%b", hashCode(), available(), consumed(), isClose(), isCompress());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>A container for GOAWAY frames metadata: the last good stream id and
|
||||
* the session status.</p>
|
||||
*/
|
||||
public class GoAwayInfo
|
||||
{
|
||||
private final int lastStreamId;
|
||||
private final SessionStatus sessionStatus;
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link GoAwayInfo} with the given last good stream id and session status</p>
|
||||
*
|
||||
* @param lastStreamId the last good stream id
|
||||
* @param sessionStatus the session status
|
||||
*/
|
||||
public GoAwayInfo(int lastStreamId, SessionStatus sessionStatus)
|
||||
{
|
||||
this.lastStreamId = lastStreamId;
|
||||
this.sessionStatus = sessionStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the last good stream id
|
||||
*/
|
||||
public int getLastStreamId()
|
||||
{
|
||||
return lastStreamId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the session status
|
||||
*/
|
||||
public SessionStatus getSessionStatus()
|
||||
{
|
||||
return sessionStatus;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>A container for name/value pairs, known as headers.</p>
|
||||
* <p>A {@link Header} is composed of a case-insensitive name string and
|
||||
* of a case-sensitive set of value strings.</p>
|
||||
* <p>The implementation of this class is not thread safe.</p>
|
||||
*/
|
||||
public class Headers implements Iterable<Headers.Header>
|
||||
{
|
||||
private final Map<String, Header> headers;
|
||||
|
||||
/**
|
||||
* <p>Creates an empty modifiable {@link Headers} instance.</p>
|
||||
* @see #Headers(Headers, boolean)
|
||||
*/
|
||||
public Headers()
|
||||
{
|
||||
headers = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link Headers} instance by copying the headers from the given
|
||||
* {@link Headers} and making it (im)mutable depending on the given {@code immutable} parameter</p>
|
||||
*
|
||||
* @param original the {@link Headers} to copy headers from
|
||||
* @param immutable whether this instance is immutable
|
||||
*/
|
||||
public Headers(Headers original, boolean immutable)
|
||||
{
|
||||
Map<String, Header> copy = new LinkedHashMap<>();
|
||||
copy.putAll(original.headers);
|
||||
headers = immutable ? Collections.unmodifiableMap(copy) : copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Headers that = (Headers)obj;
|
||||
return headers.equals(that.headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return headers.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a set of header names
|
||||
*/
|
||||
public Set<String> names()
|
||||
{
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
for (Header header : headers.values())
|
||||
result.add(header.name);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name the header name
|
||||
* @return the {@link Header} with the given name, or null if no such header exists
|
||||
*/
|
||||
public Header get(String name)
|
||||
{
|
||||
return headers.get(name.trim().toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Inserts or replaces the given name/value pair as a single-valued {@link Header}.</p>
|
||||
*
|
||||
* @param name the header name
|
||||
* @param value the header value
|
||||
*/
|
||||
public void put(String name, String value)
|
||||
{
|
||||
name = name.trim();
|
||||
Header header = new Header(name, value.trim());
|
||||
headers.put(name.toLowerCase(), header);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Inserts or replaces the given {@link Header}, mapped to the {@link Header#name() header's name}</p>
|
||||
*
|
||||
* @param header the header to add
|
||||
*/
|
||||
public void put(Header header)
|
||||
{
|
||||
if (header != null)
|
||||
headers.put(header.name().toLowerCase(), header);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds the given value to a header with the given name, creating a {@link Header} is none exists
|
||||
* for the given name.</p>
|
||||
*
|
||||
* @param name the header name
|
||||
* @param value the header value to add
|
||||
*/
|
||||
public void add(String name, String value)
|
||||
{
|
||||
name = name.trim();
|
||||
Header header = headers.get(name.toLowerCase());
|
||||
if (header == null)
|
||||
{
|
||||
header = new Header(name, value.trim());
|
||||
headers.put(name.toLowerCase(), header);
|
||||
}
|
||||
else
|
||||
{
|
||||
header = new Header(header.name(), header.value() + "," + value.trim());
|
||||
headers.put(name.toLowerCase(), header);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Removes the {@link Header} with the given name</p>
|
||||
*
|
||||
* @param name the name of the header to remove
|
||||
* @return the removed header, or null if no such header existed
|
||||
*/
|
||||
public Header remove(String name)
|
||||
{
|
||||
name = name.trim();
|
||||
return headers.remove(name.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Empties this {@link Headers} instance from all headers</p>
|
||||
* @see #isEmpty()
|
||||
*/
|
||||
public void clear()
|
||||
{
|
||||
headers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this {@link Headers} instance is empty
|
||||
*/
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return headers.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of headers
|
||||
*/
|
||||
public int size()
|
||||
{
|
||||
return headers.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an iterator over the {@link Header} present in this instance
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Header> iterator()
|
||||
{
|
||||
return headers.values().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return headers.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>A named list of string values.</p>
|
||||
* <p>The name is case-sensitive and there must be at least one value.</p>
|
||||
*/
|
||||
public static class Header
|
||||
{
|
||||
private final String name;
|
||||
private final String[] values;
|
||||
|
||||
private Header(String name, String value, String... values)
|
||||
{
|
||||
this.name = name;
|
||||
this.values = new String[values.length + 1];
|
||||
this.values[0] = value;
|
||||
if (values.length > 0)
|
||||
System.arraycopy(values, 0, this.values, 1, values.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Header that = (Header)obj;
|
||||
return name.equals(that.name) && Arrays.equals(values, that.values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int result = name.hashCode();
|
||||
result = 31 * result + Arrays.hashCode(values);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the header's name
|
||||
*/
|
||||
public String name()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first header's value
|
||||
*/
|
||||
public String value()
|
||||
{
|
||||
return values[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Attempts to convert the result of {@link #value()} to an integer,
|
||||
* returning it if the conversion is successful; returns null if the
|
||||
* result of {@link #value()} is null.</p>
|
||||
*
|
||||
* @return the result of {@link #value()} converted to an integer, or null
|
||||
* @throws NumberFormatException if the conversion fails
|
||||
*/
|
||||
public Integer valueAsInt()
|
||||
{
|
||||
final String value = value();
|
||||
return value == null ? null : Integer.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the header's values
|
||||
*/
|
||||
public String[] values()
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the header has multiple values
|
||||
*/
|
||||
public boolean hasMultipleValues()
|
||||
{
|
||||
return values.length > 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return Arrays.toString(values);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>A container for HEADERS frame metadata and headers.</p>
|
||||
*/
|
||||
public class HeadersInfo
|
||||
{
|
||||
/**
|
||||
* <p>Flag that indicates that this {@link HeadersInfo} is the last frame in the stream.</p>
|
||||
*
|
||||
* @see #isClose()
|
||||
* @see #getFlags()
|
||||
*/
|
||||
public static final byte FLAG_CLOSE = 1;
|
||||
/**
|
||||
* <p>Flag that indicates that the compression of the stream must be reset.</p>
|
||||
*
|
||||
* @see #isResetCompression()
|
||||
* @see #getFlags()
|
||||
*/
|
||||
public static final byte FLAG_RESET_COMPRESSION = 2;
|
||||
|
||||
private final boolean close;
|
||||
private final boolean resetCompression;
|
||||
private final Headers headers;
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link HeadersInfo} instance with the given headers,
|
||||
* the given close flag and no reset compression flag</p>
|
||||
*
|
||||
* @param headers the {@link Headers}
|
||||
* @param close the value of the close flag
|
||||
*/
|
||||
public HeadersInfo(Headers headers, boolean close)
|
||||
{
|
||||
this(headers, close, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link HeadersInfo} instance with the given headers,
|
||||
* the given close flag and the given reset compression flag</p>
|
||||
*
|
||||
* @param headers the {@link Headers}
|
||||
* @param close the value of the close flag
|
||||
* @param resetCompression the value of the reset compression flag
|
||||
*/
|
||||
public HeadersInfo(Headers headers, boolean close, boolean resetCompression)
|
||||
{
|
||||
this.headers = headers;
|
||||
this.close = close;
|
||||
this.resetCompression = resetCompression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of the close flag
|
||||
*/
|
||||
public boolean isClose()
|
||||
{
|
||||
return close;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of the reset compression flag
|
||||
*/
|
||||
public boolean isResetCompression()
|
||||
{
|
||||
return resetCompression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link Headers}
|
||||
*/
|
||||
public Headers getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the close and reset compression flags as integer
|
||||
* @see #FLAG_CLOSE
|
||||
* @see #FLAG_RESET_COMPRESSION
|
||||
*/
|
||||
public byte getFlags()
|
||||
{
|
||||
byte flags = isClose() ? FLAG_CLOSE : 0;
|
||||
flags += isResetCompression() ? FLAG_RESET_COMPRESSION : 0;
|
||||
return flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("HEADER close=%b %s", close, headers);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>A container for PING frames data.</p>
|
||||
*/
|
||||
public class PingInfo
|
||||
{
|
||||
private final int pingId;
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link PingInfo} with the given ping id</p>
|
||||
* @param pingId the ping id
|
||||
*/
|
||||
public PingInfo(int pingId)
|
||||
{
|
||||
this.pingId = pingId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ping id
|
||||
*/
|
||||
public int getPingId()
|
||||
{
|
||||
return pingId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>A container for SYN_REPLY frames metadata and headers.</p>
|
||||
*/
|
||||
public class ReplyInfo
|
||||
{
|
||||
/**
|
||||
* <p>Flag that indicates that this {@link ReplyInfo} is the last frame in the stream.</p>
|
||||
*
|
||||
* @see #isClose()
|
||||
* @see #getFlags()
|
||||
*/
|
||||
public static final byte FLAG_CLOSE = 1;
|
||||
|
||||
private final Headers headers;
|
||||
private final boolean close;
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link ReplyInfo} instance with empty headers and the given close flag.</p>
|
||||
*
|
||||
* @param close the value of the close flag
|
||||
*/
|
||||
public ReplyInfo(boolean close)
|
||||
{
|
||||
this(new Headers(), close);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link ReplyInfo} instance with the given headers and the given close flag.</p>
|
||||
*
|
||||
* @param headers the {@link Headers}
|
||||
* @param close the value of the close flag
|
||||
*/
|
||||
public ReplyInfo(Headers headers, boolean close)
|
||||
{
|
||||
this.headers = headers;
|
||||
this.close = close;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link Headers}
|
||||
*/
|
||||
public Headers getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of the close flag
|
||||
*/
|
||||
public boolean isClose()
|
||||
{
|
||||
return close;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the close and reset compression flags as integer
|
||||
* @see #FLAG_CLOSE
|
||||
*/
|
||||
public byte getFlags()
|
||||
{
|
||||
return isClose() ? FLAG_CLOSE : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("REPLY close=%b %s", close, headers);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>A container for RST_STREAM frames data: the stream id and the stream status.</p>
|
||||
*/
|
||||
public class RstInfo
|
||||
{
|
||||
private final int streamId;
|
||||
private final StreamStatus streamStatus;
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link RstInfo} with the given stream id and stream status</p>
|
||||
*
|
||||
* @param streamId the stream id
|
||||
* @param streamStatus the stream status
|
||||
*/
|
||||
public RstInfo(int streamId, StreamStatus streamStatus)
|
||||
{
|
||||
this.streamId = streamId;
|
||||
this.streamStatus = streamStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the stream id
|
||||
*/
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the stream status
|
||||
*/
|
||||
public StreamStatus getStreamStatus()
|
||||
{
|
||||
return streamStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("RST stream=%d %s", streamId, streamStatus);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>Helper class that holds useful SPDY constants.</p>
|
||||
*/
|
||||
public class SPDY
|
||||
{
|
||||
/**
|
||||
* <p>Constant that indicates the version 2 of the SPDY protocol</p>
|
||||
*/
|
||||
public static final short V2 = 2;
|
||||
|
||||
/**
|
||||
* <p>Constant that indicates the version 3 of the SPDY protocol</p>
|
||||
*/
|
||||
public static final short V3 = 3;
|
||||
|
||||
private SPDY()
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>An unrecoverable exception that signals to the application that
|
||||
* something wrong happened.</p>
|
||||
*/
|
||||
public class SPDYException extends RuntimeException
|
||||
{
|
||||
public SPDYException()
|
||||
{
|
||||
}
|
||||
|
||||
public SPDYException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SPDYException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public SPDYException(Throwable cause)
|
||||
{
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public SPDYException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)
|
||||
{
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.util.EventListener;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
||||
/**
|
||||
* <p>A {@link Session} represents the client-side endpoint of a SPDY connection to a single origin server.</p>
|
||||
* <p>Once a {@link Session} has been obtained, it can be used to open SPDY streams:</p>
|
||||
* <pre>
|
||||
* Session session = ...;
|
||||
* SynInfo synInfo = new SynInfo(true);
|
||||
* session.syn(synInfo, new Stream.FrameListener.Adapter()
|
||||
* {
|
||||
* public void onReply(Stream stream, ReplyInfo replyInfo)
|
||||
* {
|
||||
* // Stream reply received
|
||||
* }
|
||||
* });
|
||||
* </pre>
|
||||
* <p>A {@link Session} is the active part of the endpoint, and by calling its API applications can generate
|
||||
* events on the connection; conversely {@link SessionFrameListener} is the passive part of the endpoint, and
|
||||
* has callbacks that are invoked when events happen on the connection.</p>
|
||||
*
|
||||
* @see SessionFrameListener
|
||||
*/
|
||||
public interface Session
|
||||
{
|
||||
/**
|
||||
* @return the SPDY protocol version used by this session
|
||||
*/
|
||||
public short getVersion();
|
||||
|
||||
/**
|
||||
* <p>Registers the given {@code listener} to be notified of session events.</p>
|
||||
*
|
||||
* @param listener the listener to register
|
||||
* @see #removeListener(Listener)
|
||||
*/
|
||||
public void addListener(Listener listener);
|
||||
|
||||
/**
|
||||
* <p>Deregisters the give {@code listener} from being notified of session events.</p>
|
||||
*
|
||||
* @param listener the listener to deregister
|
||||
* @see #addListener(Listener)
|
||||
*/
|
||||
public void removeListener(Listener listener);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a SYN_FRAME to create a new {@link Stream SPDY stream}.</p>
|
||||
* <p>Callers may use the returned future to wait for the stream to be created, and
|
||||
* use the stream, for example, to send data frames.</p>
|
||||
*
|
||||
* @param synInfo the metadata to send on stream creation
|
||||
* @param listener the listener to invoke when events happen on the stream just created
|
||||
* @return a future for the stream that will be created
|
||||
* @see #syn(SynInfo, StreamFrameListener, long, TimeUnit, Callback)
|
||||
*/
|
||||
public Future<Stream> syn(SynInfo synInfo, StreamFrameListener listener);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a SYN_FRAME to create a new {@link Stream SPDY stream}.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* stream has been created and use the stream, for example, to send data frames.</p>
|
||||
*
|
||||
* @param synInfo the metadata to send on stream creation
|
||||
* @param listener the listener to invoke when events happen on the stream just created
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param callback the completion handler that gets notified of stream creation
|
||||
* @see #syn(SynInfo, StreamFrameListener)
|
||||
*/
|
||||
public void syn(SynInfo synInfo, StreamFrameListener listener, long timeout, TimeUnit unit, Callback<Stream> callback);
|
||||
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a RST_STREAM to abort a stream.</p>
|
||||
* <p>Callers may use the returned future to wait for the reset to be sent.</p>
|
||||
*
|
||||
* @param rstInfo the metadata to reset the stream
|
||||
* @return a future to wait for the reset to be sent
|
||||
* @see #rst(RstInfo, long, TimeUnit, Callback)
|
||||
*/
|
||||
public Future<Void> rst(RstInfo rstInfo);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a RST_STREAM to abort a stream.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* reset has been actually sent.</p>
|
||||
*
|
||||
* @param rstInfo the metadata to reset the stream
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param callback the completion handler that gets notified of reset's send
|
||||
* @see #rst(RstInfo)
|
||||
*/
|
||||
public void rst(RstInfo rstInfo, long timeout, TimeUnit unit, Callback<Void> callback);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a SETTINGS to configure the SPDY connection.</p>
|
||||
* <p>Callers may use the returned future to wait for the settings to be sent.</p>
|
||||
*
|
||||
* @param settingsInfo the metadata to send
|
||||
* @return a future to wait for the settings to be sent
|
||||
* @see #settings(SettingsInfo, long, TimeUnit, Callback)
|
||||
*/
|
||||
public Future<Void> settings(SettingsInfo settingsInfo);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a SETTINGS to configure the SPDY connection.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* settings has been actually sent.</p>
|
||||
*
|
||||
* @param settingsInfo the metadata to send
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param callback the completion handler that gets notified of settings' send
|
||||
* @see #settings(SettingsInfo)
|
||||
*/
|
||||
public void settings(SettingsInfo settingsInfo, long timeout, TimeUnit unit, Callback<Void> callback);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a PING, normally to measure round-trip time.</p>
|
||||
* <p>Callers may use the returned future to wait for the ping to be sent.</p>
|
||||
*
|
||||
* @return a future for the metadata sent
|
||||
* @see #ping(long, TimeUnit, Callback)
|
||||
*/
|
||||
public Future<PingInfo> ping();
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a PING, normally to measure round-trip time.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* ping has been actually sent.</p>
|
||||
*
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param callback the completion handler that gets notified of ping's send
|
||||
* @see #ping()
|
||||
*/
|
||||
public void ping(long timeout, TimeUnit unit, Callback<PingInfo> callback);
|
||||
|
||||
/**
|
||||
* <p>Closes gracefully this session, sending a GO_AWAY frame and then closing the TCP connection.</p>
|
||||
* <p>Callers may use the returned future to wait for the go away to be sent.</p>
|
||||
*
|
||||
* @return a future to wait for the go away to be sent
|
||||
* @see #goAway(long, TimeUnit, Callback)
|
||||
*/
|
||||
public Future<Void> goAway();
|
||||
|
||||
/**
|
||||
* <p>Closes gracefully this session, sending a GO_AWAY frame and then closing the TCP connection.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* go away has been actually sent.</p>
|
||||
*
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param callback the completion handler that gets notified of go away's send
|
||||
* @see #goAway()
|
||||
*/
|
||||
public void goAway(long timeout, TimeUnit unit, Callback<Void> callback);
|
||||
|
||||
/**
|
||||
* @return the streams currently active in this session
|
||||
*/
|
||||
public Set<Stream> getStreams();
|
||||
|
||||
/**
|
||||
* <p>Super interface for listeners with callbacks that are invoked on specific session events.</p>
|
||||
*/
|
||||
public interface Listener extends EventListener
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Specialized listener that is invoked upon creation and removal of streams.</p>
|
||||
*/
|
||||
public interface StreamListener extends Listener
|
||||
{
|
||||
/**
|
||||
* <p>Callback invoked when a new SPDY stream is created.</p>
|
||||
*
|
||||
* @param stream the stream just created
|
||||
*/
|
||||
public void onStreamCreated(Stream stream);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when a SPDY stream is closed.</p>
|
||||
*
|
||||
* @param stream the stream just closed.
|
||||
*/
|
||||
public void onStreamClosed(Stream stream);
|
||||
|
||||
/**
|
||||
* <p>Empty implementation of {@link StreamListener}.</p>
|
||||
*/
|
||||
public static class Adapter implements StreamListener
|
||||
{
|
||||
@Override
|
||||
public void onStreamCreated(Stream stream)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamClosed(Stream stream)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.util.EventListener;
|
||||
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
/**
|
||||
* <p>A {@link SessionFrameListener} is the passive counterpart of a {@link Session} and receives events happening
|
||||
* on a SPDY session.</p>
|
||||
*
|
||||
* @see Session
|
||||
*/
|
||||
public interface SessionFrameListener extends EventListener
|
||||
{
|
||||
/**
|
||||
* <p>Callback invoked when a request to create a stream has been received.</p>
|
||||
* <p>Application code should implement this method and reply to the stream creation, eventually
|
||||
* sending data:</p>
|
||||
* <pre>
|
||||
* public Stream.FrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
* {
|
||||
* // Do something with the metadata contained in synInfo
|
||||
*
|
||||
* if (stream.isHalfClosed()) // The other peer will not send data
|
||||
* {
|
||||
* stream.reply(new ReplyInfo(false));
|
||||
* stream.data(new StringDataInfo("foo", true));
|
||||
* return null; // Not interested in further stream events
|
||||
* }
|
||||
*
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
* <p>Alternatively, if the stream creation requires reading data sent from the other peer:</p>
|
||||
* <pre>
|
||||
* public Stream.FrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
* {
|
||||
* // Do something with the metadata contained in synInfo
|
||||
*
|
||||
* if (!stream.isHalfClosed()) // The other peer will send data
|
||||
* {
|
||||
* stream.reply(new ReplyInfo(true));
|
||||
* return new Stream.FrameListener.Adapter() // Interested in stream events
|
||||
* {
|
||||
* public void onData(Stream stream, DataInfo dataInfo)
|
||||
* {
|
||||
* // Do something with the incoming data in dataInfo
|
||||
* }
|
||||
* };
|
||||
* }
|
||||
*
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param stream the stream just created
|
||||
* @param synInfo the metadata sent on stream creation
|
||||
* @return a listener for stream events, or null if there is no interest in being notified of stream events
|
||||
*/
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when a stream error happens.</p>
|
||||
*
|
||||
* @param session the session
|
||||
* @param rstInfo the metadata of the stream error
|
||||
*/
|
||||
public void onRst(Session session, RstInfo rstInfo);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when a request to configure the SPDY connection has been received.</p>
|
||||
*
|
||||
* @param session the session
|
||||
* @param settingsInfo the metadata sent to configure
|
||||
*/
|
||||
public void onSettings(Session session, SettingsInfo settingsInfo);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when a ping request has completed its round-trip.</p>
|
||||
*
|
||||
* @param session the session
|
||||
* @param pingInfo the metadata received
|
||||
*/
|
||||
public void onPing(Session session, PingInfo pingInfo);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when the other peer signals that it is closing the connection.</p>
|
||||
*
|
||||
* @param session the session
|
||||
* @param goAwayInfo the metadata sent
|
||||
*/
|
||||
public void onGoAway(Session session, GoAwayInfo goAwayInfo);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when an exception is thrown during the processing of an event on a
|
||||
* SPDY session.</p>
|
||||
* <p>Examples of such conditions are invalid frames received, corrupted headers compression state, etc.</p>
|
||||
*
|
||||
* @param x the exception that caused the event processing failure
|
||||
*/
|
||||
public void onException(Throwable x);
|
||||
|
||||
/**
|
||||
* <p>Empty implementation of {@link SessionFrameListener}</p>
|
||||
*/
|
||||
public static class Adapter implements SessionFrameListener
|
||||
{
|
||||
private static final Logger logger = Log.getLogger(Adapter.class);
|
||||
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRst(Session session, RstInfo rstInfo)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSettings(Session session, SettingsInfo settingsInfo)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPing(Session session, PingInfo pingInfo)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGoAway(Session session, GoAwayInfo goAwayInfo)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Throwable x)
|
||||
{
|
||||
logger.info("", x);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>An enumeration of session statuses.</p>
|
||||
*/
|
||||
public enum SessionStatus
|
||||
{
|
||||
/**
|
||||
* <p>The session status indicating no errors</p>
|
||||
*/
|
||||
OK(0),
|
||||
/**
|
||||
* <p>The session status indicating a protocol error</p>
|
||||
*/
|
||||
PROTOCOL_ERROR(1);
|
||||
|
||||
/**
|
||||
* @param code the session status code
|
||||
* @return a {@link SessionStatus} from the given code,
|
||||
* or null if no status exists
|
||||
*/
|
||||
public static SessionStatus from(int code)
|
||||
{
|
||||
return Codes.codes.get(code);
|
||||
}
|
||||
|
||||
private final int code;
|
||||
|
||||
private SessionStatus(int code)
|
||||
{
|
||||
this.code = code;
|
||||
Codes.codes.put(code, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the code of this {@link SessionStatus}
|
||||
*/
|
||||
public int getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
private static class Codes
|
||||
{
|
||||
private static final Map<Integer, SessionStatus> codes = new HashMap<>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
public class Settings implements Iterable<Settings.Setting>
|
||||
{
|
||||
private Map<ID, Settings.Setting> settings = new HashMap<>();
|
||||
|
||||
public Settings()
|
||||
{
|
||||
}
|
||||
|
||||
public Settings(Settings original, boolean immutable)
|
||||
{
|
||||
Map<ID, Settings.Setting> copy = new HashMap<>(original.size());
|
||||
copy.putAll(original.settings);
|
||||
settings = immutable ? Collections.unmodifiableMap(copy) : copy;
|
||||
}
|
||||
|
||||
public Setting get(ID id)
|
||||
{
|
||||
return settings.get(id);
|
||||
}
|
||||
|
||||
public void put(Setting setting)
|
||||
{
|
||||
settings.put(setting.id(), setting);
|
||||
}
|
||||
|
||||
public Setting remove(ID id)
|
||||
{
|
||||
return settings.remove(id);
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
return settings.size();
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
settings.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Settings that = (Settings)obj;
|
||||
return settings.equals(that.settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return settings.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Setting> iterator()
|
||||
{
|
||||
return settings.values().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return settings.toString();
|
||||
}
|
||||
|
||||
public static final class ID
|
||||
{
|
||||
public static ID UPLOAD_BANDWIDTH = new ID(1);
|
||||
public static ID DOWNLOAD_BANDWIDTH = new ID(2);
|
||||
public static ID ROUND_TRIP_TIME = new ID(3);
|
||||
public static ID MAX_CONCURRENT_STREAMS = new ID(4);
|
||||
public static ID CURRENT_CONGESTION_WINDOW = new ID(5);
|
||||
public static ID DOWNLOAD_RETRANSMISSION_RATE = new ID(6);
|
||||
public static ID INITIAL_WINDOW_SIZE = new ID(7);
|
||||
|
||||
public synchronized static ID from(int code)
|
||||
{
|
||||
ID id = Codes.codes.get(code);
|
||||
if (id == null)
|
||||
id = new ID(code);
|
||||
return id;
|
||||
}
|
||||
|
||||
private final int code;
|
||||
|
||||
private ID(int code)
|
||||
{
|
||||
this.code = code;
|
||||
Codes.codes.put(code, this);
|
||||
}
|
||||
|
||||
public int code()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.valueOf(code);
|
||||
}
|
||||
|
||||
private static class Codes
|
||||
{
|
||||
private static final Map<Integer, ID> codes = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static enum Flag
|
||||
{
|
||||
NONE((byte)0),
|
||||
PERSIST((byte)1),
|
||||
PERSISTED((byte)2);
|
||||
|
||||
public static Flag from(byte code)
|
||||
{
|
||||
return Codes.codes.get(code);
|
||||
}
|
||||
|
||||
private final byte code;
|
||||
|
||||
private Flag(byte code)
|
||||
{
|
||||
this.code = code;
|
||||
Codes.codes.put(code, this);
|
||||
}
|
||||
|
||||
public byte code()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
private static class Codes
|
||||
{
|
||||
private static final Map<Byte, Flag> codes = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Setting
|
||||
{
|
||||
private final ID id;
|
||||
private final Flag flag;
|
||||
private final int value;
|
||||
|
||||
public Setting(ID id, int value)
|
||||
{
|
||||
this(id, Flag.NONE, value);
|
||||
}
|
||||
|
||||
public Setting(ID id, Flag flag, int value)
|
||||
{
|
||||
this.id = id;
|
||||
this.flag = flag;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public ID id()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public Flag flag()
|
||||
{
|
||||
return flag;
|
||||
}
|
||||
|
||||
public int value()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Setting that = (Setting)obj;
|
||||
return value == that.value && flag == that.flag && id == that.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int result = id.hashCode();
|
||||
result = 31 * result + flag.hashCode();
|
||||
result = 31 * result + value;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("[id=%s,flags=%s:value=%d]", id(), flag(), value());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
public class SettingsInfo
|
||||
{
|
||||
public static final byte CLEAR_PERSISTED = 1;
|
||||
|
||||
private final Settings settings;
|
||||
private final boolean clearPersisted;
|
||||
|
||||
public SettingsInfo(Settings settings)
|
||||
{
|
||||
this(settings, false);
|
||||
}
|
||||
|
||||
public SettingsInfo(Settings settings, boolean clearPersisted)
|
||||
{
|
||||
this.settings = settings;
|
||||
this.clearPersisted = clearPersisted;
|
||||
}
|
||||
|
||||
public boolean isClearPersisted()
|
||||
{
|
||||
return clearPersisted;
|
||||
}
|
||||
|
||||
public byte getFlags()
|
||||
{
|
||||
return isClearPersisted() ? CLEAR_PERSISTED : 0;
|
||||
}
|
||||
|
||||
public Settings getSettings()
|
||||
{
|
||||
return settings;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.nio.channels.WritePendingException;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
||||
/**
|
||||
* <p>A {@link Stream} represents a bidirectional exchange of data on top of a {@link Session}.</p>
|
||||
* <p>Differently from socket streams, where the input and output streams are permanently associated
|
||||
* with the socket (and hence with the connection that the socket represents), there can be multiple
|
||||
* SPDY streams for a SPDY session.</p>
|
||||
* <p>SPDY streams may terminate without this implying that the SPDY session is terminated.</p>
|
||||
* <p>If SPDY is used to transport the HTTP protocol, then a SPDY stream maps to a HTTP request/response
|
||||
* cycle, and after the request/response cycle is completed, the stream is closed, and other streams
|
||||
* may be opened. Differently from HTTP, though, multiple SPDY streams may be opened concurrently
|
||||
* on the same SPDY session.</p>
|
||||
* <p>Like {@link Session}, {@link Stream} is the active part and by calling its API applications
|
||||
* can generate events on the stream; conversely, {@link StreamFrameListener} is the passive part, and its
|
||||
* callbacks are invoked when events happen on the stream.</p>
|
||||
* <p>A {@link Stream} can send multiple data frames one after the other but implementations use a
|
||||
* flow control mechanism that only sends the data frames if the other end has signalled that it can
|
||||
* accept the frame.</p>
|
||||
* <p>Data frames should be sent sequentially only when the previous frame has been completely sent.
|
||||
* The reason for this requirement is to avoid potentially confusing code such as:</p>
|
||||
* <pre>
|
||||
* // WRONG CODE, DO NOT USE IT
|
||||
* final Stream stream = ...;
|
||||
* stream.data(StringDataInfo("chunk1", false), 5, TimeUnit.SECONDS, new Handler<Void>() { ... });
|
||||
* stream.data(StringDataInfo("chunk2", true), 1, TimeUnit.SECONDS, new Handler<Void>() { ... });
|
||||
* </pre>
|
||||
* <p>where the second call to {@link #data(DataInfo, long, TimeUnit, Callback)} has a timeout smaller
|
||||
* than the previous call.</p>
|
||||
* <p>The behavior of such style of invocations is unspecified (it may even throw an exception - similar
|
||||
* to {@link WritePendingException}).</p>
|
||||
* <p>The correct sending of data frames is the following:</p>
|
||||
* <pre>
|
||||
* final Stream stream = ...;
|
||||
* ...
|
||||
* // Blocking version
|
||||
* stream.data(new StringDataInfo("chunk1", false)).get(1, TimeUnit.SECONDS);
|
||||
* stream.data(new StringDataInfo("chunk2", true)).get(1, TimeUnit.SECONDS);
|
||||
*
|
||||
* // Asynchronous version
|
||||
* stream.data(new StringDataInfo("chunk1", false), 1, TimeUnit.SECONDS, new Handler.Adapter<Void>()
|
||||
* {
|
||||
* public void completed(Void context)
|
||||
* {
|
||||
* stream.data(new StringDataInfo("chunk2", true));
|
||||
* }
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @see StreamFrameListener
|
||||
*/
|
||||
public interface Stream
|
||||
{
|
||||
/**
|
||||
* @return the id of this stream
|
||||
*/
|
||||
public int getId();
|
||||
|
||||
/**
|
||||
* @return the priority of this stream
|
||||
*/
|
||||
public byte getPriority();
|
||||
|
||||
/**
|
||||
* @return the session this stream is associated to
|
||||
*/
|
||||
public Session getSession();
|
||||
|
||||
/**
|
||||
* <p>Initiate a unidirectional spdy pushstream associated to this stream asynchronously<p>
|
||||
* <p>Callers may use the returned future to get the pushstream once it got created</p>
|
||||
*
|
||||
* @param synInfo the metadata to send on stream creation
|
||||
* @return a future containing the stream once it got established
|
||||
* @see #syn(SynInfo, long, TimeUnit, Callback)
|
||||
*/
|
||||
public Future<Stream> syn(SynInfo synInfo);
|
||||
|
||||
/**
|
||||
* <p>Initiate a unidirectional spdy pushstream associated to this stream asynchronously<p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* pushstream has been established.</p>
|
||||
*
|
||||
* @param synInfo the metadata to send on stream creation
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param callback the completion handler that gets notified once the pushstream is established
|
||||
* @see #syn(SynInfo)
|
||||
*/
|
||||
public void syn(SynInfo synInfo, long timeout, TimeUnit unit, Callback<Stream> callback);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a SYN_REPLY frame in response to a SYN_STREAM frame.</p>
|
||||
* <p>Callers may use the returned future to wait for the reply to be actually sent.</p>
|
||||
*
|
||||
* @param replyInfo the metadata to send
|
||||
* @return a future to wait for the reply to be sent
|
||||
* @see #reply(ReplyInfo, long, TimeUnit, Callback)
|
||||
* @see SessionFrameListener#onSyn(Stream, SynInfo)
|
||||
*/
|
||||
public Future<Void> reply(ReplyInfo replyInfo);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a SYN_REPLY frame in response to a SYN_STREAM frame.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* reply has been actually sent.</p>
|
||||
*
|
||||
* @param replyInfo the metadata to send
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param callback the completion handler that gets notified of reply sent
|
||||
* @see #reply(ReplyInfo)
|
||||
*/
|
||||
public void reply(ReplyInfo replyInfo, long timeout, TimeUnit unit, Callback<Void> callback);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a DATA frame on this stream.</p>
|
||||
* <p>DATA frames should always be sent after a SYN_REPLY frame.</p>
|
||||
* <p>Callers may use the returned future to wait for the data to be actually sent.</p>
|
||||
*
|
||||
* @param dataInfo the metadata to send
|
||||
* @return a future to wait for the data to be sent
|
||||
* @see #data(DataInfo, long, TimeUnit, Callback)
|
||||
* @see #reply(ReplyInfo)
|
||||
*/
|
||||
public Future<Void> data(DataInfo dataInfo);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a DATA frame on this stream.</p>
|
||||
* <p>DATA frames should always be sent after a SYN_REPLY frame.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* data has been actually sent.</p>
|
||||
*
|
||||
* @param dataInfo the metadata to send
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param callback the completion handler that gets notified of data sent
|
||||
* @see #data(DataInfo)
|
||||
*/
|
||||
public void data(DataInfo dataInfo, long timeout, TimeUnit unit, Callback<Void> callback);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a HEADER frame on this stream.</p>
|
||||
* <p>HEADERS frames should always be sent after a SYN_REPLY frame.</p>
|
||||
* <p>Callers may use the returned future to wait for the headers to be actually sent.</p>
|
||||
*
|
||||
* @param headersInfo the metadata to send
|
||||
* @return a future to wait for the headers to be sent
|
||||
* @see #headers(HeadersInfo, long, TimeUnit, Callback)
|
||||
* @see #reply(ReplyInfo)
|
||||
*/
|
||||
public Future<Void> headers(HeadersInfo headersInfo);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a HEADER frame on this stream.</p>
|
||||
* <p>HEADERS frames should always be sent after a SYN_REPLY frame.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* headers have been actually sent.</p>
|
||||
*
|
||||
* @param headersInfo the metadata to send
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param callback the completion handler that gets notified of headers sent
|
||||
* @see #headers(HeadersInfo)
|
||||
*/
|
||||
public void headers(HeadersInfo headersInfo, long timeout, TimeUnit unit, Callback<Void> callback);
|
||||
|
||||
/**
|
||||
* @return whether this stream is unidirectional or not
|
||||
*/
|
||||
public boolean isUnidirectional();
|
||||
|
||||
/**
|
||||
* @return whether this stream has been reset
|
||||
*/
|
||||
public boolean isReset();
|
||||
|
||||
/**
|
||||
* @return whether this stream has been closed by both parties
|
||||
* @see #isHalfClosed()
|
||||
*/
|
||||
public boolean isClosed();
|
||||
|
||||
/**
|
||||
* @return whether this stream has been closed by one party only
|
||||
* @see #isClosed()
|
||||
*/
|
||||
public boolean isHalfClosed();
|
||||
|
||||
/**
|
||||
* @param key the attribute key
|
||||
* @return an arbitrary object associated with the given key to this stream
|
||||
* @see #setAttribute(String, Object)
|
||||
*/
|
||||
public Object getAttribute(String key);
|
||||
|
||||
/**
|
||||
* @param key the attribute key
|
||||
* @param value an arbitrary object to associate with the given key to this stream
|
||||
* @see #getAttribute(String)
|
||||
* @see #removeAttribute(String)
|
||||
*/
|
||||
public void setAttribute(String key, Object value);
|
||||
|
||||
/**
|
||||
* @param key the attribute key
|
||||
* @return the arbitrary object associated with the given key to this stream
|
||||
* @see #setAttribute(String, Object)
|
||||
*/
|
||||
public Object removeAttribute(String key);
|
||||
|
||||
/**
|
||||
* @return the associated parent stream or null if this is not an associated stream
|
||||
*/
|
||||
public Stream getAssociatedStream();
|
||||
|
||||
/**
|
||||
* @return associated child streams or an empty set if no associated streams exist
|
||||
*/
|
||||
public Set<Stream> getPushedStreams();
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.util.EventListener;
|
||||
|
||||
/**
|
||||
* <p>A {@link StreamFrameListener} is the passive counterpart of a {@link Stream} and receives
|
||||
* events happening on a SPDY stream.</p>
|
||||
*
|
||||
* @see Stream
|
||||
*/
|
||||
public interface StreamFrameListener extends EventListener
|
||||
{
|
||||
/**
|
||||
* <p>Callback invoked when a reply to a stream creation has been received.</p>
|
||||
* <p>Application code may implement this method to send more data to the other end:</p>
|
||||
* <pre>
|
||||
* public void onReply(Stream stream, ReplyInfo replyInfo)
|
||||
* {
|
||||
* stream.data(new StringDataInfo("content"), true);
|
||||
* }
|
||||
* </pre>
|
||||
* @param stream the stream
|
||||
* @param replyInfo the reply metadata
|
||||
*/
|
||||
public void onReply(Stream stream, ReplyInfo replyInfo);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when headers are received on a stream.</p>
|
||||
*
|
||||
* @param stream the stream
|
||||
* @param headersInfo the headers metadata
|
||||
*/
|
||||
public void onHeaders(Stream stream, HeadersInfo headersInfo);
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when data bytes are received on a stream.</p>
|
||||
* <p>Implementers should be read or consume the content of the
|
||||
* {@link DataInfo} before this method returns.</p>
|
||||
*
|
||||
* @param stream the stream
|
||||
* @param dataInfo the data metadata
|
||||
*/
|
||||
public void onData(Stream stream, DataInfo dataInfo);
|
||||
|
||||
/**
|
||||
* <p>Empty implementation of {@link StreamFrameListener}</p>
|
||||
*/
|
||||
public static class Adapter implements StreamFrameListener
|
||||
{
|
||||
@Override
|
||||
public void onReply(Stream stream, ReplyInfo replyInfo)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeaders(Stream stream, HeadersInfo headersInfo)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onData(Stream stream, DataInfo dataInfo)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>An enumeration of stream statuses.</p>
|
||||
*/
|
||||
public enum StreamStatus
|
||||
{
|
||||
/**
|
||||
* <p>The stream status indicating a protocol error</p>
|
||||
*/
|
||||
PROTOCOL_ERROR(1, 1),
|
||||
/**
|
||||
* <p>The stream status indicating that the stream is not valid</p>
|
||||
*/
|
||||
INVALID_STREAM(2, 2),
|
||||
/**
|
||||
* <p>The stream status indicating that the stream has been refused</p>
|
||||
*/
|
||||
REFUSED_STREAM(3, 3),
|
||||
/**
|
||||
* <p>The stream status indicating that the implementation does not support the SPDY version of the stream</p>
|
||||
*/
|
||||
UNSUPPORTED_VERSION(4, 4),
|
||||
/**
|
||||
* <p>The stream status indicating that the stream is no longer needed</p>
|
||||
*/
|
||||
CANCEL_STREAM(5, 5),
|
||||
/**
|
||||
* <p>The stream status indicating an implementation error</p>
|
||||
*/
|
||||
INTERNAL_ERROR(6, 6),
|
||||
/**
|
||||
* <p>The stream status indicating a flow control error</p>
|
||||
*/
|
||||
FLOW_CONTROL_ERROR(7, 7),
|
||||
/**
|
||||
* <p>The stream status indicating a stream opened more than once</p>
|
||||
*/
|
||||
STREAM_IN_USE(-1, 8),
|
||||
/**
|
||||
* <p>The stream status indicating data on a stream already closed</p>
|
||||
*/
|
||||
STREAM_ALREADY_CLOSED(-1, 9),
|
||||
/**
|
||||
* <p>The stream status indicating credentials not valid</p>
|
||||
*/
|
||||
INVALID_CREDENTIALS(-1, 10),
|
||||
/**
|
||||
* <p>The stream status indicating that the implementation could not support a frame too large</p>
|
||||
*/
|
||||
FRAME_TOO_LARGE(-1, 11);
|
||||
|
||||
/**
|
||||
* @param version the SPDY protocol version
|
||||
* @param code the stream status code
|
||||
* @return a {@link StreamStatus} from the given version and code,
|
||||
* or null if no such status exists
|
||||
*/
|
||||
public static StreamStatus from(short version, int code)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
return Codes.v2Codes.get(code);
|
||||
case SPDY.V3:
|
||||
return Codes.v3Codes.get(code);
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private final int v2Code;
|
||||
private final int v3Code;
|
||||
|
||||
private StreamStatus(int v2Code, int v3Code)
|
||||
{
|
||||
this.v2Code = v2Code;
|
||||
if (v2Code >= 0)
|
||||
Codes.v2Codes.put(v2Code, this);
|
||||
this.v3Code = v3Code;
|
||||
if (v3Code >= 0)
|
||||
Codes.v3Codes.put(v3Code, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param version the SPDY protocol version
|
||||
* @return the stream status code
|
||||
*/
|
||||
public int getCode(short version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
return v2Code;
|
||||
case SPDY.V3:
|
||||
return v3Code;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Codes
|
||||
{
|
||||
private static final Map<Integer, StreamStatus> v2Codes = new HashMap<>();
|
||||
private static final Map<Integer, StreamStatus> v3Codes = new HashMap<>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* <p>Specialized {@link DataInfo} for {@link String} content.</p>
|
||||
*/
|
||||
public class StringDataInfo extends BytesDataInfo
|
||||
{
|
||||
public StringDataInfo(String string, boolean close)
|
||||
{
|
||||
this(string, close, false);
|
||||
}
|
||||
|
||||
public StringDataInfo(String string, boolean close, boolean compress)
|
||||
{
|
||||
super(string.getBytes(Charset.forName("UTF-8")), close, compress);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
/**
|
||||
* <p>A container for SYN_STREAM frames metadata and data.</p>
|
||||
*/
|
||||
public class SynInfo
|
||||
{
|
||||
/**
|
||||
* <p>Flag that indicates that this {@link DataInfo} is the last frame in the stream.</p>
|
||||
*
|
||||
* @see #isClose()
|
||||
* @see #getFlags()
|
||||
*/
|
||||
public static final byte FLAG_CLOSE = 1;
|
||||
|
||||
private final boolean close;
|
||||
private final byte priority;
|
||||
private final Headers headers;
|
||||
|
||||
/**
|
||||
* <p>Creates a new {@link SynInfo} instance with empty headers and the given close flag,
|
||||
* not unidirectional, without associated stream, and with default priority.</p>
|
||||
*
|
||||
* @param close the value of the close flag
|
||||
*/
|
||||
public SynInfo(boolean close)
|
||||
{
|
||||
this(new Headers(), close);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link ReplyInfo} instance with the given headers and the given close flag,
|
||||
* not unidirectional, without associated stream, and with default priority.</p>
|
||||
*
|
||||
* @param headers the {@link Headers}
|
||||
* @param close the value of the close flag
|
||||
*/
|
||||
public SynInfo(Headers headers, boolean close)
|
||||
{
|
||||
this(headers, close, (byte)0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Creates a {@link ReplyInfo} instance with the given headers, the given close flag and with the given priority.
|
||||
* </p>
|
||||
*
|
||||
* @param headers
|
||||
* the {@link Headers}
|
||||
* @param close
|
||||
* the value of the close flag
|
||||
* @param priority
|
||||
* the priority
|
||||
*/
|
||||
public SynInfo(Headers headers, boolean close, byte priority)
|
||||
{
|
||||
this.close = close;
|
||||
this.priority = priority;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of the close flag
|
||||
*/
|
||||
public boolean isClose()
|
||||
{
|
||||
return close;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the priority
|
||||
*/
|
||||
public byte getPriority()
|
||||
{
|
||||
return priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link Headers}
|
||||
*/
|
||||
public Headers getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the close flag as integer
|
||||
* @see #FLAG_CLOSE
|
||||
*/
|
||||
public byte getFlags()
|
||||
{
|
||||
return isClose() ? FLAG_CLOSE : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("SYN close=%b headers=%s", close, headers);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api.server;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.Session;
|
||||
import org.eclipse.jetty.spdy.api.SessionFrameListener;
|
||||
|
||||
/**
|
||||
* <p>Specific, server-side, {@link SessionFrameListener}.</p>
|
||||
* <p>In addition to {@link SessionFrameListener}, this listener adds method
|
||||
* {@link #onConnect(Session)} that is called when a client first connects to the
|
||||
* server and may be used by a server-side application to send a SETTINGS frame
|
||||
* to configure the connection before the client can open any stream.</p>
|
||||
*/
|
||||
public interface ServerSessionFrameListener extends SessionFrameListener
|
||||
{
|
||||
/**
|
||||
* <p>Callback invoked when a client opens a connection.</p>
|
||||
*
|
||||
* @param session the session
|
||||
*/
|
||||
public void onConnect(Session session);
|
||||
|
||||
/**
|
||||
* <p>Empty implementation of {@link ServerSessionFrameListener}</p>
|
||||
*/
|
||||
public static class Adapter extends SessionFrameListener.Adapter implements ServerSessionFrameListener
|
||||
{
|
||||
@Override
|
||||
public void onConnect(Session session)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
public abstract class ControlFrame
|
||||
{
|
||||
public static final int HEADER_LENGTH = 8;
|
||||
|
||||
private final short version;
|
||||
private final ControlFrameType type;
|
||||
private final byte flags;
|
||||
|
||||
public ControlFrame(short version, ControlFrameType type, byte flags)
|
||||
{
|
||||
this.version = version;
|
||||
this.type = type;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public short getVersion()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
public ControlFrameType getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
public byte getFlags()
|
||||
{
|
||||
return flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s frame v%s", getType(), getVersion());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum ControlFrameType
|
||||
{
|
||||
SYN_STREAM((short)1),
|
||||
SYN_REPLY((short)2),
|
||||
RST_STREAM((short)3),
|
||||
SETTINGS((short)4),
|
||||
NOOP((short)5),
|
||||
PING((short)6),
|
||||
GO_AWAY((short)7),
|
||||
HEADERS((short)8),
|
||||
WINDOW_UPDATE((short)9);
|
||||
|
||||
public static ControlFrameType from(short code)
|
||||
{
|
||||
return Codes.codes.get(code);
|
||||
}
|
||||
|
||||
private final short code;
|
||||
|
||||
private ControlFrameType(short code)
|
||||
{
|
||||
this.code = code;
|
||||
Codes.codes.put(code, this);
|
||||
}
|
||||
|
||||
public short getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
private static class Codes
|
||||
{
|
||||
private static final Map<Short, ControlFrameType> codes = new HashMap<>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
|
||||
public class DataFrame
|
||||
{
|
||||
public static final int HEADER_LENGTH = 8;
|
||||
|
||||
private final int streamId;
|
||||
private final byte flags;
|
||||
private final int length;
|
||||
|
||||
public DataFrame(int streamId, byte flags, int length)
|
||||
{
|
||||
this.streamId = streamId;
|
||||
this.flags = flags;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public byte getFlags()
|
||||
{
|
||||
return flags;
|
||||
}
|
||||
|
||||
public int getLength()
|
||||
{
|
||||
return length;
|
||||
}
|
||||
|
||||
public boolean isClose()
|
||||
{
|
||||
return (flags & DataInfo.FLAG_CLOSE) == DataInfo.FLAG_CLOSE;
|
||||
}
|
||||
|
||||
public boolean isCompress()
|
||||
{
|
||||
return (flags & DataInfo.FLAG_COMPRESS) == DataInfo.FLAG_COMPRESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("DATA frame stream=%d length=%d close=%b compress=%b", getStreamId(), getLength(), isClose(), isCompress());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
|
||||
public class GoAwayFrame extends ControlFrame
|
||||
{
|
||||
private final int lastStreamId;
|
||||
private final int statusCode;
|
||||
|
||||
public GoAwayFrame(short version, int lastStreamId, int statusCode)
|
||||
{
|
||||
super(version, ControlFrameType.GO_AWAY, (byte)0);
|
||||
this.lastStreamId = lastStreamId;
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public int getLastStreamId()
|
||||
{
|
||||
return lastStreamId;
|
||||
}
|
||||
|
||||
public int getStatusCode()
|
||||
{
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
SessionStatus sessionStatus = SessionStatus.from(getStatusCode());
|
||||
return String.format("%s last_stream=%d status=%s", super.toString(), getLastStreamId(), sessionStatus == null ? getStatusCode() : sessionStatus);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.HeadersInfo;
|
||||
|
||||
public class HeadersFrame extends ControlFrame
|
||||
{
|
||||
private final int streamId;
|
||||
private final Headers headers;
|
||||
|
||||
public HeadersFrame(short version, byte flags, int streamId, Headers headers)
|
||||
{
|
||||
super(version, ControlFrameType.HEADERS, flags);
|
||||
this.streamId = streamId;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public Headers getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
public boolean isClose()
|
||||
{
|
||||
return (getFlags() & HeadersInfo.FLAG_CLOSE) == HeadersInfo.FLAG_CLOSE;
|
||||
}
|
||||
|
||||
public boolean isResetCompression()
|
||||
{
|
||||
return (getFlags() & HeadersInfo.FLAG_RESET_COMPRESSION) == HeadersInfo.FLAG_RESET_COMPRESSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s stream=%d close=%b reset_compression=%b", super.toString(), getStreamId(), isClose(), isResetCompression());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
|
||||
public class NoOpFrame extends ControlFrame
|
||||
{
|
||||
public NoOpFrame()
|
||||
{
|
||||
super(SPDY.V2, ControlFrameType.NOOP, (byte)0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
public class PingFrame extends ControlFrame
|
||||
{
|
||||
private final int pingId;
|
||||
|
||||
public PingFrame(short version, int pingId)
|
||||
{
|
||||
super(version, ControlFrameType.PING, (byte)0);
|
||||
this.pingId = pingId;
|
||||
}
|
||||
|
||||
public int getPingId()
|
||||
{
|
||||
return pingId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s ping=%d", super.toString(), getPingId());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.StreamStatus;
|
||||
|
||||
public class RstStreamFrame extends ControlFrame
|
||||
{
|
||||
private final int streamId;
|
||||
private final int statusCode;
|
||||
|
||||
public RstStreamFrame(short version, int streamId, int statusCode)
|
||||
{
|
||||
super(version, ControlFrameType.RST_STREAM, (byte)0);
|
||||
this.streamId = streamId;
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public int getStatusCode()
|
||||
{
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StreamStatus streamStatus = StreamStatus.from(getVersion(), getStatusCode());
|
||||
return String.format("%s stream=%d status=%s", super.toString(), getStreamId(), streamStatus == null ? getStatusCode() : streamStatus);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.Settings;
|
||||
import org.eclipse.jetty.spdy.api.SettingsInfo;
|
||||
|
||||
public class SettingsFrame extends ControlFrame
|
||||
{
|
||||
private final Settings settings;
|
||||
|
||||
public SettingsFrame(short version, byte flags, Settings settings)
|
||||
{
|
||||
super(version, ControlFrameType.SETTINGS, flags);
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public boolean isClearPersisted()
|
||||
{
|
||||
return (getFlags() & SettingsInfo.CLEAR_PERSISTED) == SettingsInfo.CLEAR_PERSISTED;
|
||||
}
|
||||
|
||||
public Settings getSettings()
|
||||
{
|
||||
return settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s clear_persisted=%b settings=%s", super.toString(), isClearPersisted(), getSettings());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.ReplyInfo;
|
||||
|
||||
public class SynReplyFrame extends ControlFrame
|
||||
{
|
||||
private final int streamId;
|
||||
private final Headers headers;
|
||||
|
||||
public SynReplyFrame(short version, byte flags, int streamId, Headers headers)
|
||||
{
|
||||
super(version, ControlFrameType.SYN_REPLY, flags);
|
||||
this.streamId = streamId;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public Headers getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
public boolean isClose()
|
||||
{
|
||||
return (getFlags() & ReplyInfo.FLAG_CLOSE) == ReplyInfo.FLAG_CLOSE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s stream=%d close=%b", super.toString(), getStreamId(), isClose());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.PushSynInfo;
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
|
||||
public class SynStreamFrame extends ControlFrame
|
||||
{
|
||||
private final int streamId;
|
||||
private final int associatedStreamId;
|
||||
private final byte priority;
|
||||
private final Headers headers;
|
||||
|
||||
public SynStreamFrame(short version, byte flags, int streamId, int associatedStreamId, byte priority, Headers headers)
|
||||
{
|
||||
super(version, ControlFrameType.SYN_STREAM, flags);
|
||||
this.streamId = streamId;
|
||||
this.associatedStreamId = associatedStreamId;
|
||||
this.priority = priority;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public int getAssociatedStreamId()
|
||||
{
|
||||
return associatedStreamId;
|
||||
}
|
||||
|
||||
public byte getPriority()
|
||||
{
|
||||
return priority;
|
||||
}
|
||||
|
||||
public Headers getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
public boolean isClose()
|
||||
{
|
||||
return (getFlags() & SynInfo.FLAG_CLOSE) == SynInfo.FLAG_CLOSE;
|
||||
}
|
||||
|
||||
public boolean isUnidirectional()
|
||||
{
|
||||
return (getFlags() & PushSynInfo.FLAG_UNIDIRECTIONAL) == PushSynInfo.FLAG_UNIDIRECTIONAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s stream=%d close=%b", super.toString(), getStreamId(), isClose());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
public class WindowUpdateFrame extends ControlFrame
|
||||
{
|
||||
private final int streamId;
|
||||
private final int windowDelta;
|
||||
|
||||
public WindowUpdateFrame(short version, int streamId, int windowDelta)
|
||||
{
|
||||
super(version, ControlFrameType.WINDOW_UPDATE, (byte)0);
|
||||
this.streamId = streamId;
|
||||
this.windowDelta = windowDelta;
|
||||
}
|
||||
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public int getWindowDelta()
|
||||
{
|
||||
return windowDelta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s stream=%d delta=%d", super.toString(), getStreamId(), getWindowDelta());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
|
||||
public abstract class ControlFrameGenerator
|
||||
{
|
||||
private final ByteBufferPool bufferPool;
|
||||
|
||||
protected ControlFrameGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
this.bufferPool = bufferPool;
|
||||
}
|
||||
|
||||
protected ByteBufferPool getByteBufferPool()
|
||||
{
|
||||
return bufferPool;
|
||||
}
|
||||
|
||||
public abstract ByteBuffer generate(ControlFrame frame);
|
||||
|
||||
protected void generateControlFrameHeader(ControlFrame frame, int frameLength, ByteBuffer buffer)
|
||||
{
|
||||
buffer.putShort((short)(0x8000 + frame.getVersion()));
|
||||
buffer.putShort(frame.getType().getCode());
|
||||
int flagsAndLength = frame.getFlags();
|
||||
flagsAndLength <<= 24;
|
||||
flagsAndLength += frameLength;
|
||||
buffer.putInt(flagsAndLength);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.frames.DataFrame;
|
||||
|
||||
public class DataFrameGenerator
|
||||
{
|
||||
private final ByteBufferPool bufferPool;
|
||||
|
||||
public DataFrameGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
this.bufferPool = bufferPool;
|
||||
}
|
||||
|
||||
public ByteBuffer generate(int streamId, int length, DataInfo dataInfo)
|
||||
{
|
||||
ByteBuffer buffer = bufferPool.acquire(DataFrame.HEADER_LENGTH + length, true);
|
||||
buffer.position(DataFrame.HEADER_LENGTH);
|
||||
// Guaranteed to always be >= 0
|
||||
int read = dataInfo.readInto(buffer);
|
||||
|
||||
buffer.putInt(0, streamId & 0x7F_FF_FF_FF);
|
||||
buffer.putInt(4, read & 0x00_FF_FF_FF);
|
||||
|
||||
byte flags = dataInfo.getFlags();
|
||||
if (dataInfo.available() > 0)
|
||||
flags &= ~DataInfo.FLAG_CLOSE;
|
||||
buffer.put(4, flags);
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumMap;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrameType;
|
||||
|
||||
public class Generator
|
||||
{
|
||||
private final EnumMap<ControlFrameType, ControlFrameGenerator> generators = new EnumMap<>(ControlFrameType.class);
|
||||
private final DataFrameGenerator dataFrameGenerator;
|
||||
|
||||
public Generator(ByteBufferPool bufferPool, CompressionFactory.Compressor compressor)
|
||||
{
|
||||
HeadersBlockGenerator headersBlockGenerator = new HeadersBlockGenerator(compressor);
|
||||
generators.put(ControlFrameType.SYN_STREAM, new SynStreamGenerator(bufferPool, headersBlockGenerator));
|
||||
generators.put(ControlFrameType.SYN_REPLY, new SynReplyGenerator(bufferPool, headersBlockGenerator));
|
||||
generators.put(ControlFrameType.RST_STREAM, new RstStreamGenerator(bufferPool));
|
||||
generators.put(ControlFrameType.SETTINGS, new SettingsGenerator(bufferPool));
|
||||
generators.put(ControlFrameType.NOOP, new NoOpGenerator(bufferPool));
|
||||
generators.put(ControlFrameType.PING, new PingGenerator(bufferPool));
|
||||
generators.put(ControlFrameType.GO_AWAY, new GoAwayGenerator(bufferPool));
|
||||
generators.put(ControlFrameType.HEADERS, new HeadersGenerator(bufferPool, headersBlockGenerator));
|
||||
generators.put(ControlFrameType.WINDOW_UPDATE, new WindowUpdateGenerator(bufferPool));
|
||||
|
||||
dataFrameGenerator = new DataFrameGenerator(bufferPool);
|
||||
}
|
||||
|
||||
public ByteBuffer control(ControlFrame frame)
|
||||
{
|
||||
ControlFrameGenerator generator = generators.get(frame.getType());
|
||||
return generator.generate(frame);
|
||||
}
|
||||
|
||||
public ByteBuffer data(int streamId, int length, DataInfo dataInfo)
|
||||
{
|
||||
return dataFrameGenerator.generate(streamId, length, dataInfo);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.GoAwayFrame;
|
||||
|
||||
public class GoAwayGenerator extends ControlFrameGenerator
|
||||
{
|
||||
public GoAwayGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
super(bufferPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
GoAwayFrame goAway = (GoAwayFrame)frame;
|
||||
|
||||
int frameBodyLength = 8;
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(goAway, frameBodyLength, buffer);
|
||||
|
||||
buffer.putInt(goAway.getLastStreamId() & 0x7F_FF_FF_FF);
|
||||
writeStatusCode(goAway, buffer);
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private void writeStatusCode(GoAwayFrame goAway, ByteBuffer buffer)
|
||||
{
|
||||
switch (goAway.getVersion())
|
||||
{
|
||||
case SPDY.V2:
|
||||
break;
|
||||
case SPDY.V3:
|
||||
buffer.putInt(goAway.getStatusCode());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.eclipse.jetty.spdy.CompressionDictionary;
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
|
||||
public class HeadersBlockGenerator
|
||||
{
|
||||
private final CompressionFactory.Compressor compressor;
|
||||
private boolean needsDictionary = true;
|
||||
|
||||
public HeadersBlockGenerator(CompressionFactory.Compressor compressor)
|
||||
{
|
||||
this.compressor = compressor;
|
||||
}
|
||||
|
||||
public ByteBuffer generate(short version, Headers headers)
|
||||
{
|
||||
// TODO: ByteArrayOutputStream is quite inefficient, but grows on demand; optimize using ByteBuffer ?
|
||||
Charset iso1 = Charset.forName("ISO-8859-1");
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(headers.size() * 64);
|
||||
writeCount(version, buffer, headers.size());
|
||||
for (Headers.Header header : headers)
|
||||
{
|
||||
String name = header.name();
|
||||
byte[] nameBytes = name.getBytes(iso1);
|
||||
writeNameLength(version, buffer, nameBytes.length);
|
||||
buffer.write(nameBytes, 0, nameBytes.length);
|
||||
|
||||
// Most common path first
|
||||
String value = header.value();
|
||||
byte[] valueBytes = value.getBytes(iso1);
|
||||
if (header.hasMultipleValues())
|
||||
{
|
||||
String[] values = header.values();
|
||||
for (int i = 1; i < values.length; ++i)
|
||||
{
|
||||
byte[] moreValueBytes = values[i].getBytes(iso1);
|
||||
byte[] newValueBytes = new byte[valueBytes.length + 1 + moreValueBytes.length];
|
||||
System.arraycopy(valueBytes, 0, newValueBytes, 0, valueBytes.length);
|
||||
newValueBytes[valueBytes.length] = 0;
|
||||
System.arraycopy(moreValueBytes, 0, newValueBytes, valueBytes.length + 1, moreValueBytes.length);
|
||||
valueBytes = newValueBytes;
|
||||
}
|
||||
}
|
||||
|
||||
writeValueLength(version, buffer, valueBytes.length);
|
||||
buffer.write(valueBytes, 0, valueBytes.length);
|
||||
}
|
||||
|
||||
return compress(version, buffer.toByteArray());
|
||||
}
|
||||
|
||||
private ByteBuffer compress(short version, byte[] bytes)
|
||||
{
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(bytes.length);
|
||||
|
||||
// The headers compression context is per-session, so we need to synchronize
|
||||
synchronized (compressor)
|
||||
{
|
||||
if (needsDictionary)
|
||||
{
|
||||
compressor.setDictionary(CompressionDictionary.get(version));
|
||||
needsDictionary = false;
|
||||
}
|
||||
|
||||
compressor.setInput(bytes);
|
||||
|
||||
// Compressed bytes may be bigger than input bytes, so we need to loop and accumulate them
|
||||
// Beware that the minimum amount of bytes generated by the compressor is few bytes, so we
|
||||
// need to use an output buffer that is big enough to exit the compress loop
|
||||
buffer.reset();
|
||||
int compressed;
|
||||
byte[] output = new byte[Math.max(256, bytes.length)];
|
||||
while (true)
|
||||
{
|
||||
// SPDY uses the SYNC_FLUSH mode
|
||||
compressed = compressor.compress(output);
|
||||
buffer.write(output, 0, compressed);
|
||||
if (compressed < output.length)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ByteBuffer.wrap(buffer.toByteArray());
|
||||
}
|
||||
|
||||
private void writeCount(short version, ByteArrayOutputStream buffer, int value)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
{
|
||||
buffer.write((value & 0xFF_00) >>> 8);
|
||||
buffer.write(value & 0x00_FF);
|
||||
break;
|
||||
}
|
||||
case SPDY.V3:
|
||||
{
|
||||
buffer.write((value & 0xFF_00_00_00) >>> 24);
|
||||
buffer.write((value & 0x00_FF_00_00) >>> 16);
|
||||
buffer.write((value & 0x00_00_FF_00) >>> 8);
|
||||
buffer.write(value & 0x00_00_00_FF);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// Here the version is trusted to be correct; if it's not
|
||||
// then it's a bug rather than an application error
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeNameLength(short version, ByteArrayOutputStream buffer, int length)
|
||||
{
|
||||
writeCount(version, buffer, length);
|
||||
}
|
||||
|
||||
private void writeValueLength(short version, ByteArrayOutputStream buffer, int length)
|
||||
{
|
||||
writeCount(version, buffer, length);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.SessionException;
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.HeadersFrame;
|
||||
|
||||
public class HeadersGenerator extends ControlFrameGenerator
|
||||
{
|
||||
private final HeadersBlockGenerator headersBlockGenerator;
|
||||
|
||||
public HeadersGenerator(ByteBufferPool bufferPool, HeadersBlockGenerator headersBlockGenerator)
|
||||
{
|
||||
super(bufferPool);
|
||||
this.headersBlockGenerator = headersBlockGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
HeadersFrame headers = (HeadersFrame)frame;
|
||||
short version = headers.getVersion();
|
||||
|
||||
ByteBuffer headersBuffer = headersBlockGenerator.generate(version, headers.getHeaders());
|
||||
|
||||
int frameBodyLength = 4;
|
||||
|
||||
int frameLength = frameBodyLength + headersBuffer.remaining();
|
||||
if (frameLength > 0xFF_FF_FF)
|
||||
{
|
||||
// Too many headers, but unfortunately we have already modified the compression
|
||||
// context, so we have no other choice than tear down the connection.
|
||||
throw new SessionException(SessionStatus.PROTOCOL_ERROR, "Too many headers");
|
||||
}
|
||||
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameLength;
|
||||
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(headers, frameLength, buffer);
|
||||
|
||||
buffer.putInt(headers.getStreamId() & 0x7F_FF_FF_FF);
|
||||
|
||||
buffer.put(headersBuffer);
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.NoOpFrame;
|
||||
|
||||
public class NoOpGenerator extends ControlFrameGenerator
|
||||
{
|
||||
public NoOpGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
super(bufferPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
NoOpFrame noOp = (NoOpFrame)frame;
|
||||
|
||||
int frameBodyLength = 0;
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(noOp, frameBodyLength, buffer);
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.PingFrame;
|
||||
|
||||
public class PingGenerator extends ControlFrameGenerator
|
||||
{
|
||||
public PingGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
super(bufferPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
PingFrame ping = (PingFrame)frame;
|
||||
|
||||
int frameBodyLength = 4;
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(ping, frameBodyLength, buffer);
|
||||
|
||||
buffer.putInt(ping.getPingId());
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.RstStreamFrame;
|
||||
|
||||
public class RstStreamGenerator extends ControlFrameGenerator
|
||||
{
|
||||
public RstStreamGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
super(bufferPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
RstStreamFrame rstStream = (RstStreamFrame)frame;
|
||||
|
||||
int frameBodyLength = 8;
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(rstStream, frameBodyLength, buffer);
|
||||
|
||||
buffer.putInt(rstStream.getStreamId() & 0x7F_FF_FF_FF);
|
||||
buffer.putInt(rstStream.getStatusCode());
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.Settings;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SettingsFrame;
|
||||
|
||||
public class SettingsGenerator extends ControlFrameGenerator
|
||||
{
|
||||
public SettingsGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
super(bufferPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
SettingsFrame settingsFrame = (SettingsFrame)frame;
|
||||
|
||||
Settings settings = settingsFrame.getSettings();
|
||||
int size = settings.size();
|
||||
int frameBodyLength = 4 + 8 * size;
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(settingsFrame, frameBodyLength, buffer);
|
||||
|
||||
buffer.putInt(size);
|
||||
|
||||
for (Settings.Setting setting : settings)
|
||||
{
|
||||
int id = setting.id().code();
|
||||
byte flags = setting.flag().code();
|
||||
int idAndFlags = convertIdAndFlags(frame.getVersion(), id, flags);
|
||||
buffer.putInt(idAndFlags);
|
||||
buffer.putInt(setting.value());
|
||||
}
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private int convertIdAndFlags(short version, int id, byte flags)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
{
|
||||
// In v2 the format is 24 bits of ID + 8 bits of flag
|
||||
int idAndFlags = (id << 8) + (flags & 0xFF);
|
||||
// A bug in the Chromium implementation forces v2 to have
|
||||
// the 3 ID bytes little endian, so we swap first and third
|
||||
int result = idAndFlags & 0x00_FF_00_FF;
|
||||
result += (idAndFlags & 0xFF_00_00_00) >>> 16;
|
||||
result += (idAndFlags & 0x00_00_FF_00) << 16;
|
||||
return result;
|
||||
}
|
||||
case SPDY.V3:
|
||||
{
|
||||
// In v3 the format is 8 bits of flags + 24 bits of ID
|
||||
return (flags << 24) + (id & 0xFF_FF_FF);
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.SessionException;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SynReplyFrame;
|
||||
|
||||
public class SynReplyGenerator extends ControlFrameGenerator
|
||||
{
|
||||
private final HeadersBlockGenerator headersBlockGenerator;
|
||||
|
||||
public SynReplyGenerator(ByteBufferPool bufferPool, HeadersBlockGenerator headersBlockGenerator)
|
||||
{
|
||||
super(bufferPool);
|
||||
this.headersBlockGenerator = headersBlockGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
SynReplyFrame synReply = (SynReplyFrame)frame;
|
||||
short version = synReply.getVersion();
|
||||
|
||||
ByteBuffer headersBuffer = headersBlockGenerator.generate(version, synReply.getHeaders());
|
||||
|
||||
int frameBodyLength = getFrameDataLength(version);
|
||||
|
||||
int frameLength = frameBodyLength + headersBuffer.remaining();
|
||||
if (frameLength > 0xFF_FF_FF)
|
||||
{
|
||||
// Too many headers, but unfortunately we have already modified the compression
|
||||
// context, so we have no other choice than tear down the connection.
|
||||
throw new SessionException(SessionStatus.PROTOCOL_ERROR, "Too many headers");
|
||||
}
|
||||
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameLength;
|
||||
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(synReply, frameLength, buffer);
|
||||
|
||||
buffer.putInt(synReply.getStreamId() & 0x7F_FF_FF_FF);
|
||||
writeAdditional(version, buffer);
|
||||
|
||||
buffer.put(headersBuffer);
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private int getFrameDataLength(short version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
return 6;
|
||||
case SPDY.V3:
|
||||
return 4;
|
||||
default:
|
||||
// Here the version is trusted to be correct; if it's not
|
||||
// then it's a bug rather than an application error
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAdditional(short version, ByteBuffer buffer)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
buffer.putShort((short)0);
|
||||
break;
|
||||
case SPDY.V3:
|
||||
break;
|
||||
default:
|
||||
// Here the version is trusted to be correct; if it's not
|
||||
// then it's a bug rather than an application error
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.SessionException;
|
||||
import org.eclipse.jetty.spdy.StreamException;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
import org.eclipse.jetty.spdy.api.StreamStatus;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SynStreamFrame;
|
||||
|
||||
public class SynStreamGenerator extends ControlFrameGenerator
|
||||
{
|
||||
private final HeadersBlockGenerator headersBlockGenerator;
|
||||
|
||||
public SynStreamGenerator(ByteBufferPool bufferPool, HeadersBlockGenerator headersBlockGenerator)
|
||||
{
|
||||
super(bufferPool);
|
||||
this.headersBlockGenerator = headersBlockGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
SynStreamFrame synStream = (SynStreamFrame)frame;
|
||||
short version = synStream.getVersion();
|
||||
|
||||
ByteBuffer headersBuffer = headersBlockGenerator.generate(version, synStream.getHeaders());
|
||||
|
||||
int frameBodyLength = 10;
|
||||
|
||||
int frameLength = frameBodyLength + headersBuffer.remaining();
|
||||
if (frameLength > 0xFF_FF_FF)
|
||||
{
|
||||
// Too many headers, but unfortunately we have already modified the compression
|
||||
// context, so we have no other choice than tear down the connection.
|
||||
throw new SessionException(SessionStatus.PROTOCOL_ERROR, "Too many headers");
|
||||
}
|
||||
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameLength;
|
||||
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(synStream, frameLength, buffer);
|
||||
|
||||
int streamId = synStream.getStreamId();
|
||||
buffer.putInt(streamId & 0x7F_FF_FF_FF);
|
||||
buffer.putInt(synStream.getAssociatedStreamId() & 0x7F_FF_FF_FF);
|
||||
writePriority(streamId, version, synStream.getPriority(), buffer);
|
||||
|
||||
buffer.put(headersBuffer);
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private void writePriority(int streamId, short version, byte priority, ByteBuffer buffer)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
priority <<= 6;
|
||||
break;
|
||||
case SPDY.V3:
|
||||
priority <<= 5;
|
||||
break;
|
||||
default:
|
||||
throw new StreamException(streamId, StreamStatus.UNSUPPORTED_VERSION);
|
||||
}
|
||||
buffer.put(priority);
|
||||
buffer.put((byte)0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.generator;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.ByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.WindowUpdateFrame;
|
||||
|
||||
public class WindowUpdateGenerator extends ControlFrameGenerator
|
||||
{
|
||||
public WindowUpdateGenerator(ByteBufferPool bufferPool)
|
||||
{
|
||||
super(bufferPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer generate(ControlFrame frame)
|
||||
{
|
||||
WindowUpdateFrame windowUpdate = (WindowUpdateFrame)frame;
|
||||
|
||||
int frameBodyLength = 8;
|
||||
int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
|
||||
ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
|
||||
generateControlFrameHeader(windowUpdate, frameBodyLength, buffer);
|
||||
|
||||
buffer.putInt(windowUpdate.getStreamId() & 0x7F_FF_FF_FF);
|
||||
buffer.putInt(windowUpdate.getWindowDelta() & 0x7F_FF_FF_FF);
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public abstract class ControlFrameBodyParser
|
||||
{
|
||||
public abstract boolean parse(ByteBuffer buffer);
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumMap;
|
||||
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrameType;
|
||||
|
||||
public abstract class ControlFrameParser
|
||||
{
|
||||
private final EnumMap<ControlFrameType, ControlFrameBodyParser> parsers = new EnumMap<>(ControlFrameType.class);
|
||||
private final ControlFrameBodyParser unknownParser = new UnknownControlFrameBodyParser(this);
|
||||
private State state = State.VERSION;
|
||||
private int cursor;
|
||||
private short version;
|
||||
private short type;
|
||||
private byte flags;
|
||||
private int length;
|
||||
private ControlFrameBodyParser parser;
|
||||
|
||||
public ControlFrameParser(CompressionFactory.Decompressor decompressor)
|
||||
{
|
||||
parsers.put(ControlFrameType.SYN_STREAM, new SynStreamBodyParser(decompressor, this));
|
||||
parsers.put(ControlFrameType.SYN_REPLY, new SynReplyBodyParser(decompressor, this));
|
||||
parsers.put(ControlFrameType.RST_STREAM, new RstStreamBodyParser(this));
|
||||
parsers.put(ControlFrameType.SETTINGS, new SettingsBodyParser(this));
|
||||
parsers.put(ControlFrameType.NOOP, new NoOpBodyParser(this));
|
||||
parsers.put(ControlFrameType.PING, new PingBodyParser(this));
|
||||
parsers.put(ControlFrameType.GO_AWAY, new GoAwayBodyParser(this));
|
||||
parsers.put(ControlFrameType.HEADERS, new HeadersBodyParser(decompressor, this));
|
||||
parsers.put(ControlFrameType.WINDOW_UPDATE, new WindowUpdateBodyParser(this));
|
||||
}
|
||||
|
||||
public short getVersion()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
public byte getFlags()
|
||||
{
|
||||
return flags;
|
||||
}
|
||||
|
||||
public int getLength()
|
||||
{
|
||||
return length;
|
||||
}
|
||||
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case VERSION:
|
||||
{
|
||||
if (buffer.remaining() >= 2)
|
||||
{
|
||||
version = (short)(buffer.getShort() & 0x7F_FF);
|
||||
state = State.TYPE;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.VERSION_BYTES;
|
||||
cursor = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VERSION_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
version += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
version &= 0x7F_FF;
|
||||
state = State.TYPE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TYPE:
|
||||
{
|
||||
if (buffer.remaining() >= 2)
|
||||
{
|
||||
type = buffer.getShort();
|
||||
state = State.FLAGS;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.TYPE_BYTES;
|
||||
cursor = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TYPE_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
type += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
state = State.FLAGS;
|
||||
break;
|
||||
}
|
||||
case FLAGS:
|
||||
{
|
||||
flags = buffer.get();
|
||||
cursor = 3;
|
||||
state = State.LENGTH;
|
||||
break;
|
||||
}
|
||||
case LENGTH:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
length += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor > 0)
|
||||
break;
|
||||
|
||||
ControlFrameType controlFrameType = ControlFrameType.from(type);
|
||||
|
||||
// SPEC v3, 2.2.1: unrecognized control frames must be ignored
|
||||
if (controlFrameType == null)
|
||||
parser = unknownParser;
|
||||
else
|
||||
parser = parsers.get(controlFrameType);
|
||||
|
||||
state = State.BODY;
|
||||
|
||||
// We have to let it fall through the next switch:
|
||||
// the NOOP frame has no body and we cannot break
|
||||
// because the buffer may be consumed and we will
|
||||
// never enter the BODY case.
|
||||
}
|
||||
case BODY:
|
||||
{
|
||||
if (parser.parse(buffer))
|
||||
{
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
state = State.VERSION;
|
||||
cursor = 0;
|
||||
version = 0;
|
||||
type = 0;
|
||||
flags = 0;
|
||||
length = 0;
|
||||
parser = null;
|
||||
}
|
||||
|
||||
protected abstract void onControlFrame(ControlFrame frame);
|
||||
|
||||
private enum State
|
||||
{
|
||||
VERSION, VERSION_BYTES, TYPE, TYPE_BYTES, FLAGS, LENGTH, BODY
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.frames.DataFrame;
|
||||
|
||||
public abstract class DataFrameParser
|
||||
{
|
||||
private State state = State.STREAM_ID;
|
||||
private int cursor;
|
||||
private int streamId;
|
||||
private byte flags;
|
||||
private int length;
|
||||
|
||||
/**
|
||||
* <p>Parses the given {@link ByteBuffer} for a data frame.</p>
|
||||
*
|
||||
* @param buffer the {@link ByteBuffer} to parse
|
||||
* @return true if the data frame has been fully parsed, false otherwise
|
||||
*/
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case STREAM_ID:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
streamId = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
state = State.FLAGS;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.STREAM_ID_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STREAM_ID_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
streamId += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
state = State.FLAGS;
|
||||
break;
|
||||
}
|
||||
case FLAGS:
|
||||
{
|
||||
flags = buffer.get();
|
||||
cursor = 3;
|
||||
state = State.LENGTH;
|
||||
break;
|
||||
}
|
||||
case LENGTH:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
length += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor > 0)
|
||||
break;
|
||||
state = State.DATA;
|
||||
// Fall down if length == 0: we can't loop because the buffer
|
||||
// may be empty but we need to invoke the application anyway
|
||||
if (length > 0)
|
||||
break;
|
||||
}
|
||||
case DATA:
|
||||
{
|
||||
// Length can only be at most 3 bytes, which is 16_777_215 i.e. 16 MiB.
|
||||
// However, compliant clients should implement flow control, so it's
|
||||
// unlikely that we will get that 16 MiB chunk.
|
||||
// However, TCP may further split the flow control window, so we may
|
||||
// only have part of the data at this point.
|
||||
|
||||
int size = Math.min(length, buffer.remaining());
|
||||
int limit = buffer.limit();
|
||||
buffer.limit(buffer.position() + size);
|
||||
ByteBuffer bytes = buffer.slice();
|
||||
buffer.limit(limit);
|
||||
buffer.position(buffer.position() + size);
|
||||
length -= size;
|
||||
if (length == 0)
|
||||
{
|
||||
onDataFrame(bytes);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We got only part of the frame data bytes,
|
||||
// so we generate a synthetic data frame
|
||||
onDataFragment(bytes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onDataFrame(ByteBuffer bytes)
|
||||
{
|
||||
DataFrame frame = new DataFrame(streamId, flags, bytes.remaining());
|
||||
onDataFrame(frame, bytes);
|
||||
reset();
|
||||
}
|
||||
|
||||
private void onDataFragment(ByteBuffer bytes)
|
||||
{
|
||||
DataFrame frame = new DataFrame(streamId, (byte)(flags & ~DataInfo.FLAG_CLOSE), bytes.remaining());
|
||||
onDataFrame(frame, bytes);
|
||||
// Do not reset, we're expecting more data
|
||||
}
|
||||
|
||||
protected abstract void onDataFrame(DataFrame frame, ByteBuffer data);
|
||||
|
||||
private void reset()
|
||||
{
|
||||
state = State.STREAM_ID;
|
||||
cursor = 0;
|
||||
streamId = 0;
|
||||
flags = 0;
|
||||
length = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
STREAM_ID, STREAM_ID_BYTES, FLAGS, LENGTH, DATA
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.frames.GoAwayFrame;
|
||||
|
||||
public class GoAwayBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private State state = State.LAST_STREAM_ID;
|
||||
private int cursor;
|
||||
private int lastStreamId;
|
||||
private int statusCode;
|
||||
|
||||
public GoAwayBodyParser(ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case LAST_STREAM_ID:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
lastStreamId = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
switch (controlFrameParser.getVersion())
|
||||
{
|
||||
case SPDY.V2:
|
||||
{
|
||||
onGoAway();
|
||||
return true;
|
||||
}
|
||||
case SPDY.V3:
|
||||
{
|
||||
state = State.STATUS_CODE;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.LAST_STREAM_ID_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LAST_STREAM_ID_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
lastStreamId += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
lastStreamId &= 0x7F_FF_FF_FF;
|
||||
switch (controlFrameParser.getVersion())
|
||||
{
|
||||
case SPDY.V2:
|
||||
{
|
||||
onGoAway();
|
||||
return true;
|
||||
}
|
||||
case SPDY.V3:
|
||||
{
|
||||
state = State.STATUS_CODE;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STATUS_CODE:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
statusCode = buffer.getInt();
|
||||
onGoAway();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.STATUS_CODE_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STATUS_CODE_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
statusCode += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
onGoAway();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onGoAway()
|
||||
{
|
||||
GoAwayFrame frame = new GoAwayFrame(controlFrameParser.getVersion(), lastStreamId, statusCode);
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
state = State.LAST_STREAM_ID;
|
||||
cursor = 0;
|
||||
lastStreamId = 0;
|
||||
statusCode = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
LAST_STREAM_ID, LAST_STREAM_ID_BYTES, STATUS_CODE, STATUS_CODE_BYTES
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.zip.ZipException;
|
||||
|
||||
import org.eclipse.jetty.spdy.CompressionDictionary;
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.SessionException;
|
||||
import org.eclipse.jetty.spdy.StreamException;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
import org.eclipse.jetty.spdy.api.StreamStatus;
|
||||
|
||||
public abstract class HeadersBlockParser
|
||||
{
|
||||
private final CompressionFactory.Decompressor decompressor;
|
||||
private byte[] data;
|
||||
private boolean needsDictionary = true;
|
||||
|
||||
protected HeadersBlockParser(CompressionFactory.Decompressor decompressor)
|
||||
{
|
||||
this.decompressor = decompressor;
|
||||
}
|
||||
|
||||
public boolean parse(int streamId, short version, int length, ByteBuffer buffer)
|
||||
{
|
||||
// Need to be sure that all the compressed data has arrived
|
||||
// Because SPDY uses SYNC_FLUSH mode, and the Java API
|
||||
// does not expose when decompression is finished with this mode
|
||||
// (but only when using NO_FLUSH), then we need to
|
||||
// accumulate the compressed bytes until we have all of them
|
||||
|
||||
boolean accumulated = accumulate(length, buffer);
|
||||
if (!accumulated)
|
||||
return false;
|
||||
|
||||
byte[] compressedHeaders = data;
|
||||
data = null;
|
||||
ByteBuffer decompressedHeaders = decompress(version, compressedHeaders);
|
||||
|
||||
Charset iso1 = Charset.forName("ISO-8859-1");
|
||||
|
||||
// We know the decoded bytes contain the full headers,
|
||||
// so optimize instead of looping byte by byte
|
||||
int count = readCount(version, decompressedHeaders);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
int nameLength = readNameLength(version, decompressedHeaders);
|
||||
if (nameLength == 0)
|
||||
throw new StreamException(streamId, StreamStatus.PROTOCOL_ERROR, "Invalid header name length");
|
||||
byte[] nameBytes = new byte[nameLength];
|
||||
decompressedHeaders.get(nameBytes);
|
||||
String name = new String(nameBytes, iso1);
|
||||
|
||||
int valueLength = readValueLength(version, decompressedHeaders);
|
||||
if (valueLength == 0)
|
||||
throw new StreamException(streamId, StreamStatus.PROTOCOL_ERROR, "Invalid header value length");
|
||||
byte[] valueBytes = new byte[valueLength];
|
||||
decompressedHeaders.get(valueBytes);
|
||||
String value = new String(valueBytes, iso1);
|
||||
// Multi valued headers are separate by NUL
|
||||
String[] values = value.split("\u0000");
|
||||
// Check if there are multiple NULs (section 2.6.9)
|
||||
for (String v : values)
|
||||
if (v.length() == 0)
|
||||
throw new StreamException(streamId, StreamStatus.PROTOCOL_ERROR, "Invalid multi valued header");
|
||||
|
||||
onHeader(name, values);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean accumulate(int length, ByteBuffer buffer)
|
||||
{
|
||||
int remaining = buffer.remaining();
|
||||
if (data == null)
|
||||
{
|
||||
if (remaining < length)
|
||||
{
|
||||
data = new byte[remaining];
|
||||
buffer.get(data);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
data = new byte[length];
|
||||
buffer.get(data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int accumulated = data.length;
|
||||
int needed = length - accumulated;
|
||||
if (remaining < needed)
|
||||
{
|
||||
byte[] local = new byte[accumulated + remaining];
|
||||
System.arraycopy(data, 0, local, 0, accumulated);
|
||||
buffer.get(local, accumulated, remaining);
|
||||
data = local;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] local = new byte[length];
|
||||
System.arraycopy(data, 0, local, 0, accumulated);
|
||||
buffer.get(local, accumulated, needed);
|
||||
data = local;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int readCount(int version, ByteBuffer buffer)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
return buffer.getShort();
|
||||
case SPDY.V3:
|
||||
return buffer.getInt();
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private int readNameLength(int version, ByteBuffer buffer)
|
||||
{
|
||||
return readCount(version, buffer);
|
||||
}
|
||||
|
||||
private int readValueLength(int version, ByteBuffer buffer)
|
||||
{
|
||||
return readCount(version, buffer);
|
||||
}
|
||||
|
||||
protected abstract void onHeader(String name, String[] values);
|
||||
|
||||
private ByteBuffer decompress(short version, byte[] compressed)
|
||||
{
|
||||
// Differently from compression, decompression always happens
|
||||
// non-concurrently because we read and parse with a single
|
||||
// thread, and therefore there is no need for synchronization.
|
||||
|
||||
try
|
||||
{
|
||||
byte[] decompressed = null;
|
||||
byte[] buffer = new byte[compressed.length * 2];
|
||||
decompressor.setInput(compressed);
|
||||
|
||||
while (true)
|
||||
{
|
||||
int count = decompressor.decompress(buffer);
|
||||
if (count == 0)
|
||||
{
|
||||
if (decompressed != null)
|
||||
{
|
||||
return ByteBuffer.wrap(decompressed);
|
||||
}
|
||||
else if (needsDictionary)
|
||||
{
|
||||
decompressor.setDictionary(CompressionDictionary.get(version));
|
||||
needsDictionary = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (count < buffer.length)
|
||||
{
|
||||
if (decompressed == null)
|
||||
{
|
||||
// Only one pass was needed to decompress
|
||||
return ByteBuffer.wrap(buffer, 0, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Last pass needed to decompress, merge decompressed bytes
|
||||
byte[] result = new byte[decompressed.length + count];
|
||||
System.arraycopy(decompressed, 0, result, 0, decompressed.length);
|
||||
System.arraycopy(buffer, 0, result, decompressed.length, count);
|
||||
return ByteBuffer.wrap(result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (decompressed == null)
|
||||
{
|
||||
decompressed = buffer;
|
||||
buffer = new byte[buffer.length];
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] result = new byte[decompressed.length + buffer.length];
|
||||
System.arraycopy(decompressed, 0, result, 0, decompressed.length);
|
||||
System.arraycopy(buffer, 0, result, decompressed.length, buffer.length);
|
||||
decompressed = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ZipException x)
|
||||
{
|
||||
// We had a compression problem, and since the compression context
|
||||
// is per-connection, we need to tear down the connection
|
||||
throw new SessionException(SessionStatus.PROTOCOL_ERROR, x);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.HeadersInfo;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrameType;
|
||||
import org.eclipse.jetty.spdy.frames.HeadersFrame;
|
||||
|
||||
public class HeadersBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final Headers headers = new Headers();
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private final HeadersBlockParser headersBlockParser;
|
||||
private State state = State.STREAM_ID;
|
||||
private int cursor;
|
||||
private int streamId;
|
||||
|
||||
public HeadersBodyParser(CompressionFactory.Decompressor decompressor, ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
this.headersBlockParser = new HeadersHeadersBlockParser(decompressor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case STREAM_ID:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
streamId = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
state = State.HEADERS;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.STREAM_ID_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STREAM_ID_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
streamId += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
streamId &= 0x7F_FF_FF_FF;
|
||||
state = State.HEADERS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HEADERS:
|
||||
{
|
||||
short version = controlFrameParser.getVersion();
|
||||
int length = controlFrameParser.getLength() - 4;
|
||||
if (headersBlockParser.parse(streamId, version, length, buffer))
|
||||
{
|
||||
byte flags = controlFrameParser.getFlags();
|
||||
if (flags != 0 && flags != HeadersInfo.FLAG_CLOSE && flags != HeadersInfo.FLAG_RESET_COMPRESSION)
|
||||
throw new IllegalArgumentException("Invalid flag " + flags + " for frame " + ControlFrameType.HEADERS);
|
||||
|
||||
HeadersFrame frame = new HeadersFrame(version, flags, streamId, new Headers(headers, true));
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
headers.clear();
|
||||
state = State.STREAM_ID;
|
||||
cursor = 0;
|
||||
streamId = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
STREAM_ID, STREAM_ID_BYTES, HEADERS
|
||||
}
|
||||
|
||||
private class HeadersHeadersBlockParser extends HeadersBlockParser
|
||||
{
|
||||
public HeadersHeadersBlockParser(CompressionFactory.Decompressor decompressor)
|
||||
{
|
||||
super(decompressor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHeader(String name, String[] values)
|
||||
{
|
||||
for (String value : values)
|
||||
headers.add(name, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.frames.NoOpFrame;
|
||||
|
||||
public class NoOpBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
|
||||
public NoOpBodyParser(ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
NoOpFrame frame = new NoOpFrame();
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EventListener;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.SessionException;
|
||||
import org.eclipse.jetty.spdy.StreamException;
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.DataFrame;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class Parser
|
||||
{
|
||||
private static final Logger logger = Log.getLogger(Parser.class);
|
||||
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private final DataFrameParser dataFrameParser;
|
||||
private State state = State.CONTROL_BIT;
|
||||
|
||||
public Parser(CompressionFactory.Decompressor decompressor)
|
||||
{
|
||||
// It is important to allocate one decompression context per
|
||||
// SPDY session for the control frames (to decompress the headers)
|
||||
controlFrameParser = new ControlFrameParser(decompressor)
|
||||
{
|
||||
@Override
|
||||
protected void onControlFrame(ControlFrame frame)
|
||||
{
|
||||
logger.debug("Parsed {}", frame);
|
||||
notifyControlFrame(frame);
|
||||
}
|
||||
};
|
||||
dataFrameParser = new DataFrameParser()
|
||||
{
|
||||
@Override
|
||||
protected void onDataFrame(DataFrame frame, ByteBuffer data)
|
||||
{
|
||||
logger.debug("Parsed {}, {} data bytes", frame, data.remaining());
|
||||
notifyDataFrame(frame, data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void addListener(Listener listener)
|
||||
{
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener)
|
||||
{
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
protected void notifyControlFrame(ControlFrame frame)
|
||||
{
|
||||
for (Listener listener : listeners)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onControlFrame(frame);
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifyDataFrame(DataFrame frame, ByteBuffer data)
|
||||
{
|
||||
for (Listener listener : listeners)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onDataFrame(frame, data);
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifyStreamException(StreamException x)
|
||||
{
|
||||
for (Listener listener : listeners)
|
||||
{
|
||||
listener.onStreamException(x);
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifySessionException(SessionException x)
|
||||
{
|
||||
logger.debug("SPDY session exception", x);
|
||||
for (Listener listener : listeners)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onSessionException(x);
|
||||
}
|
||||
catch (Exception xx)
|
||||
{
|
||||
logger.debug("Could not notify listener " + listener, xx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.debug("Parsing {} bytes", buffer.remaining());
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case CONTROL_BIT:
|
||||
{
|
||||
// We must only peek the first byte and not advance the buffer
|
||||
// because the 7 least significant bits may be relevant in data frames
|
||||
int currByte = buffer.get(buffer.position());
|
||||
boolean isControlFrame = (currByte & 0x80) == 0x80;
|
||||
state = isControlFrame ? State.CONTROL_FRAME : State.DATA_FRAME;
|
||||
break;
|
||||
}
|
||||
case CONTROL_FRAME:
|
||||
{
|
||||
if (controlFrameParser.parse(buffer))
|
||||
reset();
|
||||
break;
|
||||
}
|
||||
case DATA_FRAME:
|
||||
{
|
||||
if (dataFrameParser.parse(buffer))
|
||||
reset();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SessionException x)
|
||||
{
|
||||
notifySessionException(x);
|
||||
}
|
||||
catch (StreamException x)
|
||||
{
|
||||
notifyStreamException(x);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
notifySessionException(new SessionException(SessionStatus.PROTOCOL_ERROR, x));
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Be sure to consume after exceptions
|
||||
buffer.position(buffer.limit());
|
||||
}
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
state = State.CONTROL_BIT;
|
||||
}
|
||||
|
||||
public interface Listener extends EventListener
|
||||
{
|
||||
public void onControlFrame(ControlFrame frame);
|
||||
|
||||
public void onDataFrame(DataFrame frame, ByteBuffer data);
|
||||
|
||||
public void onStreamException(StreamException x);
|
||||
|
||||
public void onSessionException(SessionException x);
|
||||
|
||||
public static class Adapter implements Listener
|
||||
{
|
||||
@Override
|
||||
public void onControlFrame(ControlFrame frame)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataFrame(DataFrame frame, ByteBuffer data)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamException(StreamException x)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionException(SessionException x)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
CONTROL_BIT, CONTROL_FRAME, DATA_FRAME
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.frames.PingFrame;
|
||||
|
||||
public class PingBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private State state = State.PING_ID;
|
||||
private int cursor;
|
||||
private int pingId;
|
||||
|
||||
public PingBodyParser(ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case PING_ID:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
pingId = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
onPing();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.PING_ID_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PING_ID_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
pingId += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
onPing();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onPing()
|
||||
{
|
||||
PingFrame frame = new PingFrame(controlFrameParser.getVersion(), pingId);
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
state = State.PING_ID;
|
||||
cursor = 0;
|
||||
pingId = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
PING_ID, PING_ID_BYTES
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.frames.RstStreamFrame;
|
||||
|
||||
public class RstStreamBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private State state = State.STREAM_ID;
|
||||
private int cursor;
|
||||
private int streamId;
|
||||
private int statusCode;
|
||||
|
||||
public RstStreamBodyParser(ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case STREAM_ID:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
streamId = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
state = State.STATUS_CODE;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.STREAM_ID_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STREAM_ID_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
streamId += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
streamId &= 0x7F_FF_FF_FF;
|
||||
state = State.STATUS_CODE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STATUS_CODE:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
statusCode = buffer.getInt();
|
||||
onRstStream();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.STATUS_CODE_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STATUS_CODE_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
statusCode += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
onRstStream();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onRstStream()
|
||||
{
|
||||
// TODO: check that statusCode is not 0
|
||||
RstStreamFrame frame = new RstStreamFrame(controlFrameParser.getVersion(), streamId, statusCode);
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
state = State.STREAM_ID;
|
||||
cursor = 0;
|
||||
streamId = 0;
|
||||
statusCode = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
STREAM_ID, STREAM_ID_BYTES, STATUS_CODE, STATUS_CODE_BYTES
|
||||
}
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.Settings;
|
||||
import org.eclipse.jetty.spdy.frames.SettingsFrame;
|
||||
|
||||
public class SettingsBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final Settings settings = new Settings();
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private State state = State.COUNT;
|
||||
private int cursor;
|
||||
private int count;
|
||||
private int idAndFlags;
|
||||
private int value;
|
||||
|
||||
public SettingsBodyParser(ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case COUNT:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
count = buffer.getInt();
|
||||
state = State.ID_FLAGS;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.COUNT_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case COUNT_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
count += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
state = State.ID_FLAGS;
|
||||
break;
|
||||
}
|
||||
case ID_FLAGS:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
idAndFlags = convertIdAndFlags(controlFrameParser.getVersion(), buffer.getInt());
|
||||
state = State.VALUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.ID_FLAGS_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ID_FLAGS_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
value += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
idAndFlags = convertIdAndFlags(controlFrameParser.getVersion(), value);
|
||||
state = State.VALUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VALUE:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
value = buffer.getInt();
|
||||
if (onPair())
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.VALUE_BYTES;
|
||||
cursor = 4;
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VALUE_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
value += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
if (onPair())
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private int convertIdAndFlags(short version, int idAndFlags)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
{
|
||||
// A bug in the Chromium implementation forces v2 to have
|
||||
// 3 ID bytes little endian + 1 byte of flags
|
||||
// Here we normalize this to conform with v3, which is
|
||||
// 1 bytes of flag + 3 ID bytes big endian
|
||||
int result = (idAndFlags & 0x00_00_00_FF) << 24;
|
||||
result += (idAndFlags & 0x00_00_FF_00) << 8;
|
||||
result += (idAndFlags & 0x00_FF_00_00) >>> 8;
|
||||
result += (idAndFlags & 0xFF_00_00_00) >>> 24;
|
||||
return result;
|
||||
}
|
||||
case SPDY.V3:
|
||||
{
|
||||
return idAndFlags;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean onPair()
|
||||
{
|
||||
int id = idAndFlags & 0x00_FF_FF_FF;
|
||||
byte flags = (byte)((idAndFlags & 0xFF_00_00_00) >>> 24);
|
||||
settings.put(new Settings.Setting(Settings.ID.from(id), Settings.Flag.from(flags), value));
|
||||
state = State.ID_FLAGS;
|
||||
idAndFlags = 0;
|
||||
value = 0;
|
||||
--count;
|
||||
if (count == 0)
|
||||
{
|
||||
onSettings();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onSettings()
|
||||
{
|
||||
SettingsFrame frame = new SettingsFrame(controlFrameParser.getVersion(), controlFrameParser.getFlags(), new Settings(settings, true));
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
settings.clear();
|
||||
state = State.COUNT;
|
||||
cursor = 0;
|
||||
count = 0;
|
||||
idAndFlags = 0;
|
||||
value = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
COUNT, COUNT_BYTES, ID_FLAGS, ID_FLAGS_BYTES, VALUE, VALUE_BYTES
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.ReplyInfo;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrameType;
|
||||
import org.eclipse.jetty.spdy.frames.SynReplyFrame;
|
||||
|
||||
public class SynReplyBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final Headers headers = new Headers();
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private final HeadersBlockParser headersBlockParser;
|
||||
private State state = State.STREAM_ID;
|
||||
private int cursor;
|
||||
private int streamId;
|
||||
|
||||
public SynReplyBodyParser(CompressionFactory.Decompressor decompressor, ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
this.headersBlockParser = new SynReplyHeadersBlockParser(decompressor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case STREAM_ID:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
streamId = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
state = State.ADDITIONAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.STREAM_ID_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STREAM_ID_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
streamId += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
streamId &= 0x7F_FF_FF_FF;
|
||||
state = State.ADDITIONAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ADDITIONAL:
|
||||
{
|
||||
switch (controlFrameParser.getVersion())
|
||||
{
|
||||
case SPDY.V2:
|
||||
{
|
||||
if (buffer.remaining() >= 2)
|
||||
{
|
||||
buffer.getShort();
|
||||
state = State.HEADERS;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.ADDITIONAL_BYTES;
|
||||
cursor = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SPDY.V3:
|
||||
{
|
||||
state = State.HEADERS;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ADDITIONAL_BYTES:
|
||||
{
|
||||
assert controlFrameParser.getVersion() == SPDY.V2;
|
||||
buffer.get();
|
||||
--cursor;
|
||||
if (cursor == 0)
|
||||
state = State.HEADERS;
|
||||
break;
|
||||
}
|
||||
case HEADERS:
|
||||
{
|
||||
short version = controlFrameParser.getVersion();
|
||||
int length = controlFrameParser.getLength() - getSynReplyDataLength(version);
|
||||
if (headersBlockParser.parse(streamId, version, length, buffer))
|
||||
{
|
||||
byte flags = controlFrameParser.getFlags();
|
||||
if (flags != 0 && flags != ReplyInfo.FLAG_CLOSE)
|
||||
throw new IllegalArgumentException("Invalid flag " + flags + " for frame " + ControlFrameType.SYN_REPLY);
|
||||
|
||||
SynReplyFrame frame = new SynReplyFrame(version, flags, streamId, new Headers(headers, true));
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private int getSynReplyDataLength(short version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case 2:
|
||||
return 6;
|
||||
case 3:
|
||||
return 4;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
headers.clear();
|
||||
state = State.STREAM_ID;
|
||||
cursor = 0;
|
||||
streamId = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
STREAM_ID, STREAM_ID_BYTES, ADDITIONAL, ADDITIONAL_BYTES, HEADERS
|
||||
}
|
||||
|
||||
private class SynReplyHeadersBlockParser extends HeadersBlockParser
|
||||
{
|
||||
public SynReplyHeadersBlockParser(CompressionFactory.Decompressor decompressor)
|
||||
{
|
||||
super(decompressor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHeader(String name, String[] values)
|
||||
{
|
||||
for (String value : values)
|
||||
headers.add(name, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.PushSynInfo;
|
||||
import org.eclipse.jetty.spdy.StreamException;
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.StreamStatus;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrameType;
|
||||
import org.eclipse.jetty.spdy.frames.SynStreamFrame;
|
||||
|
||||
public class SynStreamBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final Headers headers = new Headers();
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private final HeadersBlockParser headersBlockParser;
|
||||
private State state = State.STREAM_ID;
|
||||
private int cursor;
|
||||
private int streamId;
|
||||
private int associatedStreamId;
|
||||
private byte priority;
|
||||
|
||||
public SynStreamBodyParser(CompressionFactory.Decompressor decompressor, ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
this.headersBlockParser = new SynStreamHeadersBlockParser(decompressor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case STREAM_ID:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
streamId = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
state = State.ASSOCIATED_STREAM_ID;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.STREAM_ID_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STREAM_ID_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
streamId += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
streamId &= 0x7F_FF_FF_FF;
|
||||
state = State.ASSOCIATED_STREAM_ID;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ASSOCIATED_STREAM_ID:
|
||||
{
|
||||
// Now we know the streamId, we can do the version check
|
||||
// and if it is wrong, issue a RST_STREAM
|
||||
checkVersion(controlFrameParser.getVersion(), streamId);
|
||||
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
associatedStreamId = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
state = State.PRIORITY;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.ASSOCIATED_STREAM_ID_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ASSOCIATED_STREAM_ID_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
associatedStreamId += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
associatedStreamId &= 0x7F_FF_FF_FF;
|
||||
state = State.PRIORITY;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PRIORITY:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
++cursor;
|
||||
if (cursor == 1)
|
||||
{
|
||||
priority = readPriority(controlFrameParser.getVersion(), currByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unused byte after priority, skip it
|
||||
cursor = 0;
|
||||
state = State.HEADERS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HEADERS:
|
||||
{
|
||||
short version = controlFrameParser.getVersion();
|
||||
int length = controlFrameParser.getLength() - 10;
|
||||
if (headersBlockParser.parse(streamId, version, length, buffer))
|
||||
{
|
||||
byte flags = controlFrameParser.getFlags();
|
||||
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));
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void checkVersion(short version, int streamId)
|
||||
{
|
||||
if (version != SPDY.V2 && version != SPDY.V3)
|
||||
throw new StreamException(streamId, StreamStatus.UNSUPPORTED_VERSION);
|
||||
}
|
||||
|
||||
private byte readPriority(short version, byte currByte)
|
||||
{
|
||||
// Right shift retains the sign bit when operated on a byte,
|
||||
// so we use an int to perform the shifts
|
||||
switch (version)
|
||||
{
|
||||
case SPDY.V2:
|
||||
int p2 = currByte & 0b1100_0000;
|
||||
p2 >>>= 6;
|
||||
return (byte)p2;
|
||||
case SPDY.V3:
|
||||
int p3 = currByte & 0b1110_0000;
|
||||
p3 >>>= 5;
|
||||
return (byte)p3;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
headers.clear();
|
||||
state = State.STREAM_ID;
|
||||
cursor = 0;
|
||||
streamId = 0;
|
||||
associatedStreamId = 0;
|
||||
priority = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
STREAM_ID, STREAM_ID_BYTES, ASSOCIATED_STREAM_ID, ASSOCIATED_STREAM_ID_BYTES, PRIORITY, HEADERS
|
||||
}
|
||||
|
||||
private class SynStreamHeadersBlockParser extends HeadersBlockParser
|
||||
{
|
||||
public SynStreamHeadersBlockParser(CompressionFactory.Decompressor decompressor)
|
||||
{
|
||||
super(decompressor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHeader(String name, String[] values)
|
||||
{
|
||||
for (String value : values)
|
||||
headers.add(name, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class UnknownControlFrameBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private State state = State.BODY;
|
||||
private int remaining;
|
||||
|
||||
public UnknownControlFrameBodyParser(ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case BODY:
|
||||
{
|
||||
remaining = controlFrameParser.getLength();
|
||||
state = State.CONSUME;
|
||||
// Fall down
|
||||
}
|
||||
case CONSUME:
|
||||
{
|
||||
int consume = Math.min(remaining, buffer.remaining());
|
||||
buffer.position(buffer.position() + consume);
|
||||
remaining -= consume;
|
||||
if (remaining > 0)
|
||||
return false;
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
state = State.BODY;
|
||||
remaining = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
BODY, CONSUME
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.frames.WindowUpdateFrame;
|
||||
|
||||
public class WindowUpdateBodyParser extends ControlFrameBodyParser
|
||||
{
|
||||
private final ControlFrameParser controlFrameParser;
|
||||
private State state = State.STREAM_ID;
|
||||
private int cursor;
|
||||
private int streamId;
|
||||
private int windowDelta;
|
||||
|
||||
public WindowUpdateBodyParser(ControlFrameParser controlFrameParser)
|
||||
{
|
||||
this.controlFrameParser = controlFrameParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case STREAM_ID:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
streamId = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
state = State.WINDOW_DELTA;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.STREAM_ID_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STREAM_ID_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
streamId += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
streamId &= 0x7F_FF_FF_FF;
|
||||
state = State.WINDOW_DELTA;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WINDOW_DELTA:
|
||||
{
|
||||
if (buffer.remaining() >= 4)
|
||||
{
|
||||
windowDelta = buffer.getInt() & 0x7F_FF_FF_FF;
|
||||
onWindowUpdate();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.WINDOW_DELTA_BYTES;
|
||||
cursor = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WINDOW_DELTA_BYTES:
|
||||
{
|
||||
byte currByte = buffer.get();
|
||||
--cursor;
|
||||
windowDelta += (currByte & 0xFF) << 8 * cursor;
|
||||
if (cursor == 0)
|
||||
{
|
||||
windowDelta &= 0x7F_FF_FF_FF;
|
||||
onWindowUpdate();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onWindowUpdate()
|
||||
{
|
||||
WindowUpdateFrame frame = new WindowUpdateFrame(controlFrameParser.getVersion(), streamId, windowDelta);
|
||||
controlFrameParser.onControlFrame(frame);
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
state = State.STREAM_ID;
|
||||
cursor = 0;
|
||||
streamId = 0;
|
||||
windowDelta = 0;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
STREAM_ID, STREAM_ID_BYTES, WINDOW_DELTA, WINDOW_DELTA_BYTES;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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.Stream;
|
||||
import org.eclipse.jetty.spdy.api.StringDataInfo;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
import org.eclipse.jetty.spdy.generator.Generator;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class AsyncTimeoutTest
|
||||
{
|
||||
@Test
|
||||
public void testAsyncTimeoutInControlFrames() throws Exception
|
||||
{
|
||||
final long timeout = 1000;
|
||||
final TimeUnit unit = TimeUnit.MILLISECONDS;
|
||||
|
||||
ByteBufferPool bufferPool = new StandardByteBufferPool();
|
||||
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)
|
||||
{
|
||||
@Override
|
||||
public void flush()
|
||||
{
|
||||
try
|
||||
{
|
||||
unit.sleep(2 * timeout);
|
||||
super.flush();
|
||||
}
|
||||
catch (InterruptedException x)
|
||||
{
|
||||
throw new SPDYException(x);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final CountDownLatch failedLatch = new CountDownLatch(1);
|
||||
session.syn(new SynInfo(true), null, timeout, unit, new Callback<Stream>()
|
||||
{
|
||||
@Override
|
||||
public void completed(Stream stream)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
failedLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
Assert.assertTrue(failedLatch.await(2 * timeout, unit));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncTimeoutInDataFrames() throws Exception
|
||||
{
|
||||
final long timeout = 1000;
|
||||
final TimeUnit unit = TimeUnit.MILLISECONDS;
|
||||
|
||||
ByteBufferPool bufferPool = new StandardByteBufferPool();
|
||||
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)
|
||||
{
|
||||
@Override
|
||||
protected void write(ByteBuffer buffer, Callback<FrameBytes> handler, FrameBytes frameBytes)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Wait if we're writing the data frame (control frame's first byte is 0x80)
|
||||
if (buffer.get(0) == 0)
|
||||
unit.sleep(2 * timeout);
|
||||
super.write(buffer, handler, frameBytes);
|
||||
}
|
||||
catch (InterruptedException x)
|
||||
{
|
||||
throw new SPDYException(x);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Stream stream = session.syn(new SynInfo(false), null).get(5, TimeUnit.SECONDS);
|
||||
final CountDownLatch failedLatch = new CountDownLatch(1);
|
||||
stream.data(new StringDataInfo("data", true), timeout, unit, new Callback<Void>()
|
||||
{
|
||||
@Override
|
||||
public void completed(Void context)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
failedLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
Assert.assertTrue(failedLatch.await(2 * timeout, unit));
|
||||
}
|
||||
|
||||
private static class TestController implements Controller<StandardSession.FrameBytes>
|
||||
{
|
||||
@Override
|
||||
public int write(ByteBuffer buffer, Callback<StandardSession.FrameBytes> callback, StandardSession.FrameBytes context)
|
||||
{
|
||||
callback.completed(context);
|
||||
return buffer.remaining();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(boolean onlyOutput)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,457 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.HeadersInfo;
|
||||
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.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.frames.DataFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SynReplyFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SynStreamFrame;
|
||||
import org.eclipse.jetty.spdy.generator.Generator;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
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;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class StandardSessionTest
|
||||
{
|
||||
@Mock
|
||||
private ISession sessionMock;
|
||||
private ByteBufferPool bufferPool;
|
||||
private Executor threadPool;
|
||||
private StandardSession session;
|
||||
private Generator generator;
|
||||
private ScheduledExecutorService scheduler;
|
||||
private Headers headers;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
bufferPool = new StandardByteBufferPool();
|
||||
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);
|
||||
headers = new Headers();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamIsRemovedFromSessionWhenReset() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
assertThatStreamIsInSession(stream);
|
||||
assertThat("stream is not reset",stream.isReset(),is(false));
|
||||
session.rst(new RstInfo(stream.getId(),StreamStatus.STREAM_ALREADY_CLOSED));
|
||||
assertThatStreamIsNotInSession(stream);
|
||||
assertThatStreamIsReset(stream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamIsAddedAndRemovedFromSession() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
assertThatStreamIsInSession(stream);
|
||||
stream.updateCloseState(true,true);
|
||||
session.onControlFrame(new SynReplyFrame(SPDY.V2,SynInfo.FLAG_CLOSE,stream.getId(),null));
|
||||
assertThatStreamIsClosed(stream);
|
||||
assertThatStreamIsNotInSession(stream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamIsRemovedWhenHeadersWithCloseFlagAreSent() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
assertThatStreamIsInSession(stream);
|
||||
stream.updateCloseState(true,false);
|
||||
stream.headers(new HeadersInfo(headers,true));
|
||||
assertThatStreamIsClosed(stream);
|
||||
assertThatStreamIsNotInSession(stream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamIsUnidirectional() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
assertThat("stream is not unidirectional",stream.isUnidirectional(),not(true));
|
||||
Stream pushStream = createPushStream(stream);
|
||||
assertThat("pushStream is unidirectional",pushStream.isUnidirectional(),is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushStreamCreation() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
Stream stream = createStream();
|
||||
IStream pushStream = createPushStream(stream);
|
||||
assertThat("Push stream must be associated to the first stream created",pushStream.getAssociatedStream().getId(),is(stream.getId()));
|
||||
assertThat("streamIds need to be monotonic",pushStream.getId(),greaterThan(stream.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushStreamIsNotClosedWhenAssociatedStreamIsClosed() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
Stream pushStream = createPushStream(stream);
|
||||
assertThatStreamIsNotHalfClosed(stream);
|
||||
assertThatStreamIsNotClosed(stream);
|
||||
assertThatPushStreamIsHalfClosed(pushStream);
|
||||
assertThatPushStreamIsNotClosed(pushStream);
|
||||
|
||||
stream.updateCloseState(true,true);
|
||||
assertThatStreamIsHalfClosed(stream);
|
||||
assertThatStreamIsNotClosed(stream);
|
||||
assertThatPushStreamIsHalfClosed(pushStream);
|
||||
assertThatPushStreamIsNotClosed(pushStream);
|
||||
|
||||
session.onControlFrame(new SynReplyFrame(SPDY.V2,SynInfo.FLAG_CLOSE,stream.getId(),null));
|
||||
assertThatStreamIsClosed(stream);
|
||||
assertThatPushStreamIsNotClosed(pushStream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePushStreamOnClosedStream() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
stream.updateCloseState(true,true);
|
||||
assertThatStreamIsHalfClosed(stream);
|
||||
stream.updateCloseState(true,false);
|
||||
assertThatStreamIsClosed(stream);
|
||||
createPushStreamAndMakeSureItFails(stream);
|
||||
}
|
||||
|
||||
private void createPushStreamAndMakeSureItFails(IStream stream) throws InterruptedException
|
||||
{
|
||||
final CountDownLatch failedLatch = new CountDownLatch(1);
|
||||
SynInfo synInfo = new SynInfo(headers,false,stream.getPriority());
|
||||
stream.syn(synInfo,5,TimeUnit.SECONDS,new Callback<Stream>()
|
||||
{
|
||||
@Override
|
||||
public void completed(Stream context)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
failedLatch.countDown();
|
||||
}
|
||||
});
|
||||
assertThat("pushStream creation failed",failedLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushStreamIsAddedAndRemovedFromParentAndSessionWhenClosed() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
IStream pushStream = createPushStream(stream);
|
||||
assertThatPushStreamIsHalfClosed(pushStream);
|
||||
assertThatPushStreamIsInSession(pushStream);
|
||||
assertThatStreamIsAssociatedWithPushStream(stream,pushStream);
|
||||
session.data(pushStream,new StringDataInfo("close",true),5,TimeUnit.SECONDS,null,null);
|
||||
assertThatPushStreamIsClosed(pushStream);
|
||||
assertThatPushStreamIsNotInSession(pushStream);
|
||||
assertThatStreamIsNotAssociatedWithPushStream(stream,pushStream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushStreamIsRemovedWhenReset() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
IStream pushStream = (IStream)stream.syn(new SynInfo(false)).get();
|
||||
assertThatPushStreamIsInSession(pushStream);
|
||||
session.rst(new RstInfo(pushStream.getId(),StreamStatus.INVALID_STREAM));
|
||||
assertThatPushStreamIsNotInSession(pushStream);
|
||||
assertThatStreamIsNotAssociatedWithPushStream(stream,pushStream);
|
||||
assertThatStreamIsReset(pushStream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushStreamWithSynInfoClosedTrue() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
SynInfo synInfo = new SynInfo(headers,true,stream.getPriority());
|
||||
IStream pushStream = (IStream)stream.syn(synInfo).get(5,TimeUnit.SECONDS);
|
||||
assertThatPushStreamIsHalfClosed(pushStream);
|
||||
assertThatPushStreamIsClosed(pushStream);
|
||||
assertThatStreamIsNotAssociatedWithPushStream(stream,pushStream);
|
||||
assertThatStreamIsNotInSession(pushStream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushStreamSendHeadersWithCloseFlagIsRemovedFromSessionAndDisassociateFromParent() throws InterruptedException, ExecutionException,
|
||||
TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
SynInfo synInfo = new SynInfo(headers,false,stream.getPriority());
|
||||
IStream pushStream = (IStream)stream.syn(synInfo).get(5,TimeUnit.SECONDS);
|
||||
assertThatStreamIsAssociatedWithPushStream(stream,pushStream);
|
||||
assertThatPushStreamIsInSession(pushStream);
|
||||
pushStream.headers(new HeadersInfo(headers,true));
|
||||
assertThatPushStreamIsNotInSession(pushStream);
|
||||
assertThatPushStreamIsHalfClosed(pushStream);
|
||||
assertThatPushStreamIsClosed(pushStream);
|
||||
assertThatStreamIsNotAssociatedWithPushStream(stream,pushStream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatedAndClosedListenersAreCalledForNewStream() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
final CountDownLatch createdListenerCalledLatch = new CountDownLatch(1);
|
||||
final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
|
||||
session.addListener(new TestStreamListener(createdListenerCalledLatch,closedListenerCalledLatch));
|
||||
IStream stream = createStream();
|
||||
session.onDataFrame(new DataFrame(stream.getId(),SynInfo.FLAG_CLOSE,128),ByteBuffer.allocate(128));
|
||||
stream.data(new StringDataInfo("close",true));
|
||||
assertThat("onStreamCreated listener has been called",createdListenerCalledLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
assertThatOnStreamClosedListenerHasBeenCalled(closedListenerCalledLatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListenerIsCalledForResetStream() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
|
||||
session.addListener(new TestStreamListener(null,closedListenerCalledLatch));
|
||||
IStream stream = createStream();
|
||||
session.rst(new RstInfo(stream.getId(),StreamStatus.CANCEL_STREAM));
|
||||
assertThatOnStreamClosedListenerHasBeenCalled(closedListenerCalledLatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatedAndClosedListenersAreCalledForNewPushStream() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
final CountDownLatch createdListenerCalledLatch = new CountDownLatch(2);
|
||||
final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
|
||||
session.addListener(new TestStreamListener(createdListenerCalledLatch,closedListenerCalledLatch));
|
||||
IStream stream = createStream();
|
||||
IStream pushStream = createPushStream(stream);
|
||||
session.data(pushStream,new StringDataInfo("close",true),5,TimeUnit.SECONDS,null,null);
|
||||
assertThat("onStreamCreated listener has been called twice. Once for the stream and once for the pushStream",
|
||||
createdListenerCalledLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
assertThatOnStreamClosedListenerHasBeenCalled(closedListenerCalledLatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListenerIsCalledForResetPushStream() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
|
||||
session.addListener(new TestStreamListener(null,closedListenerCalledLatch));
|
||||
IStream stream = createStream();
|
||||
IStream pushStream = createPushStream(stream);
|
||||
session.rst(new RstInfo(pushStream.getId(),StreamStatus.CANCEL_STREAM));
|
||||
assertThatOnStreamClosedListenerHasBeenCalled(closedListenerCalledLatch);
|
||||
}
|
||||
|
||||
private class TestStreamListener extends Session.StreamListener.Adapter
|
||||
{
|
||||
private CountDownLatch createdListenerCalledLatch;
|
||||
private CountDownLatch closedListenerCalledLatch;
|
||||
|
||||
public TestStreamListener(CountDownLatch createdListenerCalledLatch, CountDownLatch closedListenerCalledLatch)
|
||||
{
|
||||
this.createdListenerCalledLatch = createdListenerCalledLatch;
|
||||
this.closedListenerCalledLatch = closedListenerCalledLatch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamCreated(Stream stream)
|
||||
{
|
||||
if (createdListenerCalledLatch != null)
|
||||
createdListenerCalledLatch.countDown();
|
||||
super.onStreamCreated(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamClosed(Stream stream)
|
||||
{
|
||||
if (closedListenerCalledLatch != null)
|
||||
closedListenerCalledLatch.countDown();
|
||||
super.onStreamClosed(stream);
|
||||
}
|
||||
}
|
||||
|
||||
@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(Callback.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
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveDataOnRemotelyClosedStreamIsIgnored() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
final CountDownLatch onDataCalledLatch = new CountDownLatch(1);
|
||||
Stream stream = session.syn(new SynInfo(false),new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataInfo dataInfo)
|
||||
{
|
||||
onDataCalledLatch.countDown();
|
||||
super.onData(stream,dataInfo);
|
||||
}
|
||||
}).get(5,TimeUnit.SECONDS);
|
||||
session.onControlFrame(new SynReplyFrame(SPDY.V2,SynInfo.FLAG_CLOSE,stream.getId(),headers));
|
||||
session.onDataFrame(new DataFrame(stream.getId(),(byte)0,0),ByteBuffer.allocate(128));
|
||||
assertThat("onData is never called",onDataCalledLatch.await(1,TimeUnit.SECONDS),not(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);
|
||||
}
|
||||
|
||||
private IStream createPushStream(Stream stream) throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
SynInfo synInfo = new SynInfo(headers,false,stream.getPriority());
|
||||
return (IStream)stream.syn(synInfo).get(5,TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private static class TestController implements Controller<StandardSession.FrameBytes>
|
||||
{
|
||||
@Override
|
||||
public int write(ByteBuffer buffer, Callback<StandardSession.FrameBytes> callback, StandardSession.FrameBytes context)
|
||||
{
|
||||
callback.completed(context);
|
||||
return buffer.remaining();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(boolean onlyOutput)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void assertThatStreamIsClosed(IStream stream)
|
||||
{
|
||||
assertThat("stream is closed",stream.isClosed(),is(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsReset(IStream stream)
|
||||
{
|
||||
assertThat("stream is reset",stream.isReset(),is(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsNotInSession(IStream stream)
|
||||
{
|
||||
assertThat("stream is not in session",session.getStreams().contains(stream),not(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsInSession(IStream stream)
|
||||
{
|
||||
assertThat("stream is in session",session.getStreams().contains(stream),is(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsNotClosed(IStream stream)
|
||||
{
|
||||
assertThat("stream is not closed",stream.isClosed(),not(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsNotHalfClosed(IStream stream)
|
||||
{
|
||||
assertThat("stream is not halfClosed",stream.isHalfClosed(),not(true));
|
||||
}
|
||||
|
||||
private void assertThatPushStreamIsNotClosed(Stream pushStream)
|
||||
{
|
||||
assertThat("pushStream is not closed",pushStream.isClosed(),not(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsHalfClosed(IStream stream)
|
||||
{
|
||||
assertThat("stream is halfClosed",stream.isHalfClosed(),is(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsNotAssociatedWithPushStream(IStream stream, IStream pushStream)
|
||||
{
|
||||
assertThat("pushStream is removed from parent",stream.getPushedStreams().contains(pushStream),not(true));
|
||||
}
|
||||
|
||||
private void assertThatPushStreamIsNotInSession(Stream pushStream)
|
||||
{
|
||||
assertThat("pushStream is not in session",session.getStreams().contains(pushStream.getId()),not(true));
|
||||
}
|
||||
|
||||
private void assertThatPushStreamIsInSession(Stream pushStream)
|
||||
{
|
||||
assertThat("pushStream is in session",session.getStreams().contains(pushStream),is(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsAssociatedWithPushStream(IStream stream, Stream pushStream)
|
||||
{
|
||||
assertThat("stream is associated with pushStream",stream.getPushedStreams().contains(pushStream),is(true));
|
||||
}
|
||||
|
||||
private void assertThatPushStreamIsClosed(Stream pushStream)
|
||||
{
|
||||
assertThat("pushStream is closed",pushStream.isClosed(),is(true));
|
||||
}
|
||||
|
||||
private void assertThatPushStreamIsHalfClosed(Stream pushStream)
|
||||
{
|
||||
assertThat("pushStream is half closed ",pushStream.isHalfClosed(),is(true));
|
||||
}
|
||||
|
||||
private void assertThatOnStreamClosedListenerHasBeenCalled(final CountDownLatch closedListenerCalledLatch) throws InterruptedException
|
||||
{
|
||||
assertThat("onStreamClosed listener has been called",closedListenerCalledLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
// ========================================================================
|
||||
// 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.spdy;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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.SynStreamFrame;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
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.anyLong;
|
||||
import static org.mockito.Matchers.argThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
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)}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSyn()
|
||||
{
|
||||
Stream stream = new StandardStream(synStreamFrame,session,0,null);
|
||||
Set<Stream> streams = new HashSet<>();
|
||||
streams.add(stream);
|
||||
when(synStreamFrame.isClose()).thenReturn(false);
|
||||
SynInfo synInfo = new SynInfo(false);
|
||||
when(session.getStreams()).thenReturn(streams);
|
||||
stream.syn(synInfo);
|
||||
verify(session).syn(argThat(new PushSynInfoMatcher(stream.getId(),synInfo)),any(StreamFrameListener.class),anyLong(),any(TimeUnit.class),any(Callback.class));
|
||||
}
|
||||
|
||||
private class PushSynInfoMatcher extends ArgumentMatcher<PushSynInfo>{
|
||||
int associatedStreamId;
|
||||
SynInfo synInfo;
|
||||
|
||||
public PushSynInfoMatcher(int associatedStreamId, SynInfo synInfo)
|
||||
{
|
||||
this.associatedStreamId = associatedStreamId;
|
||||
this.synInfo = synInfo;
|
||||
}
|
||||
@Override
|
||||
public boolean matches(Object argument)
|
||||
{
|
||||
PushSynInfo pushSynInfo = (PushSynInfo)argument;
|
||||
if(pushSynInfo.getAssociatedStreamId() != associatedStreamId){
|
||||
System.out.println("streamIds do not match!");
|
||||
return false;
|
||||
}
|
||||
if(pushSynInfo.isClose() != synInfo.isClose()){
|
||||
System.out.println("isClose doesn't match");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSynOnClosedStream(){
|
||||
IStream stream = new StandardStream(synStreamFrame,session,0,null);
|
||||
stream.updateCloseState(true,true);
|
||||
stream.updateCloseState(true,false);
|
||||
assertThat("stream expected to be closed",stream.isClosed(),is(true));
|
||||
final CountDownLatch failedLatch = new CountDownLatch(1);
|
||||
stream.syn(new SynInfo(false),1,TimeUnit.SECONDS,new Callback.Adapter<Stream>()
|
||||
{
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
failedLatch.countDown();
|
||||
}
|
||||
});
|
||||
assertThat("PushStream creation failed", failedLatch.getCount(), equalTo(0L));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.spdy.StandardSession;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
@Ignore
|
||||
public class ClientUsageTest
|
||||
{
|
||||
@Test
|
||||
public void testClientRequestResponseNoBody() throws Exception
|
||||
{
|
||||
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null);
|
||||
|
||||
session.syn(new SynInfo(true), new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onReply(Stream stream, ReplyInfo replyInfo)
|
||||
{
|
||||
// Do something with the response
|
||||
replyInfo.getHeaders().get("host");
|
||||
|
||||
// Then issue another similar request
|
||||
stream.getSession().syn(new SynInfo(true), this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientRequestWithBodyResponseNoBody() throws Exception
|
||||
{
|
||||
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null);
|
||||
|
||||
Stream stream = session.syn(new SynInfo(false), new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onReply(Stream stream, ReplyInfo replyInfo)
|
||||
{
|
||||
// Do something with the response
|
||||
replyInfo.getHeaders().get("host");
|
||||
|
||||
// Then issue another similar request
|
||||
stream.getSession().syn(new SynInfo(true), this);
|
||||
}
|
||||
}).get(5, TimeUnit.SECONDS);
|
||||
// Send-and-forget the data
|
||||
stream.data(new StringDataInfo("data", true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncClientRequestWithBodyResponseNoBody() throws Exception
|
||||
{
|
||||
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null);
|
||||
|
||||
final String context = "context";
|
||||
session.syn(new SynInfo(false), new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onReply(Stream stream, ReplyInfo replyInfo)
|
||||
{
|
||||
// Do something with the response
|
||||
replyInfo.getHeaders().get("host");
|
||||
|
||||
// Then issue another similar request
|
||||
stream.getSession().syn(new SynInfo(true), this);
|
||||
}
|
||||
}, 0, TimeUnit.MILLISECONDS, new Callback.Adapter<Stream>()
|
||||
{
|
||||
@Override
|
||||
public void completed(Stream stream)
|
||||
{
|
||||
// Differently from JDK 7 AIO, there is no need to
|
||||
// have an explicit parameter for the context since
|
||||
// that is captured while the handler is created anyway,
|
||||
// and it is used only by the handler as parameter
|
||||
|
||||
// The style below is fire-and-forget, since
|
||||
// we do not pass the handler nor we call get()
|
||||
// to wait for the data to be sent
|
||||
stream.data(new StringDataInfo(context, true));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncClientRequestWithBodyAndResponseWithBody() throws Exception
|
||||
{
|
||||
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null);
|
||||
|
||||
session.syn(new SynInfo(false), new StreamFrameListener.Adapter()
|
||||
{
|
||||
// The good of passing the listener to syn() is that applications can safely
|
||||
// accumulate info from the reply headers to be used in the data callback,
|
||||
// e.g. content-type, charset, etc.
|
||||
|
||||
@Override
|
||||
public void onReply(Stream stream, ReplyInfo replyInfo)
|
||||
{
|
||||
// Do something with the response
|
||||
Headers headers = replyInfo.getHeaders();
|
||||
int contentLength = headers.get("content-length").valueAsInt();
|
||||
stream.setAttribute("content-length", contentLength);
|
||||
if (!replyInfo.isClose())
|
||||
stream.setAttribute("builder", new StringBuilder());
|
||||
|
||||
// May issue another similar request while waiting for data
|
||||
stream.getSession().syn(new SynInfo(true), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onData(Stream stream, DataInfo dataInfo)
|
||||
{
|
||||
StringBuilder builder = (StringBuilder)stream.getAttribute("builder");
|
||||
builder.append(dataInfo.asString("UTF-8", true));
|
||||
if (dataInfo.isClose())
|
||||
{
|
||||
int receivedLength = builder.toString().getBytes(Charset.forName("UTF-8")).length;
|
||||
assert receivedLength == (Integer)stream.getAttribute("content-length");
|
||||
}
|
||||
|
||||
}
|
||||
}, 0, TimeUnit.MILLISECONDS, new Callback.Adapter<Stream>()
|
||||
{
|
||||
@Override
|
||||
public void completed(Stream stream)
|
||||
{
|
||||
stream.data(new BytesDataInfo("wee".getBytes(Charset.forName("UTF-8")), false));
|
||||
stream.data(new StringDataInfo("foo", false));
|
||||
stream.data(new ByteBufferDataInfo(Charset.forName("UTF-8").encode("bar"), true));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
@Ignore
|
||||
public class ServerUsageTest
|
||||
{
|
||||
@Test
|
||||
public void testServerSynAndReplyWithData() throws Exception
|
||||
{
|
||||
new ServerSessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo streamInfo)
|
||||
{
|
||||
Headers synHeaders = streamInfo.getHeaders();
|
||||
// Do something with headers, for example extract them and
|
||||
// perform an http request via Jetty's LocalConnector
|
||||
|
||||
// Get the http response, fill headers and data
|
||||
Headers replyHeaders = new Headers();
|
||||
replyHeaders.put(synHeaders.get("host"));
|
||||
// Sends a reply
|
||||
stream.reply(new ReplyInfo(replyHeaders, false));
|
||||
|
||||
// Sends data
|
||||
StringDataInfo dataInfo = new StringDataInfo("foo", false);
|
||||
stream.data(dataInfo);
|
||||
// Stream is now closed
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerInitiatesStreamAndPushesData() throws Exception
|
||||
{
|
||||
new ServerSessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onConnect(Session session)
|
||||
{
|
||||
// SPDY does not allow the server to initiate a stream without an existing stream
|
||||
// being opened by the client already.
|
||||
// Correct SPDY sequence will be:
|
||||
// C --- SYN_STREAM(id=1) --> S
|
||||
// C <-- SYN_REPLY(id=1) --- S
|
||||
// C <-- SYN_STREAM(id=2,uni,assId=1) --- S
|
||||
//
|
||||
// However, the API may allow to initiate the stream
|
||||
|
||||
session.syn(new SynInfo(false), null, 0, TimeUnit.MILLISECONDS, new Callback.Adapter<Stream>()
|
||||
{
|
||||
@Override
|
||||
public void completed(Stream stream)
|
||||
{
|
||||
// The point here is that we have no idea if the client accepted our stream
|
||||
// So we return a stream, we may be able to send the headers frame, but later
|
||||
// the client sends a rst frame.
|
||||
// We have to atomically set some flag on the stream to signal it's closed
|
||||
// and any operation on it will throw
|
||||
stream.headers(new HeadersInfo(new Headers(), true));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerPush() throws Exception
|
||||
{
|
||||
new ServerSessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo streamInfo)
|
||||
{
|
||||
// Need to send the reply first
|
||||
stream.reply(new ReplyInfo(false));
|
||||
|
||||
Session session = stream.getSession();
|
||||
// Since it's unidirectional, no need to pass the listener
|
||||
session.syn(new SynInfo(new Headers(), false, (byte)0), null, 0, TimeUnit.MILLISECONDS, new Callback.Adapter<Stream>()
|
||||
{
|
||||
@Override
|
||||
public void completed(Stream pushStream)
|
||||
{
|
||||
pushStream.data(new StringDataInfo("foo", false));
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* 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.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.StandardByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.StandardCompressionFactory;
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.api.StringDataInfo;
|
||||
import org.eclipse.jetty.spdy.generator.Generator;
|
||||
import org.eclipse.jetty.spdy.parser.Parser;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class DataGenerateParseTest
|
||||
{
|
||||
@Test
|
||||
public void testGenerateParse() throws Exception
|
||||
{
|
||||
testGenerateParse("test1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseZeroLength() throws Exception
|
||||
{
|
||||
testGenerateParse("");
|
||||
}
|
||||
|
||||
private void testGenerateParse(String content) throws Exception
|
||||
{
|
||||
int length = content.length();
|
||||
DataInfo data = new StringDataInfo(content, true);
|
||||
int streamId = 13;
|
||||
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
|
||||
ByteBuffer buffer = generator.data(streamId, 2 * length, data);
|
||||
|
||||
Assert.assertNotNull(buffer);
|
||||
|
||||
TestSPDYParserListener listener = new TestSPDYParserListener();
|
||||
Parser parser = new Parser(new StandardCompressionFactory().newDecompressor());
|
||||
parser.addListener(listener);
|
||||
parser.parse(buffer);
|
||||
DataFrame frame2 = listener.getDataFrame();
|
||||
|
||||
Assert.assertNotNull(frame2);
|
||||
Assert.assertEquals(streamId, frame2.getStreamId());
|
||||
Assert.assertEquals(DataInfo.FLAG_CLOSE, frame2.getFlags());
|
||||
Assert.assertEquals(length, frame2.getLength());
|
||||
Assert.assertEquals(length, listener.getData().remaining());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseOneByteAtATime() throws Exception
|
||||
{
|
||||
String content = "test2";
|
||||
int length = content.length();
|
||||
DataInfo data = new StringDataInfo(content, true);
|
||||
int streamId = 13;
|
||||
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
|
||||
ByteBuffer buffer = generator.data(streamId, 2 * length, data);
|
||||
|
||||
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()}));
|
||||
if (buffer.remaining() < length)
|
||||
{
|
||||
DataFrame frame2 = listener.getDataFrame();
|
||||
Assert.assertNotNull(frame2);
|
||||
Assert.assertEquals(streamId, frame2.getStreamId());
|
||||
Assert.assertEquals(buffer.hasRemaining() ? 0 : DataInfo.FLAG_CLOSE, frame2.getFlags());
|
||||
Assert.assertEquals(1, frame2.getLength());
|
||||
Assert.assertEquals(1, listener.getData().remaining());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseWithSyntheticFrames() throws Exception
|
||||
{
|
||||
String content = "0123456789ABCDEF";
|
||||
int length = content.length();
|
||||
DataInfo data = new StringDataInfo(content, true);
|
||||
int streamId = 13;
|
||||
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
|
||||
ByteBuffer buffer = generator.data(streamId, 2 * length, data);
|
||||
|
||||
Assert.assertNotNull(buffer);
|
||||
|
||||
TestSPDYParserListener listener = new TestSPDYParserListener();
|
||||
Parser parser = new Parser(new StandardCompressionFactory().newDecompressor());
|
||||
parser.addListener(listener);
|
||||
|
||||
// Split the buffer to simulate a split boundary in receiving the bytes
|
||||
int split = 3;
|
||||
ByteBuffer buffer1 = ByteBuffer.allocate(buffer.remaining() - split);
|
||||
buffer.limit(buffer.limit() - split);
|
||||
buffer1.put(buffer);
|
||||
buffer1.flip();
|
||||
ByteBuffer buffer2 = ByteBuffer.allocate(split);
|
||||
buffer.limit(buffer.limit() + split);
|
||||
buffer2.put(buffer);
|
||||
buffer2.flip();
|
||||
|
||||
parser.parse(buffer1);
|
||||
DataFrame frame2 = listener.getDataFrame();
|
||||
|
||||
Assert.assertNotNull(frame2);
|
||||
Assert.assertEquals(streamId, frame2.getStreamId());
|
||||
Assert.assertEquals(0, frame2.getFlags());
|
||||
Assert.assertEquals(length - split, frame2.getLength());
|
||||
Assert.assertEquals(length - split, listener.getData().remaining());
|
||||
|
||||
parser.parse(buffer2);
|
||||
DataFrame frame3 = listener.getDataFrame();
|
||||
|
||||
Assert.assertNotNull(frame3);
|
||||
Assert.assertEquals(streamId, frame3.getStreamId());
|
||||
Assert.assertEquals(DataInfo.FLAG_CLOSE, frame3.getFlags());
|
||||
Assert.assertEquals(split, frame3.getLength());
|
||||
Assert.assertEquals(split, listener.getData().remaining());
|
||||
}
|
||||
}
|
|
@ -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.frames;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
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.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class GoAwayGenerateParseTest
|
||||
{
|
||||
@Test
|
||||
public void testGenerateParse() throws Exception
|
||||
{
|
||||
int lastStreamId = 13;
|
||||
int statusCode = 1;
|
||||
GoAwayFrame frame1 = new GoAwayFrame(SPDY.V3, lastStreamId, statusCode);
|
||||
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.GO_AWAY, frame2.getType());
|
||||
GoAwayFrame goAway = (GoAwayFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V3, goAway.getVersion());
|
||||
Assert.assertEquals(lastStreamId, goAway.getLastStreamId());
|
||||
Assert.assertEquals(0, goAway.getFlags());
|
||||
Assert.assertEquals(statusCode, goAway.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseOneByteAtATime() throws Exception
|
||||
{
|
||||
int lastStreamId = 13;
|
||||
int statusCode = 1;
|
||||
GoAwayFrame frame1 = new GoAwayFrame(SPDY.V3, lastStreamId, statusCode);
|
||||
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.GO_AWAY, frame2.getType());
|
||||
GoAwayFrame goAway = (GoAwayFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V3, goAway.getVersion());
|
||||
Assert.assertEquals(lastStreamId, goAway.getLastStreamId());
|
||||
Assert.assertEquals(0, goAway.getFlags());
|
||||
Assert.assertEquals(statusCode, goAway.getStatusCode());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.StandardByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.StandardCompressionFactory;
|
||||
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.generator.Generator;
|
||||
import org.eclipse.jetty.spdy.parser.Parser;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class HeadersGenerateParseTest
|
||||
{
|
||||
@Test
|
||||
public void testGenerateParse() throws Exception
|
||||
{
|
||||
byte flags = HeadersInfo.FLAG_RESET_COMPRESSION;
|
||||
int streamId = 13;
|
||||
Headers headers = new Headers();
|
||||
headers.put("a", "b");
|
||||
HeadersFrame frame1 = new HeadersFrame(SPDY.V2, flags, streamId, headers);
|
||||
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.HEADERS, frame2.getType());
|
||||
HeadersFrame headersFrame = (HeadersFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V2, headersFrame.getVersion());
|
||||
Assert.assertEquals(streamId, headersFrame.getStreamId());
|
||||
Assert.assertEquals(flags, headersFrame.getFlags());
|
||||
Assert.assertEquals(headers, headersFrame.getHeaders());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseOneByteAtATime() throws Exception
|
||||
{
|
||||
byte flags = HeadersInfo.FLAG_RESET_COMPRESSION;
|
||||
int streamId = 13;
|
||||
Headers headers = new Headers();
|
||||
headers.put("a", "b");
|
||||
HeadersFrame frame1 = new HeadersFrame(SPDY.V2, flags, streamId, headers);
|
||||
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.HEADERS, frame2.getType());
|
||||
HeadersFrame headersFrame = (HeadersFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V2, headersFrame.getVersion());
|
||||
Assert.assertEquals(streamId, headersFrame.getStreamId());
|
||||
Assert.assertEquals(flags, headersFrame.getFlags());
|
||||
Assert.assertEquals(headers, headersFrame.getHeaders());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.StandardByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.StandardCompressionFactory;
|
||||
import org.eclipse.jetty.spdy.generator.Generator;
|
||||
import org.eclipse.jetty.spdy.parser.Parser;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class NoOpGenerateParseTest
|
||||
{
|
||||
@Test
|
||||
public void testGenerateParse() throws Exception
|
||||
{
|
||||
NoOpFrame frame1 = new NoOpFrame();
|
||||
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.NOOP, frame2.getType());
|
||||
NoOpFrame noOp = (NoOpFrame)frame2;
|
||||
Assert.assertEquals(0, noOp.getFlags());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseOneByteAtATime() throws Exception
|
||||
{
|
||||
NoOpFrame frame1 = new NoOpFrame();
|
||||
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.NOOP, frame2.getType());
|
||||
NoOpFrame noOp = (NoOpFrame)frame2;
|
||||
Assert.assertEquals(0, noOp.getFlags());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.nio.ByteBuffer;
|
||||
|
||||
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.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class PingGenerateParseTest
|
||||
{
|
||||
@Test
|
||||
public void testGenerateParse() throws Exception
|
||||
{
|
||||
int pingId = 13;
|
||||
PingFrame frame1 = new PingFrame(SPDY.V2, pingId);
|
||||
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.PING, frame2.getType());
|
||||
PingFrame ping = (PingFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V2, ping.getVersion());
|
||||
Assert.assertEquals(pingId, ping.getPingId());
|
||||
Assert.assertEquals(0, ping.getFlags());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseOneByteAtATime() throws Exception
|
||||
{
|
||||
int pingId = 13;
|
||||
PingFrame frame1 = new PingFrame(SPDY.V2, pingId);
|
||||
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.PING, frame2.getType());
|
||||
PingFrame ping = (PingFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V2, ping.getVersion());
|
||||
Assert.assertEquals(pingId, ping.getPingId());
|
||||
Assert.assertEquals(0, ping.getFlags());
|
||||
}
|
||||
}
|
|
@ -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.frames;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.StandardByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.StandardCompressionFactory;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.StreamStatus;
|
||||
import org.eclipse.jetty.spdy.generator.Generator;
|
||||
import org.eclipse.jetty.spdy.parser.Parser;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class RstStreamGenerateParseTest
|
||||
{
|
||||
@Test
|
||||
public void testGenerateParse() throws Exception
|
||||
{
|
||||
int streamId = 13;
|
||||
int streamStatus = StreamStatus.UNSUPPORTED_VERSION.getCode(SPDY.V2);
|
||||
RstStreamFrame frame1 = new RstStreamFrame(SPDY.V2, streamId, streamStatus);
|
||||
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
|
||||
ByteBuffer buffer = generator.control(frame1);
|
||||
|
||||
assertThat("buffer is not null", buffer, not(nullValue()));
|
||||
|
||||
TestSPDYParserListener listener = new TestSPDYParserListener();
|
||||
Parser parser = new Parser(new StandardCompressionFactory().newDecompressor());
|
||||
parser.addListener(listener);
|
||||
parser.parse(buffer);
|
||||
ControlFrame frame2 = listener.getControlFrame();
|
||||
|
||||
assertThat("frame2 is not null", frame2, not(nullValue()));
|
||||
assertThat("frame2 is type RST_STREAM",ControlFrameType.RST_STREAM, equalTo(frame2.getType()));
|
||||
RstStreamFrame rstStream = (RstStreamFrame)frame2;
|
||||
assertThat("rstStream version is SPDY.V2",SPDY.V2, equalTo(rstStream.getVersion()));
|
||||
assertThat("rstStream id is equal to streamId",streamId, equalTo(rstStream.getStreamId()));
|
||||
assertThat("rstStream flags are 0",(byte)0, equalTo(rstStream.getFlags()));
|
||||
assertThat("stream status is equal to rstStream statuscode",streamStatus, is(rstStream.getStatusCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseOneByteAtATime() throws Exception
|
||||
{
|
||||
int streamId = 13;
|
||||
int streamStatus = StreamStatus.UNSUPPORTED_VERSION.getCode(SPDY.V2);
|
||||
RstStreamFrame frame1 = new RstStreamFrame(SPDY.V2, streamId, streamStatus);
|
||||
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.RST_STREAM, frame2.getType());
|
||||
RstStreamFrame rstStream = (RstStreamFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V2, rstStream.getVersion());
|
||||
Assert.assertEquals(streamId, rstStream.getStreamId());
|
||||
Assert.assertEquals(0, rstStream.getFlags());
|
||||
Assert.assertEquals(streamStatus, rstStream.getStatusCode());
|
||||
}
|
||||
}
|
|
@ -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.frames;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.StandardByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.StandardCompressionFactory;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.Settings;
|
||||
import org.eclipse.jetty.spdy.api.SettingsInfo;
|
||||
import org.eclipse.jetty.spdy.generator.Generator;
|
||||
import org.eclipse.jetty.spdy.parser.Parser;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SettingsGenerateParseTest
|
||||
{
|
||||
@Test
|
||||
public void testGenerateParse() throws Exception
|
||||
{
|
||||
byte flags = SettingsInfo.CLEAR_PERSISTED;
|
||||
Settings settings = new Settings();
|
||||
settings.put(new Settings.Setting(Settings.ID.MAX_CONCURRENT_STREAMS, Settings.Flag.PERSIST, 100));
|
||||
settings.put(new Settings.Setting(Settings.ID.ROUND_TRIP_TIME, Settings.Flag.PERSISTED, 500));
|
||||
SettingsFrame frame1 = new SettingsFrame(SPDY.V2, flags, settings);
|
||||
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.SETTINGS, frame2.getType());
|
||||
SettingsFrame settingsFrame = (SettingsFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V2, settingsFrame.getVersion());
|
||||
Assert.assertEquals(flags, settingsFrame.getFlags());
|
||||
Assert.assertEquals(settings, settingsFrame.getSettings());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseOneByteAtATime() throws Exception
|
||||
{
|
||||
byte flags = SettingsInfo.CLEAR_PERSISTED;
|
||||
Settings settings = new Settings();
|
||||
settings.put(new Settings.Setting(Settings.ID.DOWNLOAD_RETRANSMISSION_RATE, 100));
|
||||
settings.put(new Settings.Setting(Settings.ID.ROUND_TRIP_TIME, 500));
|
||||
SettingsFrame frame1 = new SettingsFrame(SPDY.V2, flags, settings);
|
||||
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.SETTINGS, frame2.getType());
|
||||
SettingsFrame settingsFrame = (SettingsFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V2, settingsFrame.getVersion());
|
||||
Assert.assertEquals(flags, settingsFrame.getFlags());
|
||||
Assert.assertEquals(settings, settingsFrame.getSettings());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.StandardByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.StandardCompressionFactory;
|
||||
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.generator.Generator;
|
||||
import org.eclipse.jetty.spdy.parser.Parser;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SynReplyGenerateParseTest
|
||||
{
|
||||
@Test
|
||||
public void testGenerateParse() throws Exception
|
||||
{
|
||||
byte flags = ReplyInfo.FLAG_CLOSE;
|
||||
int streamId = 13;
|
||||
Headers headers = new Headers();
|
||||
headers.put("a", "b");
|
||||
SynReplyFrame frame1 = new SynReplyFrame(SPDY.V2, flags, streamId, headers);
|
||||
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.SYN_REPLY, frame2.getType());
|
||||
SynReplyFrame synReply = (SynReplyFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V2, synReply.getVersion());
|
||||
Assert.assertEquals(flags, synReply.getFlags());
|
||||
Assert.assertEquals(streamId, synReply.getStreamId());
|
||||
Assert.assertEquals(headers, synReply.getHeaders());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseOneByteAtATime() throws Exception
|
||||
{
|
||||
byte flags = ReplyInfo.FLAG_CLOSE;
|
||||
int streamId = 13;
|
||||
Headers headers = new Headers();
|
||||
headers.put("a", "b");
|
||||
SynReplyFrame frame1 = new SynReplyFrame(SPDY.V2, flags, streamId, headers);
|
||||
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.SYN_REPLY, frame2.getType());
|
||||
SynReplyFrame synReply = (SynReplyFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V2, synReply.getVersion());
|
||||
Assert.assertEquals(flags, synReply.getFlags());
|
||||
Assert.assertEquals(streamId, synReply.getStreamId());
|
||||
Assert.assertEquals(headers, synReply.getHeaders());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.StandardByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.StandardCompressionFactory;
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
import org.eclipse.jetty.spdy.generator.Generator;
|
||||
import org.eclipse.jetty.spdy.parser.Parser;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SynStreamGenerateParseTest
|
||||
{
|
||||
@Test
|
||||
public void testGenerateParse() throws Exception
|
||||
{
|
||||
byte flags = SynInfo.FLAG_CLOSE;
|
||||
int streamId = 13;
|
||||
int associatedStreamId = 11;
|
||||
byte priority = 3;
|
||||
Headers headers = new Headers();
|
||||
headers.put("a", "b");
|
||||
headers.put("c", "d");
|
||||
SynStreamFrame frame1 = new SynStreamFrame(SPDY.V2, flags, streamId, associatedStreamId, priority, headers);
|
||||
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.SYN_STREAM, frame2.getType());
|
||||
SynStreamFrame synStream = (SynStreamFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V2, synStream.getVersion());
|
||||
Assert.assertEquals(streamId, synStream.getStreamId());
|
||||
Assert.assertEquals(associatedStreamId, synStream.getAssociatedStreamId());
|
||||
Assert.assertEquals(flags, synStream.getFlags());
|
||||
Assert.assertEquals(priority, synStream.getPriority());
|
||||
Assert.assertEquals(headers, synStream.getHeaders());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseOneByteAtATime() throws Exception
|
||||
{
|
||||
byte flags = SynInfo.FLAG_CLOSE;
|
||||
int streamId = 13;
|
||||
int associatedStreamId = 11;
|
||||
byte priority = 3;
|
||||
Headers headers = new Headers();
|
||||
headers.put("a", "b");
|
||||
headers.put("c", "d");
|
||||
SynStreamFrame frame1 = new SynStreamFrame(SPDY.V2, flags, streamId, associatedStreamId, priority, headers);
|
||||
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.SYN_STREAM, frame2.getType());
|
||||
SynStreamFrame synStream = (SynStreamFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V2, synStream.getVersion());
|
||||
Assert.assertEquals(streamId, synStream.getStreamId());
|
||||
Assert.assertEquals(associatedStreamId, synStream.getAssociatedStreamId());
|
||||
Assert.assertEquals(flags, synStream.getFlags());
|
||||
Assert.assertEquals(priority, synStream.getPriority());
|
||||
Assert.assertEquals(headers, synStream.getHeaders());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.SessionException;
|
||||
import org.eclipse.jetty.spdy.StreamException;
|
||||
import org.eclipse.jetty.spdy.parser.Parser;
|
||||
|
||||
public class TestSPDYParserListener implements Parser.Listener
|
||||
{
|
||||
private ControlFrame controlFrame;
|
||||
private DataFrame dataFrame;
|
||||
private ByteBuffer data;
|
||||
|
||||
@Override
|
||||
public void onControlFrame(ControlFrame frame)
|
||||
{
|
||||
this.controlFrame = frame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataFrame(DataFrame frame, ByteBuffer data)
|
||||
{
|
||||
this.dataFrame = frame;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamException(StreamException x)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionException(SessionException x)
|
||||
{
|
||||
}
|
||||
|
||||
public ControlFrame getControlFrame()
|
||||
{
|
||||
return controlFrame;
|
||||
}
|
||||
|
||||
public DataFrame getDataFrame()
|
||||
{
|
||||
return dataFrame;
|
||||
}
|
||||
|
||||
public ByteBuffer getData()
|
||||
{
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -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.frames;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
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.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class WindowUpdateGenerateParseTest
|
||||
{
|
||||
@Test
|
||||
public void testGenerateParse() throws Exception
|
||||
{
|
||||
int streamId = 13;
|
||||
int windowDelta = 17;
|
||||
WindowUpdateFrame frame1 = new WindowUpdateFrame(SPDY.V2, streamId, windowDelta);
|
||||
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.WINDOW_UPDATE, frame2.getType());
|
||||
WindowUpdateFrame windowUpdate = (WindowUpdateFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V2, windowUpdate.getVersion());
|
||||
Assert.assertEquals(streamId, windowUpdate.getStreamId());
|
||||
Assert.assertEquals(0, windowUpdate.getFlags());
|
||||
Assert.assertEquals(windowDelta, windowUpdate.getWindowDelta());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseOneByteAtATime() throws Exception
|
||||
{
|
||||
int streamId = 13;
|
||||
int windowDelta = 17;
|
||||
WindowUpdateFrame frame1 = new WindowUpdateFrame(SPDY.V2, streamId, windowDelta);
|
||||
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.WINDOW_UPDATE, frame2.getType());
|
||||
WindowUpdateFrame windowUpdate = (WindowUpdateFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V2, windowUpdate.getVersion());
|
||||
Assert.assertEquals(streamId, windowUpdate.getStreamId());
|
||||
Assert.assertEquals(0, windowUpdate.getFlags());
|
||||
Assert.assertEquals(windowDelta, windowUpdate.getWindowDelta());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jetty.spdy.StandardCompressionFactory;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrameType;
|
||||
import org.eclipse.jetty.spdy.frames.SynStreamFrame;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class LiveChromiumRequestParserTest
|
||||
{
|
||||
@Test
|
||||
public void testSynStream() throws Exception
|
||||
{
|
||||
// Bytes taken with wireshark from a live chromium request
|
||||
byte[] bytes1 = toBytes("" +
|
||||
"800200010100011a0000000100000000000038eadfa251b262e0626083a41706" +
|
||||
"7bb80b75302cd6ae4017cdcdb12eb435d0b3d4d1d2d702b32c18f850732c036f" +
|
||||
"68889bae850e44da94811f2d0b3308821ca80375a14e714a72065c0d2cd619f8" +
|
||||
"52f37443837552f3a076b080b234033f28de73404c2b43630b135306b65c6059" +
|
||||
"929fc2c0ecee1ac2c0560c4c7eb9a940b52525050ccc206f32ea337021f22643" +
|
||||
"bb6f7e55664e4ea2bea99e81824684a1a135400a3e9979a5150a151666f16626" +
|
||||
"9a0a8e40afa686a726796796e89b1a9bea992b68787b84f8fae828e46466a72a" +
|
||||
"b8a72667e76b2a842695e69594ea1b0203d640c1390358e06496e6ea1b9ae901" +
|
||||
"c3c5d048cfdc1c22988a22149c98965894093195811d1a150c1cb01802000000" +
|
||||
"ffff");
|
||||
byte[] bytes2 = toBytes("" +
|
||||
"800200010100002700000003000000008000428a106660d00ee640e5d14f4b2c" +
|
||||
"cb0466313d203154c217000000ffff");
|
||||
|
||||
final AtomicReference<ControlFrame> frameRef = new AtomicReference<>();
|
||||
Parser parser = new Parser(new StandardCompressionFactory().newDecompressor());
|
||||
parser.addListener(new Parser.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onControlFrame(ControlFrame frame)
|
||||
{
|
||||
frameRef.set(frame);
|
||||
}
|
||||
});
|
||||
parser.parse(ByteBuffer.wrap(bytes1));
|
||||
|
||||
ControlFrame frame = frameRef.get();
|
||||
Assert.assertNotNull(frame);
|
||||
Assert.assertEquals(ControlFrameType.SYN_STREAM, frame.getType());
|
||||
SynStreamFrame synStream = (SynStreamFrame)frame;
|
||||
Assert.assertEquals(2, synStream.getVersion());
|
||||
Assert.assertEquals(1, synStream.getStreamId());
|
||||
Assert.assertEquals(0, synStream.getAssociatedStreamId());
|
||||
Assert.assertEquals(0, synStream.getPriority());
|
||||
Assert.assertNotNull(synStream.getHeaders());
|
||||
Assert.assertFalse(synStream.getHeaders().isEmpty());
|
||||
|
||||
frameRef.set(null);
|
||||
parser.parse(ByteBuffer.wrap(bytes2));
|
||||
|
||||
frame = frameRef.get();
|
||||
Assert.assertNotNull(frame);
|
||||
Assert.assertEquals(ControlFrameType.SYN_STREAM, frame.getType());
|
||||
synStream = (SynStreamFrame)frame;
|
||||
Assert.assertEquals(2, synStream.getVersion());
|
||||
Assert.assertEquals(3, synStream.getStreamId());
|
||||
Assert.assertEquals(0, synStream.getAssociatedStreamId());
|
||||
Assert.assertEquals(2, synStream.getPriority());
|
||||
Assert.assertNotNull(synStream.getHeaders());
|
||||
Assert.assertFalse(synStream.getHeaders().isEmpty());
|
||||
}
|
||||
|
||||
private byte[] toBytes(String hexs)
|
||||
{
|
||||
byte[] bytes = new byte[hexs.length() / 2];
|
||||
for (int i = 0; i < hexs.length(); i += 2)
|
||||
{
|
||||
String hex = hexs.substring(i, i + 2);
|
||||
bytes[i / 2] = (byte)Integer.parseInt(hex, 16);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ParseVersusCacheBenchmarkTest
|
||||
{
|
||||
@Ignore
|
||||
@Test
|
||||
public void testParseVersusCache() throws Exception
|
||||
{
|
||||
// The parser knows the header name and value lengths, so it creates strings
|
||||
// out of the bytes; however, this involves creating a byte[] copy the bytes,
|
||||
// and creating a new String.
|
||||
// The alternative is to use a cache<ByteBuffer, String>. Is that faster ?
|
||||
// See also: http://jeremymanson.blogspot.com/2008/04/immutability-in-java.html
|
||||
|
||||
String name = "Content-Type";
|
||||
String value = "application/octect-stream";
|
||||
Charset charset = Charset.forName("ISO-8859-1");
|
||||
ByteBuffer buffer = ByteBuffer.wrap((name + value).getBytes(charset));
|
||||
int iterations = 100_000_000;
|
||||
|
||||
long begin = System.nanoTime();
|
||||
for (int i = 0; i < iterations; ++i)
|
||||
{
|
||||
byte[] nameBytes = new byte[name.length()];
|
||||
buffer.get(nameBytes);
|
||||
String name2 = new String(nameBytes, charset);
|
||||
Assert.assertEquals(name2, name);
|
||||
|
||||
byte[] valueBytes = new byte[value.length()];
|
||||
buffer.get(valueBytes);
|
||||
String value2 = new String(valueBytes, charset);
|
||||
Assert.assertEquals(value2, value);
|
||||
|
||||
buffer.flip();
|
||||
}
|
||||
long end = System.nanoTime();
|
||||
System.err.printf("parse time: %d%n", TimeUnit.NANOSECONDS.toMillis(end - begin));
|
||||
|
||||
Map<ByteBuffer, String> map = new HashMap<>();
|
||||
map.put(ByteBuffer.wrap(name.getBytes(charset)), name);
|
||||
map.put(ByteBuffer.wrap(value.getBytes(charset)), value);
|
||||
final Map<ByteBuffer, String> cache = Collections.unmodifiableMap(map);
|
||||
|
||||
begin = System.nanoTime();
|
||||
for (int i = 0; i < iterations; ++i)
|
||||
{
|
||||
buffer.limit(buffer.position() + name.length());
|
||||
String name2 = cache.get(buffer);
|
||||
Assert.assertEquals(name2, name);
|
||||
|
||||
buffer.position(buffer.limit());
|
||||
buffer.limit(buffer.position() + value.length());
|
||||
String value2 = cache.get(buffer);
|
||||
Assert.assertEquals(value2, value);
|
||||
|
||||
buffer.position(buffer.limit());
|
||||
buffer.flip();
|
||||
}
|
||||
end = System.nanoTime();
|
||||
System.err.printf("cache time: %d%n", TimeUnit.NANOSECONDS.toMillis(end - begin));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package org.eclipse.jetty.spdy.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.spdy.SessionException;
|
||||
import org.eclipse.jetty.spdy.StandardByteBufferPool;
|
||||
import org.eclipse.jetty.spdy.StandardCompressionFactory;
|
||||
import org.eclipse.jetty.spdy.StreamException;
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
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.SynStreamFrame;
|
||||
import org.eclipse.jetty.spdy.generator.Generator;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class UnknownControlFrameTest
|
||||
{
|
||||
@Test
|
||||
public void testUnknownControlFrame() throws Exception
|
||||
{
|
||||
SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, new Headers());
|
||||
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
|
||||
ByteBuffer buffer = generator.control(frame);
|
||||
// Change the frame type to unknown
|
||||
buffer.putShort(2, (short)0);
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Parser parser = new Parser(new StandardCompressionFactory.StandardDecompressor());
|
||||
parser.addListener(new Parser.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onControlFrame(ControlFrame frame)
|
||||
{
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataFrame(DataFrame frame, ByteBuffer data)
|
||||
{
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamException(StreamException x)
|
||||
{
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionException(SessionException x)
|
||||
{
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
parser.parse(buffer);
|
||||
|
||||
Assert.assertFalse(latch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
# LOG4J levels: OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL
|
||||
#
|
||||
log4j.rootLogger=ALL,CONSOLE
|
||||
|
||||
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
|
||||
#log4j.appender.CONSOLE.threshold=INFO
|
||||
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
|
||||
#log4j.appender.CONSOLE.layout.ConversionPattern=%d %t [%5p][%c{1}] %m%n
|
||||
log4j.appender.CONSOLE.layout.ConversionPattern=%d %t [%5p][%c{1}] %m%n
|
||||
log4j.appender.CONSOLE.target=System.err
|
||||
|
||||
# Level tuning
|
||||
log4j.logger.org.eclipse.jetty=INFO
|
||||
#log4j.logger.org.eclipse.jetty.spdy=DEBUG
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.spdy</groupId>
|
||||
<artifactId>spdy-parent</artifactId>
|
||||
<version>9.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spdy-jetty-http-webapp</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<name>Jetty :: SPDY :: Jetty HTTP Web Application</name>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>config</descriptorRef>
|
||||
</descriptorRefs>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!--
|
||||
<plugin>
|
||||
<groupId>org.mortbay.jetty</groupId>
|
||||
<artifactId>jetty-maven-plugin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<configuration>
|
||||
<stopPort>8888</stopPort>
|
||||
<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
|
||||
</jvmArgs>
|
||||
<jettyXml>${basedir}/src/main/config/etc/jetty-spdy.xml</jettyXml>
|
||||
<contextPath>/</contextPath>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.spdy</groupId>
|
||||
<artifactId>spdy-jetty-http</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<version>${slf4j-version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
-->
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
|
||||
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
|
||||
<New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
|
||||
<Set name="keyStorePath">src/main/resources/keystore.jks</Set>
|
||||
<Set name="keyStorePassword">storepwd</Set>
|
||||
<Set name="trustStore">src/main/resources/truststore.jks</Set>
|
||||
<Set name="trustStorePassword">storepwd</Set>
|
||||
<Set name="protocol">TLSv1</Set>
|
||||
</New>
|
||||
|
||||
<Call name="addConnector">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.spdy.http.HTTPSPDYServerConnector">
|
||||
<Set name="Port">8080</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
<Call name="addConnector">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.spdy.http.HTTPSPDYServerConnector">
|
||||
<Arg>
|
||||
<Ref id="sslContextFactory" />
|
||||
</Arg>
|
||||
<Set name="Port">8443</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
|
||||
</Configure>
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue