diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java index b0f5a8ec116..6ffa9e8df41 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java @@ -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); diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java index b6012e41b0f..a633f61bbba 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java @@ -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()); diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PrefaceParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PrefaceParser.java new file mode 100644 index 00000000000..9c029e34517 --- /dev/null +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PrefaceParser.java @@ -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); + } + } +} diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java new file mode 100644 index 00000000000..f20328b30c4 --- /dev/null +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java @@ -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 + } +} diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java index b9d947d1242..fd5e6bff90b 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java @@ -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); diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java index fe656685a49..2d2a581dc0f 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java @@ -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()); diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java index a0a3f3f735d..45939982ea7 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerSession.java @@ -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(), 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) { diff --git a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2ServerTest.java b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2ServerTest.java index 8ffd237eebd..2b7b1c47013 100644 --- a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2ServerTest.java +++ b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2ServerTest.java @@ -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 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));