Implemented HTTP2 connection preface.
This commit is contained in:
parent
7aeddff675
commit
630bee5887
|
@ -149,6 +149,11 @@ public abstract class HTTP2Session implements ISession, Parser.Listener
|
|||
flusher.flush(lease);
|
||||
}
|
||||
|
||||
protected void disconnect()
|
||||
{
|
||||
endPoint.close();
|
||||
}
|
||||
|
||||
protected IStream putIfAbsent(IStream stream)
|
||||
{
|
||||
return streams.putIfAbsent(stream.getId(), stream);
|
||||
|
|
|
@ -38,14 +38,16 @@ public class Parser
|
|||
{
|
||||
private static final Logger LOG = Log.getLogger(Parser.class);
|
||||
|
||||
private final HeaderParser headerParser = new HeaderParser();
|
||||
private final BodyParser[] bodyParsers = new BodyParser[FrameType.values().length];
|
||||
private final Listener listener;
|
||||
private final HeaderParser headerParser;
|
||||
private final BodyParser[] bodyParsers;
|
||||
private State state = State.HEADER;
|
||||
|
||||
public Parser(ByteBufferPool byteBufferPool, Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
this.headerParser = new HeaderParser();
|
||||
this.bodyParsers = new BodyParser[FrameType.values().length];
|
||||
|
||||
HeaderBlockParser headerBlockParser = new HeaderBlockParser(byteBufferPool, new HpackDecoder());
|
||||
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2014 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.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class PrefaceParser
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(PrefaceParser.class);
|
||||
public static final byte[] PREFACE_BYTES = new byte[]
|
||||
{
|
||||
0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
|
||||
0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
|
||||
0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a
|
||||
};
|
||||
private final Parser.Listener listener;
|
||||
|
||||
private int cursor;
|
||||
|
||||
public PrefaceParser(Parser.Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
int currByte = buffer.get();
|
||||
if (currByte != PREFACE_BYTES[cursor])
|
||||
{
|
||||
notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_preface");
|
||||
return false;
|
||||
}
|
||||
++cursor;
|
||||
if (cursor == PREFACE_BYTES.length)
|
||||
{
|
||||
cursor = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void notifyConnectionFailure(int error, String reason)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onConnectionFailure(error, reason);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.info("Failure while notifying listener " + listener, x);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2014 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.parser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class ServerParser extends Parser
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ServerParser.class);
|
||||
|
||||
private final Listener listener;
|
||||
private final PrefaceParser prefaceParser;
|
||||
private State state = State.PREFACE;
|
||||
|
||||
public ServerParser(ByteBufferPool byteBufferPool, Listener listener)
|
||||
{
|
||||
super(byteBufferPool, listener);
|
||||
this.listener = listener;
|
||||
this.prefaceParser = new PrefaceParser(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
LOG.debug("Parsing {}", buffer);
|
||||
|
||||
while (true)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case PREFACE:
|
||||
{
|
||||
if (!prefaceParser.parse(buffer))
|
||||
return false;
|
||||
if (onPreface())
|
||||
return true;
|
||||
state = State.FRAMES;
|
||||
break;
|
||||
}
|
||||
case FRAMES:
|
||||
{
|
||||
// Stay forever in the FRAMES state.
|
||||
return super.parse(buffer);
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean onPreface()
|
||||
{
|
||||
return notifyPreface();
|
||||
}
|
||||
|
||||
private boolean notifyPreface()
|
||||
{
|
||||
try
|
||||
{
|
||||
return listener.onPreface();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.info("Failure while notifying listener " + listener, x);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Listener extends Parser.Listener
|
||||
{
|
||||
public boolean onPreface();
|
||||
|
||||
public static class Adapter extends Parser.Listener.Adapter implements Listener
|
||||
{
|
||||
@Override
|
||||
public boolean onPreface()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
PREFACE, FRAMES
|
||||
}
|
||||
}
|
|
@ -95,7 +95,7 @@ public class HpackEncoder
|
|||
public void encode(MetaData metadata,Lease lease)
|
||||
{
|
||||
ByteBuffer buffer = lease.acquire(8*1024,false); // TODO make size configurable
|
||||
|
||||
lease.append(buffer,true);
|
||||
// TODO handle multiple buffers if large size configured.
|
||||
BufferUtil.clearToFill(buffer);
|
||||
encode(buffer,metadata);
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
package org.eclipse.jetty.http2.server;
|
||||
|
||||
import org.eclipse.jetty.http2.HTTP2Connection;
|
||||
import org.eclipse.jetty.http2.HTTP2Session;
|
||||
import org.eclipse.jetty.http2.IStream;
|
||||
import org.eclipse.jetty.http2.api.Session;
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
|
@ -28,6 +27,7 @@ import org.eclipse.jetty.http2.frames.DataFrame;
|
|||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.http2.generator.Generator;
|
||||
import org.eclipse.jetty.http2.parser.Parser;
|
||||
import org.eclipse.jetty.http2.parser.ServerParser;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.server.AbstractConnectionFactory;
|
||||
|
@ -56,9 +56,9 @@ public class HTTP2ServerConnectionFactory extends AbstractConnectionFactory
|
|||
Session.Listener listener = new HTTPServerSessionListener(connector, httpConfiguration, endPoint);
|
||||
|
||||
Generator generator = new Generator(connector.getByteBufferPool());
|
||||
HTTP2Session session = new HTTP2ServerSession(endPoint, generator, listener);
|
||||
HTTP2ServerSession session = new HTTP2ServerSession(endPoint, generator, listener);
|
||||
|
||||
Parser parser = new Parser(connector.getByteBufferPool(), session);
|
||||
Parser parser = new ServerParser(connector.getByteBufferPool(), session);
|
||||
HTTP2Connection connection = new HTTP2Connection(connector.getByteBufferPool(), connector.getExecutor(),
|
||||
endPoint, parser, getInputBufferSize());
|
||||
|
||||
|
|
|
@ -18,21 +18,41 @@
|
|||
|
||||
package org.eclipse.jetty.http2.server;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.eclipse.jetty.http2.HTTP2Session;
|
||||
import org.eclipse.jetty.http2.HTTP2Stream;
|
||||
import org.eclipse.jetty.http2.IStream;
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||
import org.eclipse.jetty.http2.generator.Generator;
|
||||
import org.eclipse.jetty.http2.parser.ServerParser;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
||||
public class HTTP2ServerSession extends HTTP2Session
|
||||
public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Listener
|
||||
{
|
||||
public HTTP2ServerSession(EndPoint endPoint, Generator generator, Listener listener)
|
||||
{
|
||||
super(endPoint, generator, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreface()
|
||||
{
|
||||
frame(new SettingsFrame(new HashMap<Integer, Integer>(), false), new Callback.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
// If cannot write the SETTINGS frame, hard disconnect.
|
||||
disconnect();
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onHeaders(HeadersFrame frame)
|
||||
{
|
||||
|
|
|
@ -18,14 +18,16 @@
|
|||
|
||||
package org.eclipse.jetty.http2.server;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -35,12 +37,14 @@ import org.eclipse.jetty.http.HttpFields;
|
|||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||
import org.eclipse.jetty.http2.generator.Generator;
|
||||
import org.eclipse.jetty.http2.hpack.HpackContext;
|
||||
import org.eclipse.jetty.http2.hpack.HpackDecoder;
|
||||
import org.eclipse.jetty.http2.hpack.HpackEncoder;
|
||||
import org.eclipse.jetty.http2.hpack.MetaData;
|
||||
import org.eclipse.jetty.http2.parser.Parser;
|
||||
import org.eclipse.jetty.http2.parser.PrefaceParser;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
|
@ -48,6 +52,7 @@ import org.eclipse.jetty.server.Server;
|
|||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
|
@ -89,7 +94,7 @@ public class HTTP2ServerTest
|
|||
@Test
|
||||
public void testRequestResponseNoContent() throws Exception
|
||||
{
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final CountDownLatch latch = new CountDownLatch(3);
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
|
@ -106,30 +111,52 @@ public class HTTP2ServerTest
|
|||
host + ":" + port, host, port, path, fields);
|
||||
HeadersFrame request = new HeadersFrame(1, metaData, null, true);
|
||||
Generator.LeaseCallback lease = generator.generate(request, Callback.Adapter.INSTANCE);
|
||||
lease.prepend(ByteBuffer.wrap(PrefaceParser.PREFACE_BYTES), false);
|
||||
|
||||
try (SocketChannel client = SocketChannel.open(new InetSocketAddress(host, port)))
|
||||
try (Socket client = new Socket(host, port))
|
||||
{
|
||||
OutputStream output = client.getOutputStream();
|
||||
for (ByteBuffer buffer : lease.getByteBuffers())
|
||||
{
|
||||
client.write(buffer);
|
||||
output.write(BufferUtil.toArray(buffer));
|
||||
}
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(2048);
|
||||
client.read(buffer);
|
||||
buffer.flip();
|
||||
|
||||
final AtomicReference<HeadersFrame> frameRef = new AtomicReference<>();
|
||||
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public boolean onSettings(SettingsFrame frame)
|
||||
{
|
||||
latch.countDown();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onHeaders(HeadersFrame frame)
|
||||
{
|
||||
frameRef.set(frame);
|
||||
latch.countDown();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
parser.parse(buffer);
|
||||
byte[] buffer = new byte[2048];
|
||||
InputStream input = client.getInputStream();
|
||||
client.setSoTimeout(1000);
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
int read = input.read(buffer);
|
||||
if (read <= 0)
|
||||
throw new EOFException();
|
||||
parser.parse(ByteBuffer.wrap(buffer, 0, read));
|
||||
}
|
||||
catch (SocketTimeoutException x)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
|
|
Loading…
Reference in New Issue