Issue #2679 - h2spec compliance.
Integrated HPACK modifications to comply with the specification. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
0da9225056
commit
4ace2e4d8d
|
@ -98,7 +98,7 @@ public class HTTP2ClientSession extends HTTP2Session
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Stream #{} not found", streamId);
|
||||
if ((streamId & 1) == 1)
|
||||
if (isClientStream(streamId))
|
||||
{
|
||||
// Normal stream.
|
||||
// Headers or trailers arriving after
|
||||
|
@ -134,11 +134,14 @@ public class HTTP2ClientSession extends HTTP2Session
|
|||
else
|
||||
{
|
||||
IStream pushStream = createRemoteStream(pushStreamId);
|
||||
if (pushStream != null)
|
||||
{
|
||||
pushStream.process(frame, Callback.NOOP);
|
||||
Stream.Listener listener = notifyPush(stream, pushStream, frame);
|
||||
pushStream.setListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Stream.Listener notifyPush(IStream stream, IStream pushStream, PushPromiseFrame frame)
|
||||
{
|
||||
|
|
|
@ -734,7 +734,21 @@ public abstract class FlowControlStrategyTest
|
|||
public void testClientExceedingSessionWindow() throws Exception
|
||||
{
|
||||
// On server, we don't consume the data.
|
||||
start(new ServerSessionListener.Adapter());
|
||||
start(new ServerSessionListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
return new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
// Do not succeed the callback.
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
final CountDownLatch closeLatch = new CountDownLatch(1);
|
||||
Session session = newClient(new Session.Listener.Adapter()
|
||||
|
@ -805,6 +819,19 @@ public abstract class FlowControlStrategyTest
|
|||
((ISession)session).updateRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
|
||||
return super.onPreface(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
return new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
// Do not succeed the callback.
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
final CountDownLatch closeLatch = new CountDownLatch(1);
|
||||
|
|
|
@ -46,12 +46,7 @@ import org.eclipse.jetty.http2.api.server.ServerSessionListener;
|
|||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.GoAwayFrame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.http2.frames.PingFrame;
|
||||
import org.eclipse.jetty.http2.frames.PriorityFrame;
|
||||
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
|
||||
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
|
||||
import org.eclipse.jetty.http2.parser.ServerParser;
|
||||
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
|
@ -752,7 +747,7 @@ public class HTTP2Test extends AbstractTest
|
|||
@Override
|
||||
protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener)
|
||||
{
|
||||
return super.newServerParser(connector, new ServerParserListenerWrapper(listener)
|
||||
return super.newServerParser(connector, new ServerParser.Listener.Wrapper(listener)
|
||||
{
|
||||
@Override
|
||||
public void onGoAway(GoAwayFrame frame)
|
||||
|
@ -806,80 +801,4 @@ public class HTTP2Test extends AbstractTest
|
|||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ServerParserListenerWrapper implements ServerParser.Listener
|
||||
{
|
||||
private final ServerParser.Listener listener;
|
||||
|
||||
private ServerParserListenerWrapper(ServerParser.Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreface()
|
||||
{
|
||||
listener.onPreface();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onData(DataFrame frame)
|
||||
{
|
||||
listener.onData(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeaders(HeadersFrame frame)
|
||||
{
|
||||
listener.onHeaders(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPriority(PriorityFrame frame)
|
||||
{
|
||||
listener.onPriority(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReset(ResetFrame frame)
|
||||
{
|
||||
listener.onReset(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSettings(SettingsFrame frame)
|
||||
{
|
||||
listener.onSettings(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPushPromise(PushPromiseFrame frame)
|
||||
{
|
||||
listener.onPushPromise(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPing(PingFrame frame)
|
||||
{
|
||||
listener.onPing(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGoAway(GoAwayFrame frame)
|
||||
{
|
||||
listener.onGoAway(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowUpdate(WindowUpdateFrame frame)
|
||||
{
|
||||
listener.onWindowUpdate(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailure(int error, String reason)
|
||||
{
|
||||
listener.onConnectionFailure(error, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,13 +26,13 @@ import java.util.List;
|
|||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
|
@ -42,11 +42,13 @@ import org.eclipse.jetty.http2.api.server.ServerSessionListener;
|
|||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.Frame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.FuturePromise;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
@ -106,7 +108,7 @@ public class TrailersTest extends AbstractTest
|
|||
start(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
Request jettyRequest = (Request)request;
|
||||
// No trailers yet.
|
||||
|
@ -238,7 +240,7 @@ public class TrailersTest extends AbstractTest
|
|||
start(new EmptyHttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
Request jettyRequest = (Request)request;
|
||||
Response jettyResponse = jettyRequest.getResponse();
|
||||
|
@ -287,4 +289,63 @@ public class TrailersTest extends AbstractTest
|
|||
Assert.assertTrue(trailers.isEndStream());
|
||||
Assert.assertThat(trailers.getMetaData().getFields().get(trailerName), Matchers.equalTo(trailerValue));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestTrailerInvalidHpack() throws Exception
|
||||
{
|
||||
CountDownLatch serverLatch = new CountDownLatch(1);
|
||||
start(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
// Read the content to read the trailers
|
||||
ServletInputStream input = request.getInputStream();
|
||||
while (true)
|
||||
{
|
||||
int read = input.read();
|
||||
if (read < 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
serverLatch.countDown();
|
||||
throw x;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
CountDownLatch clientLatch = new CountDownLatch(1);
|
||||
Session session = newClient(new Session.Listener.Adapter());
|
||||
MetaData.Request request = newRequest("POST", new HttpFields());
|
||||
HeadersFrame requestFrame = new HeadersFrame(request, null, false);
|
||||
FuturePromise<Stream> promise = new FuturePromise<>();
|
||||
session.newStream(requestFrame, promise, new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onReset(Stream stream, ResetFrame frame)
|
||||
{
|
||||
clientLatch.countDown();
|
||||
}
|
||||
});
|
||||
Stream stream = promise.get(5, TimeUnit.SECONDS);
|
||||
ByteBuffer data = ByteBuffer.wrap(StringUtil.getUtf8Bytes("hello"));
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
stream.data(new DataFrame(stream.getId(), data, false), completable);
|
||||
completable.thenRun(() ->
|
||||
{
|
||||
// Invalid trailer: cannot contain pseudo headers.
|
||||
HttpFields trailerFields = new HttpFields();
|
||||
trailerFields.put(HttpHeader.C_METHOD, "GET");
|
||||
MetaData trailer = new MetaData(HttpVersion.HTTP_2, trailerFields);
|
||||
HeadersFrame trailerFrame = new HeadersFrame(stream.getId(), trailer, null, true);
|
||||
stream.headers(trailerFrame, Callback.NOOP);
|
||||
});
|
||||
|
||||
Assert.assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
|
||||
Assert.assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package org.eclipse.jetty.http2;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -96,6 +97,19 @@ public enum ErrorCode
|
|||
return Codes.codes.get(error);
|
||||
}
|
||||
|
||||
public static String toString(int error, String dft)
|
||||
{
|
||||
ErrorCode errorCode = from(error);
|
||||
String result;
|
||||
if (errorCode != null)
|
||||
result = errorCode.name().toLowerCase(Locale.ENGLISH);
|
||||
else if (dft == null)
|
||||
result = String.valueOf(error);
|
||||
else
|
||||
result = dft;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class Codes
|
||||
{
|
||||
private static final Map<Integer, ErrorCode> codes = new HashMap<>();
|
||||
|
|
|
@ -27,14 +27,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.GoAwayFrame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.http2.frames.PingFrame;
|
||||
import org.eclipse.jetty.http2.frames.PriorityFrame;
|
||||
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
|
||||
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
|
||||
import org.eclipse.jetty.http2.parser.Parser;
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
|
@ -220,6 +212,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
|
|||
private final Callback fillableCallback = new FillableCallback();
|
||||
private NetworkBuffer buffer;
|
||||
private boolean shutdown;
|
||||
private boolean failed;
|
||||
|
||||
private void setInputBuffer(ByteBuffer byteBuffer)
|
||||
{
|
||||
|
@ -237,7 +230,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
|
|||
if (task != null)
|
||||
return task;
|
||||
|
||||
if (isFillInterested() || shutdown)
|
||||
if (isFillInterested() || shutdown || failed)
|
||||
return null;
|
||||
|
||||
if (buffer == null)
|
||||
|
@ -248,11 +241,22 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
|
|||
if (parse)
|
||||
{
|
||||
buffer.retain();
|
||||
|
||||
boolean released;
|
||||
try
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
parser.parse(buffer.buffer);
|
||||
|
||||
boolean released = buffer.tryRelease();
|
||||
if (failed)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
released = buffer.tryRelease();
|
||||
if (failed && released)
|
||||
releaseNetworkBuffer();
|
||||
}
|
||||
|
||||
task = pollTask();
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -345,13 +349,11 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
|
|||
}
|
||||
}
|
||||
|
||||
private class ParserListener implements Parser.Listener
|
||||
private class ParserListener extends Parser.Listener.Wrapper
|
||||
{
|
||||
private final Parser.Listener listener;
|
||||
|
||||
private ParserListener(Parser.Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
super(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -363,58 +365,11 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
|
|||
session.onData(frame, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeaders(HeadersFrame frame)
|
||||
{
|
||||
listener.onHeaders(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPriority(PriorityFrame frame)
|
||||
{
|
||||
listener.onPriority(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReset(ResetFrame frame)
|
||||
{
|
||||
listener.onReset(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSettings(SettingsFrame frame)
|
||||
{
|
||||
listener.onSettings(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPushPromise(PushPromiseFrame frame)
|
||||
{
|
||||
listener.onPushPromise(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPing(PingFrame frame)
|
||||
{
|
||||
listener.onPing(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGoAway(GoAwayFrame frame)
|
||||
{
|
||||
listener.onGoAway(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowUpdate(WindowUpdateFrame frame)
|
||||
{
|
||||
listener.onWindowUpdate(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailure(int error, String reason)
|
||||
{
|
||||
listener.onConnectionFailure(error, reason);
|
||||
producer.failed = true;
|
||||
super.onConnectionFailure(error, reason);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.eclipse.jetty.http2.api.Session;
|
|||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.DisconnectFrame;
|
||||
import org.eclipse.jetty.http2.frames.FailureFrame;
|
||||
import org.eclipse.jetty.http2.frames.Frame;
|
||||
import org.eclipse.jetty.http2.frames.FrameType;
|
||||
import org.eclipse.jetty.http2.frames.GoAwayFrame;
|
||||
|
@ -106,7 +107,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
this.maxLocalStreams = -1;
|
||||
this.maxRemoteStreams = -1;
|
||||
this.localStreamIds.set(initialStreamId);
|
||||
this.lastRemoteStreamId.set((initialStreamId & 0x01) == 0x01 ? 0 : -1);
|
||||
this.lastRemoteStreamId.set(isClientStream(initialStreamId) ? 0 : -1);
|
||||
this.streamIdleTimeout = endPoint.getIdleTimeout();
|
||||
this.sendWindow.set(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
|
||||
this.recvWindow.set(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
|
||||
|
@ -251,7 +252,9 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
// We must enlarge the session flow control window,
|
||||
// otherwise other requests will be stalled.
|
||||
flowControl.onDataConsumed(this, null, flowControlLength);
|
||||
if (isRemoteStreamClosed(streamId))
|
||||
boolean local = (streamId & 1) == (localStreamIds.get() & 1);
|
||||
boolean closed = local ? isLocalStreamClosed(streamId) : isRemoteStreamClosed(streamId);
|
||||
if (closed)
|
||||
reset(new ResetFrame(streamId, ErrorCode.STREAM_CLOSED_ERROR.code), callback);
|
||||
else
|
||||
onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_data_frame", callback);
|
||||
|
@ -288,7 +291,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
IStream stream = getStream(streamId);
|
||||
if (stream != null)
|
||||
{
|
||||
stream.process(frame, new ResetCallback());
|
||||
stream.process(frame, new OnResetCallback());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -501,6 +504,17 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamFailure(int streamId, int error, String reason)
|
||||
{
|
||||
Callback callback = new ResetCallback(streamId, error, Callback.NOOP);
|
||||
IStream stream = getStream(streamId);
|
||||
if (stream != null)
|
||||
stream.process(new FailureFrame(error, reason), callback);
|
||||
else
|
||||
callback.succeeded();
|
||||
}
|
||||
|
||||
private boolean sumOverflows(int a, int b)
|
||||
{
|
||||
try
|
||||
|
@ -520,7 +534,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
onConnectionFailure(error, reason, Callback.NOOP);
|
||||
}
|
||||
|
||||
private void onConnectionFailure(int error, String reason, Callback callback)
|
||||
protected void onConnectionFailure(int error, String reason, Callback callback)
|
||||
{
|
||||
notifyFailure(this, new IOException(String.format("%d/%s", error, reason)), new CloseCallback(error, reason, callback));
|
||||
}
|
||||
|
@ -1181,6 +1195,12 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
}
|
||||
}
|
||||
|
||||
protected static boolean isClientStream(int streamId)
|
||||
{
|
||||
// Client-initiated stream ids are odd.
|
||||
return (streamId & 1) == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(Appendable out, String indent) throws IOException
|
||||
{
|
||||
|
@ -1509,7 +1529,37 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
}
|
||||
}
|
||||
|
||||
private class ResetCallback implements Callback
|
||||
private class ResetCallback extends Callback.Nested
|
||||
{
|
||||
private final int streamId;
|
||||
private final int error;
|
||||
|
||||
private ResetCallback(int streamId, int error, Callback callback)
|
||||
{
|
||||
super(callback);
|
||||
this.streamId = streamId;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
complete();
|
||||
}
|
||||
|
||||
private void complete()
|
||||
{
|
||||
reset(new ResetFrame(streamId, error), getCallback());
|
||||
}
|
||||
}
|
||||
|
||||
private class OnResetCallback implements Callback
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
|
@ -1535,17 +1585,16 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
}
|
||||
}
|
||||
|
||||
private class CloseCallback implements Callback
|
||||
private class CloseCallback extends Callback.Nested
|
||||
{
|
||||
private final int error;
|
||||
private final String reason;
|
||||
private final Callback callback;
|
||||
|
||||
private CloseCallback(int error, String reason, Callback callback)
|
||||
{
|
||||
super(callback);
|
||||
this.error = error;
|
||||
this.reason = reason;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1560,15 +1609,9 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvocationType getInvocationType()
|
||||
{
|
||||
return InvocationType.NON_BLOCKING;
|
||||
}
|
||||
|
||||
private void complete()
|
||||
{
|
||||
close(error, reason, callback);
|
||||
close(error, reason, getCallback());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.FailureFrame;
|
||||
import org.eclipse.jetty.http2.frames.Frame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
|
||||
|
@ -255,6 +256,11 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
onWindowUpdate((WindowUpdateFrame)frame, callback);
|
||||
break;
|
||||
}
|
||||
case FAILURE:
|
||||
{
|
||||
onFailure((FailureFrame)frame, callback);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
|
@ -321,6 +327,11 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
callback.succeeded();
|
||||
}
|
||||
|
||||
private void onFailure(FailureFrame frame, Callback callback)
|
||||
{
|
||||
notifyFailure(this, frame, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateClose(boolean update, CloseState.Event event)
|
||||
{
|
||||
|
@ -498,9 +509,9 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
|
||||
private void notifyData(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
final Listener listener = this.listener;
|
||||
if (listener == null)
|
||||
return;
|
||||
Listener listener = this.listener;
|
||||
if (listener != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onData(stream, frame, callback);
|
||||
|
@ -508,14 +519,20 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
catch (Throwable x)
|
||||
{
|
||||
LOG.info("Failure while notifying listener " + listener, x);
|
||||
callback.failed(x);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
callback.succeeded();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyReset(Stream stream, ResetFrame frame, Callback callback)
|
||||
{
|
||||
final Listener listener = this.listener;
|
||||
if (listener == null)
|
||||
return;
|
||||
Listener listener = this.listener;
|
||||
if (listener != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onReset(stream, frame, callback);
|
||||
|
@ -523,6 +540,12 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
catch (Throwable x)
|
||||
{
|
||||
LOG.info("Failure while notifying listener " + listener, x);
|
||||
callback.failed(x);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
callback.succeeded();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -542,6 +565,27 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
}
|
||||
}
|
||||
|
||||
private void notifyFailure(Stream stream, FailureFrame frame, Callback callback)
|
||||
{
|
||||
Listener listener = this.listener;
|
||||
if (listener != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onFailure(stream, frame.getError(), frame.getReason(), callback);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.info("Failure while notifying listener " + listener, x);
|
||||
callback.failed(x);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
callback.succeeded();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dump()
|
||||
{
|
||||
|
|
|
@ -214,6 +214,11 @@ public interface Stream
|
|||
return true;
|
||||
}
|
||||
|
||||
public default void onFailure(Stream stream, int error, String reason, Callback callback)
|
||||
{
|
||||
callback.succeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Empty implementation of {@link Listener}</p>
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 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.http2.frames;
|
||||
|
||||
public class FailureFrame extends Frame
|
||||
{
|
||||
private final int error;
|
||||
private final String reason;
|
||||
|
||||
public FailureFrame(int error, String reason)
|
||||
{
|
||||
super(FrameType.FAILURE);
|
||||
this.error = error;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public int getError()
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
public String getReason()
|
||||
{
|
||||
return reason;
|
||||
}
|
||||
}
|
|
@ -35,7 +35,8 @@ public enum FrameType
|
|||
CONTINUATION(9),
|
||||
// Synthetic frames only needed by the implementation.
|
||||
PREFACE(10),
|
||||
DISCONNECT(11);
|
||||
DISCONNECT(11),
|
||||
FAILURE(12);
|
||||
|
||||
public static FrameType from(int type)
|
||||
{
|
||||
|
|
|
@ -76,11 +76,10 @@ public class GoAwayFrame extends Frame
|
|||
@Override
|
||||
public String toString()
|
||||
{
|
||||
ErrorCode errorCode = ErrorCode.from(error);
|
||||
return String.format("%s,%d/%s/%s/%s",
|
||||
super.toString(),
|
||||
lastStreamId,
|
||||
errorCode != null ? errorCode.toString() : String.valueOf(error),
|
||||
ErrorCode.toString(error, null),
|
||||
tryConvertPayload(),
|
||||
closeState);
|
||||
}
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.http2.frames;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jetty.http2.ErrorCode;
|
||||
|
||||
public class ResetFrame extends Frame
|
||||
|
@ -49,8 +47,6 @@ public class ResetFrame extends Frame
|
|||
@Override
|
||||
public String toString()
|
||||
{
|
||||
ErrorCode errorCode = ErrorCode.from(error);
|
||||
String reason = errorCode == null ? "error=" + error : errorCode.name().toLowerCase(Locale.ENGLISH);
|
||||
return String.format("%s#%d{%s}", super.toString(), streamId, reason);
|
||||
return String.format("%s#%d{%s}", super.toString(), streamId, ErrorCode.toString(error, null));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -222,4 +222,21 @@ public abstract class BodyParser
|
|||
LOG.info("Failure while notifying listener " + listener, x);
|
||||
}
|
||||
}
|
||||
|
||||
protected void streamFailure(int streamId, int error, String reason)
|
||||
{
|
||||
notifyStreamFailure(streamId, error, reason);
|
||||
}
|
||||
|
||||
private void notifyStreamFailure(int streamId, int error, String reason)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onStreamFailure(streamId, error, reason);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.info("Failure while notifying listener " + listener, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ public class ContinuationBodyParser extends BodyParser
|
|||
headerBlockFragments.storeFragment(buffer, length, last);
|
||||
reset();
|
||||
if (last)
|
||||
onHeaders();
|
||||
return onHeaders();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -94,12 +94,17 @@ public class ContinuationBodyParser extends BodyParser
|
|||
return false;
|
||||
}
|
||||
|
||||
private void onHeaders()
|
||||
private boolean onHeaders()
|
||||
{
|
||||
ByteBuffer headerBlock = headerBlockFragments.complete();
|
||||
MetaData metaData = headerBlockParser.parse(headerBlock, headerBlock.remaining());
|
||||
if (metaData == HeaderBlockParser.SESSION_FAILURE)
|
||||
return false;
|
||||
if (metaData == null || metaData == HeaderBlockParser.STREAM_FAILURE)
|
||||
return true;
|
||||
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, headerBlockFragments.getPriorityFrame(), headerBlockFragments.isEndStream());
|
||||
notifyHeaders(frame);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void reset()
|
||||
|
|
|
@ -20,26 +20,46 @@ package org.eclipse.jetty.http2.parser;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http2.ErrorCode;
|
||||
import org.eclipse.jetty.http2.hpack.HpackDecoder;
|
||||
import org.eclipse.jetty.http2.hpack.HpackException.SessionException;
|
||||
import org.eclipse.jetty.http2.hpack.HpackException.StreamException;
|
||||
import org.eclipse.jetty.http2.hpack.HpackException;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class HeaderBlockParser
|
||||
{
|
||||
public static final MetaData STREAM_FAILURE = new MetaData(HttpVersion.HTTP_2, null);
|
||||
public static final MetaData SESSION_FAILURE = new MetaData(HttpVersion.HTTP_2, null);
|
||||
private static final Logger LOG = Log.getLogger(HeaderBlockParser.class);
|
||||
|
||||
private final HeaderParser headerParser;
|
||||
private final ByteBufferPool byteBufferPool;
|
||||
private final HpackDecoder hpackDecoder;
|
||||
private final BodyParser notifier;
|
||||
private ByteBuffer blockBuffer;
|
||||
|
||||
public HeaderBlockParser(ByteBufferPool byteBufferPool, HpackDecoder hpackDecoder)
|
||||
public HeaderBlockParser(HeaderParser headerParser, ByteBufferPool byteBufferPool, HpackDecoder hpackDecoder, BodyParser notifier)
|
||||
{
|
||||
this.headerParser = headerParser;
|
||||
this.byteBufferPool = byteBufferPool;
|
||||
this.hpackDecoder = hpackDecoder;
|
||||
this.notifier = notifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses @{code blockLength} HPACK bytes from the given {@code buffer}.
|
||||
*
|
||||
* @param buffer the buffer to parse
|
||||
* @param blockLength the length of the HPACK block
|
||||
* @return null, if the buffer contains less than {@code blockLength} bytes;
|
||||
* {@link #STREAM_FAILURE} if parsing the HPACK block produced a stream failure;
|
||||
* {@link #SESSION_FAILURE} if parsing the HPACK block produced a session failure;
|
||||
* a valid MetaData object if the parsing was successful.
|
||||
*/
|
||||
public MetaData parse(ByteBuffer buffer, int blockLength)
|
||||
{
|
||||
// We must wait for the all the bytes of the header block to arrive.
|
||||
|
@ -77,35 +97,28 @@ public class HeaderBlockParser
|
|||
|
||||
try
|
||||
{
|
||||
MetaData metadata = hpackDecoder.decode(toDecode);
|
||||
|
||||
if (metadata instanceof MetaData.Request)
|
||||
{
|
||||
// TODO this must be an initial HEADERs frame received by the server OR
|
||||
// TODO a push promise received by the client.
|
||||
// TODO this must not be a trailers frame
|
||||
return hpackDecoder.decode(toDecode);
|
||||
}
|
||||
else if (metadata instanceof MetaData.Response)
|
||||
catch (HpackException.StreamException x)
|
||||
{
|
||||
// TODO this must be an initial HEADERs frame received by the client
|
||||
// TODO this must not be a trailers frame
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(x);
|
||||
notifier.streamFailure(headerParser.getStreamId(), ErrorCode.PROTOCOL_ERROR.code, "invalid_hpack_block");
|
||||
return STREAM_FAILURE;
|
||||
}
|
||||
else
|
||||
catch (HpackException.CompressionException x)
|
||||
{
|
||||
// TODO this must be a trailers frame
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(x);
|
||||
notifier.connectionFailure(buffer, ErrorCode.COMPRESSION_ERROR.code, "invalid_hpack_block");
|
||||
return SESSION_FAILURE;
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
catch(StreamException ex)
|
||||
catch (HpackException.SessionException x)
|
||||
{
|
||||
// TODO reset the stream
|
||||
throw new BadMessageException("TODO");
|
||||
}
|
||||
catch(SessionException ex)
|
||||
{
|
||||
// TODO reset the session
|
||||
throw new BadMessageException("TODO");
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(x);
|
||||
notifier.connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_hpack_block");
|
||||
return SESSION_FAILURE;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -170,12 +170,15 @@ public class HeadersBodyParser extends BodyParser
|
|||
if (hasFlag(Flags.END_HEADERS))
|
||||
{
|
||||
MetaData metaData = headerBlockParser.parse(buffer, length);
|
||||
if (metaData == HeaderBlockParser.SESSION_FAILURE)
|
||||
return false;
|
||||
if (metaData != null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Parsed {} frame hpack from {}", FrameType.HEADERS, buffer);
|
||||
state = State.PADDING;
|
||||
loop = paddingLength == 0;
|
||||
if (metaData != HeaderBlockParser.STREAM_FAILURE)
|
||||
onHeaders(parentStreamId, weight, exclusive, metaData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ import org.eclipse.jetty.http2.frames.SettingsFrame;
|
|||
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
|
||||
import org.eclipse.jetty.http2.hpack.HpackDecoder;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
|
@ -63,8 +62,8 @@ public class Parser
|
|||
{
|
||||
this.listener = listener;
|
||||
this.headerParser = new HeaderParser();
|
||||
this.headerBlockParser = new HeaderBlockParser(byteBufferPool, new HpackDecoder(maxDynamicTableSize, maxHeaderSize));
|
||||
this.unknownBodyParser = new UnknownBodyParser(headerParser, listener);
|
||||
this.headerBlockParser = new HeaderBlockParser(headerParser, byteBufferPool, new HpackDecoder(maxDynamicTableSize, maxHeaderSize), unknownBodyParser);
|
||||
this.maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
|
||||
this.bodyParsers = new BodyParser[FrameType.values().length];
|
||||
}
|
||||
|
@ -265,6 +264,8 @@ public class Parser
|
|||
|
||||
public void onWindowUpdate(WindowUpdateFrame frame);
|
||||
|
||||
public void onStreamFailure(int streamId, int error, String reason);
|
||||
|
||||
public void onConnectionFailure(int error, String reason);
|
||||
|
||||
public static class Adapter implements Listener
|
||||
|
@ -314,12 +315,98 @@ public class Parser
|
|||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamFailure(int streamId, int error, String reason)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailure(int error, String reason)
|
||||
{
|
||||
LOG.warn("Connection failure: {}/{}", error, reason);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Wrapper implements Listener
|
||||
{
|
||||
private final Parser.Listener listener;
|
||||
|
||||
public Wrapper(Parser.Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public Listener getParserListener()
|
||||
{
|
||||
return listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onData(DataFrame frame)
|
||||
{
|
||||
listener.onData(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeaders(HeadersFrame frame)
|
||||
{
|
||||
listener.onHeaders(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPriority(PriorityFrame frame)
|
||||
{
|
||||
listener.onPriority(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReset(ResetFrame frame)
|
||||
{
|
||||
listener.onReset(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSettings(SettingsFrame frame)
|
||||
{
|
||||
listener.onSettings(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPushPromise(PushPromiseFrame frame)
|
||||
{
|
||||
listener.onPushPromise(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPing(PingFrame frame)
|
||||
{
|
||||
listener.onPing(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGoAway(GoAwayFrame frame)
|
||||
{
|
||||
listener.onGoAway(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowUpdate(WindowUpdateFrame frame)
|
||||
{
|
||||
listener.onWindowUpdate(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamFailure(int streamId, int error, String reason)
|
||||
{
|
||||
listener.onStreamFailure(streamId, error, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailure(int error, String reason)
|
||||
{
|
||||
listener.onConnectionFailure(error, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum State
|
||||
|
|
|
@ -125,10 +125,13 @@ public class PushPromiseBodyParser extends BodyParser
|
|||
case HEADERS:
|
||||
{
|
||||
MetaData metaData = headerBlockParser.parse(buffer, length);
|
||||
if (metaData == HeaderBlockParser.SESSION_FAILURE)
|
||||
return false;
|
||||
if (metaData != null)
|
||||
{
|
||||
state = State.PADDING;
|
||||
loop = paddingLength == 0;
|
||||
if (metaData != HeaderBlockParser.STREAM_FAILURE)
|
||||
onPushPromise(streamId, metaData);
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -158,6 +158,26 @@ public class ServerParser extends Parser
|
|||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static class Wrapper extends Parser.Listener.Wrapper implements Listener
|
||||
{
|
||||
public Wrapper(ServerParser.Listener listener)
|
||||
{
|
||||
super(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerParser.Listener getParserListener()
|
||||
{
|
||||
return (Listener)super.getParserListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreface()
|
||||
{
|
||||
getParserListener().onPreface();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum State
|
||||
|
|
|
@ -23,10 +23,8 @@ import java.nio.ByteBuffer;
|
|||
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
|
||||
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
|
|
@ -21,8 +21,11 @@ package org.eclipse.jetty.http2.hpack;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
|
@ -31,6 +34,9 @@ import org.eclipse.jetty.http.MetaData;
|
|||
import org.eclipse.jetty.http.PreEncodedHttpField;
|
||||
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
|
||||
import org.eclipse.jetty.http2.hpack.HpackContext.StaticEntry;
|
||||
import org.eclipse.jetty.util.ArrayTrie;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.Trie;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -38,17 +44,13 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
public class HpackEncoder
|
||||
{
|
||||
public static final Logger LOG = Log.getLogger(HpackEncoder.class);
|
||||
|
||||
private final static HttpField[] __status= new HttpField[599];
|
||||
|
||||
|
||||
final static EnumSet<HttpHeader> __DO_NOT_HUFFMAN =
|
||||
EnumSet.of(
|
||||
HttpHeader.AUTHORIZATION,
|
||||
HttpHeader.CONTENT_MD5,
|
||||
HttpHeader.PROXY_AUTHENTICATE,
|
||||
HttpHeader.PROXY_AUTHORIZATION);
|
||||
|
||||
final static EnumSet<HttpHeader> __DO_NOT_INDEX =
|
||||
EnumSet.of(
|
||||
// HttpHeader.C_PATH, // TODO more data needed
|
||||
|
@ -69,18 +71,21 @@ public class HpackEncoder
|
|||
HttpHeader.LAST_MODIFIED,
|
||||
HttpHeader.SET_COOKIE,
|
||||
HttpHeader.SET_COOKIE2);
|
||||
|
||||
|
||||
final static EnumSet<HttpHeader> __NEVER_INDEX =
|
||||
EnumSet.of(
|
||||
HttpHeader.AUTHORIZATION,
|
||||
HttpHeader.SET_COOKIE,
|
||||
HttpHeader.SET_COOKIE2);
|
||||
private static final PreEncodedHttpField CONNECTION_TE = new PreEncodedHttpField(HttpHeader.CONNECTION, "te");
|
||||
private static final PreEncodedHttpField TE_TRAILERS = new PreEncodedHttpField(HttpHeader.TE, "trailers");
|
||||
private static final Trie<Boolean> specialHopHeaders = new ArrayTrie<>(6);
|
||||
|
||||
static
|
||||
{
|
||||
for (HttpStatus.Code code : HttpStatus.Code.values())
|
||||
__status[code.getCode()]=new PreEncodedHttpField(HttpHeader.C_STATUS,Integer.toString(code.getCode()));
|
||||
specialHopHeaders.put("close", true);
|
||||
specialHopHeaders.put("te", true);
|
||||
}
|
||||
|
||||
private final HpackContext _context;
|
||||
|
@ -174,9 +179,30 @@ public class HpackEncoder
|
|||
encode(buffer,status);
|
||||
}
|
||||
|
||||
// Add all the other fields
|
||||
for (HttpField field : metadata)
|
||||
// Add all non-connection fields.
|
||||
HttpFields fields = metadata.getFields();
|
||||
if (fields != null)
|
||||
{
|
||||
Set<String> hopHeaders = fields.getCSV(HttpHeader.CONNECTION, false).stream()
|
||||
.filter(v -> specialHopHeaders.get(v) == Boolean.TRUE)
|
||||
.map(StringUtil::asciiToLowerCase)
|
||||
.collect(Collectors.toSet());
|
||||
for (HttpField field : fields)
|
||||
{
|
||||
if (field.getHeader() == HttpHeader.CONNECTION)
|
||||
continue;
|
||||
if (!hopHeaders.isEmpty() && hopHeaders.contains(StringUtil.asciiToLowerCase(field.getName())))
|
||||
continue;
|
||||
if (field.getHeader() == HttpHeader.TE)
|
||||
{
|
||||
if (!field.contains("trailers"))
|
||||
continue;
|
||||
encode(buffer, CONNECTION_TE);
|
||||
encode(buffer, TE_TRAILERS);
|
||||
}
|
||||
encode(buffer,field);
|
||||
}
|
||||
}
|
||||
|
||||
// Check size
|
||||
if (_maxHeaderListSize>0 && _headerListSize>_maxHeaderListSize)
|
||||
|
@ -305,7 +331,7 @@ public class HpackEncoder
|
|||
encoding="Lit"+
|
||||
((name==null)?"HuffN":("IdxN"+(name.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(4,_context.index(name)))))+
|
||||
(huffman?"HuffV":"LitV")+
|
||||
(indexed?"Idx":(never_index?"!!Idx":"!Idx"));
|
||||
(never_index?"!!Idx":"!Idx");
|
||||
}
|
||||
else if (field_size>=_context.getMaxDynamicTableSize() || header==HttpHeader.CONTENT_LENGTH && field.getValue().length()>2)
|
||||
{
|
||||
|
|
|
@ -170,19 +170,19 @@ public class MetaDataBuilder
|
|||
if ("trailers".equalsIgnoreCase(value))
|
||||
_fields.add(field);
|
||||
else
|
||||
streamException("Unsupported TE value %s", value);
|
||||
streamException("Unsupported TE value '%s'", value);
|
||||
break;
|
||||
|
||||
case CONNECTION:
|
||||
if ("TE".equalsIgnoreCase(value))
|
||||
_fields.add(field);
|
||||
else
|
||||
streamException("Connection specific field %s", header);
|
||||
streamException("Connection specific field '%s'", header);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (name.charAt(0)==':')
|
||||
streamException("Unknown pseudo header %s", name);
|
||||
streamException("Unknown pseudo header '%s'", name);
|
||||
else
|
||||
_fields.add(field);
|
||||
break;
|
||||
|
@ -191,7 +191,7 @@ public class MetaDataBuilder
|
|||
else
|
||||
{
|
||||
if (name.charAt(0)==':')
|
||||
streamException("Unknown pseudo header %s",name);
|
||||
streamException("Unknown pseudo header '%s'",name);
|
||||
else
|
||||
_fields.add(field);
|
||||
}
|
||||
|
@ -281,7 +281,7 @@ public class MetaDataBuilder
|
|||
* Check that the max size will not be exceeded.
|
||||
* @param length the length
|
||||
* @param huffman the huffman name
|
||||
* @throws SessionException
|
||||
* @throws SessionException in case of size errors
|
||||
*/
|
||||
public void checkSize(int length, boolean huffman) throws SessionException
|
||||
{
|
||||
|
|
|
@ -19,12 +19,6 @@
|
|||
|
||||
package org.eclipse.jetty.http2.hpack;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Iterator;
|
||||
|
||||
|
@ -39,6 +33,12 @@ import org.hamcrest.Matchers;
|
|||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class HpackDecoderTest
|
||||
{
|
||||
@Test
|
||||
|
@ -327,7 +327,7 @@ public class HpackDecoderTest
|
|||
}
|
||||
catch(StreamException ex)
|
||||
{
|
||||
Assert.assertThat(ex.getMessage(),Matchers.containsString("Connection specific field Connection"));
|
||||
Assert.assertThat(ex.getMessage(),Matchers.containsString("Connection specific field 'Connection'"));
|
||||
}
|
||||
|
||||
// 2: Sends a HEADERS frame that contains the TE header field with any value other than "trailers"
|
||||
|
@ -340,7 +340,7 @@ public class HpackDecoderTest
|
|||
}
|
||||
catch(StreamException ex)
|
||||
{
|
||||
Assert.assertThat(ex.getMessage(),Matchers.containsString("Unsupported TE value not_trailers"));
|
||||
Assert.assertThat(ex.getMessage(),Matchers.containsString("Unsupported TE value 'not_trailers'"));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||
org.eclipse.jetty.http2.LEVEL=INFO
|
||||
org.eclipse.jetty.http2.hpack.LEVEL=INFO
|
||||
#org.eclipse.jetty.http2.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.http2.hpack.LEVEL=DEBUG
|
||||
|
|
|
@ -23,7 +23,6 @@ import java.nio.ByteBuffer;
|
|||
import java.util.ArrayDeque;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Queue;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
|
@ -165,10 +164,8 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
|
|||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange == null)
|
||||
return;
|
||||
|
||||
ErrorCode error = ErrorCode.from(frame.getError());
|
||||
String reason = error == null ? "reset" : error.name().toLowerCase(Locale.ENGLISH);
|
||||
exchange.getRequest().abort(new IOException(reason));
|
||||
int error = frame.getError();
|
||||
exchange.getRequest().abort(new IOException(ErrorCode.toString(error, "reset_code_" + error)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -178,6 +175,13 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Stream stream, int error, String reason, Callback callback)
|
||||
{
|
||||
responseFailure(new IOException(String.format("%s/%s", ErrorCode.toString(error, null), reason)));
|
||||
callback.succeeded();
|
||||
}
|
||||
|
||||
private void notifyContent(HttpExchange exchange, DataFrame frame, Callback callback)
|
||||
{
|
||||
contentNotifier.offer(new DataInfo(exchange, frame, callback));
|
||||
|
|
|
@ -576,6 +576,40 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
|||
Assert.assertArrayEquals(bytes, response.getContent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidResponseHPack() throws Exception
|
||||
{
|
||||
start(new ServerSessionListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
// Produce an invalid HPACK block by adding a request pseudo-header to the response.
|
||||
HttpFields fields = new HttpFields();
|
||||
fields.put(":method", "get");
|
||||
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, fields, 0);
|
||||
int streamId = stream.getId();
|
||||
HeadersFrame responseFrame = new HeadersFrame(streamId, response, null, false);
|
||||
Callback.Completable callback = new Callback.Completable();
|
||||
stream.headers(responseFrame, callback);
|
||||
byte[] bytes = "hello".getBytes(StandardCharsets.US_ASCII);
|
||||
callback.thenRun(() -> stream.data(new DataFrame(streamId, ByteBuffer.wrap(bytes), true), Callback.NOOP));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send(result ->
|
||||
{
|
||||
if (result.isFailed())
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testExternalServer() throws Exception
|
||||
|
|
|
@ -115,13 +115,10 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF
|
|||
@Override
|
||||
public void onClose(Session session, GoAwayFrame frame, Callback callback)
|
||||
{
|
||||
ErrorCode error = ErrorCode.from(frame.getError());
|
||||
if (error == null)
|
||||
error = ErrorCode.STREAM_CLOSED_ERROR;
|
||||
String reason = frame.tryConvertPayload();
|
||||
if (reason != null && !reason.isEmpty())
|
||||
reason = " (" + reason + ")";
|
||||
getConnection().onSessionFailure(new EofException("HTTP/2 " + error + reason), callback);
|
||||
getConnection().onSessionFailure(new EofException(String.format("Close %s/%s", ErrorCode.toString(frame.getError(), null), reason)), callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -156,10 +153,13 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF
|
|||
@Override
|
||||
public void onReset(Stream stream, ResetFrame frame, Callback callback)
|
||||
{
|
||||
ErrorCode error = ErrorCode.from(frame.getError());
|
||||
if (error == null)
|
||||
error = ErrorCode.CANCEL_STREAM_ERROR;
|
||||
getConnection().onStreamFailure((IStream)stream, new EofException("HTTP/2 " + error), callback);
|
||||
getConnection().onStreamFailure((IStream)stream, new EofException("Reset " + ErrorCode.toString(frame.getError(), null)), callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Stream stream, int error, String reason, Callback callback)
|
||||
{
|
||||
getConnection().onStreamFailure((IStream)stream, new EofException(String.format("Failure %s/%s", ErrorCode.toString(error, null), reason)), callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -84,7 +84,7 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis
|
|||
LOG.debug("Received {}", frame);
|
||||
|
||||
int streamId = frame.getStreamId();
|
||||
if ((streamId & 1) != 1)
|
||||
if (!isClientStream(streamId))
|
||||
{
|
||||
onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "invalid_stream_id");
|
||||
return;
|
||||
|
|
Loading…
Reference in New Issue