Implemented HTTP2 connection preface.

This commit is contained in:
Simone Bordet 2014-06-10 18:08:57 +02:00
parent 7aeddff675
commit 630bee5887
8 changed files with 256 additions and 18 deletions

View File

@ -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);

View File

@ -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());

View File

@ -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);
}
}
}

View File

@ -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
}
}

View File

@ -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);

View File

@ -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());

View File

@ -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)
{

View File

@ -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));