Repository: netty/netty
Issue: SPDY receive "PROTOCOL_ERROR" error
Labels: needs info

Recently connecting to http://www.google.com with spdy is broken.

After two setting frame, I got a GoAwayFrame with "PROTOCOL_ERROR" status. It works just few days ago. Maybe google just update his protocol??

Version: both 3.8.3 and 4.0.13


Issue comments:
Google only supports SPDY over https with NPN. Which protocol version did you negotiate?

I have try both spdy3 & spdy3.1 with NPN extension. It works few days ago, but failed in recent days.

@jpinner anything we can do here ?

What did you send in your settings frame? Google's servers send a GO_AWAY if they receive any setting they do not recognize.

I didn't send setting frame manually, do you need me to capture the setting frame?

@jpinner could you chime in ?

If you could post a frame level / wire level dump of the session that would be helpful.

Please reopen with the information requested.


Pull request review comments:
Why not use Unpooled.copiedBuffer(...) ?

I think you can use SelectedProtocol.protocolForName(...) ?

`@Override` annotations ?

You should add it to the parent pom version with version number ...

Not from the current code.

```
public enum SelectedProtocol {
    SPDY_3,
    SPDY_3_1,
    HTTP_1_1,
    HTTP_1_0,
    UNKNOWN
}
```

Hmm what is the problem with using SelectedProtocol.protocol(String) 

From what I can see, `SelectedProtocol` is just a simple enum 

```
public enum SelectedProtocol {
    SPDY_3,
    SPDY_3_1,
    HTTP_1_1,
    HTTP_1_0,
    UNKNOWN
}
```

Are you suggesting we change the enum?

@veebs maybe you are not on latest 4.0 branch ?

https://github.com/netty/netty/blob/4.0/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyOrHttpChooser.java#L66

OK - makes sense now.  I forked and worked on the SPDY example before Jan 11th when that change was pulled in.  

Thanks @normanmaurer.  I'll merge and make the change.


Patch that resolved the issue:
diff --git a/example/pom.xml b/example/pom.xml
index eed48abf9bf..0dcff3cd82a 100644
--- a/example/pom.xml
+++ b/example/pom.xml
@@ -68,6 +68,10 @@
       <artifactId>javassist</artifactId>
       <scope>runtime</scope>
     </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.npn</groupId>
+      <artifactId>npn-api</artifactId>
+    </dependency>
 
     <!-- see https://github.com/netty/netty/issues/874 -->
     <dependency>
diff --git a/example/src/main/java/io/netty/example/spdy/SpdyOrHttpHandler.java b/example/src/main/java/io/netty/example/spdy/SpdyOrHttpHandler.java
new file mode 100644
index 00000000000..dc394747515
--- /dev/null
+++ b/example/src/main/java/io/netty/example/spdy/SpdyOrHttpHandler.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.example.spdy;
+
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.npn.NextProtoNego;
+
+import io.netty.channel.ChannelHandler;
+import io.netty.handler.codec.spdy.SpdyOrHttpChooser;
+
+/**
+ * Negotiates with the browser if SPDY or HTTP is going to be used. Once decided, the Netty pipeline is setup with
+ * the correct handlers for the selected protocol.
+ */
+public class SpdyOrHttpHandler extends SpdyOrHttpChooser {
+    private static final int MAX_CONTENT_LENGTH = 1024 * 100;
+
+    public SpdyOrHttpHandler() {
+        this(MAX_CONTENT_LENGTH, MAX_CONTENT_LENGTH);
+    }
+
+    public SpdyOrHttpHandler(int maxSpdyContentLength, int maxHttpContentLength) {
+        super(maxSpdyContentLength, maxHttpContentLength);
+    }
+
+    @Override
+    protected SelectedProtocol getProtocol(SSLEngine engine) {
+        SpdyServerProvider provider = (SpdyServerProvider) NextProtoNego.get(engine);
+        String selectedProtocol = provider.getSelectedProtocol();
+
+        System.out.println("Selected Protocol is " + (selectedProtocol == null ? "Unknown" : selectedProtocol));
+
+        if (selectedProtocol == null) {
+            return SpdyOrHttpChooser.SelectedProtocol.UNKNOWN;
+        } else if (selectedProtocol.equalsIgnoreCase("spdy/3.1")) {
+            return SpdyOrHttpChooser.SelectedProtocol.SPDY_3_1;
+        } else if (selectedProtocol.equalsIgnoreCase("spdy/3")) {
+            return SpdyOrHttpChooser.SelectedProtocol.SPDY_3;
+        } else if (selectedProtocol.equalsIgnoreCase("http/1.1")) {
+            return SpdyOrHttpChooser.SelectedProtocol.HTTP_1_1;
+        } else if (selectedProtocol.equalsIgnoreCase("http/1.0")) {
+            return SpdyOrHttpChooser.SelectedProtocol.HTTP_1_0;
+        } else {
+            return SpdyOrHttpChooser.SelectedProtocol.UNKNOWN;
+        }
+    }
+
+    @Override
+    protected ChannelHandler createHttpRequestHandlerForHttp() {
+        return new SpdyServerHandler();
+    }
+
+}
diff --git a/example/src/main/java/io/netty/example/spdy/SpdyServer.java b/example/src/main/java/io/netty/example/spdy/SpdyServer.java
new file mode 100644
index 00000000000..0de13d04818
--- /dev/null
+++ b/example/src/main/java/io/netty/example/spdy/SpdyServer.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.example.spdy;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+
+/**
+ * A SPDY Server that responds to a GET request with a Hello World.
+ * <p>
+ * This class must be run with the JVM parameter: {@code java -Xbootclasspath/p:<path_to_npn_boot_jar> ...}.
+ * The "path_to_npn_boot_jar" is the path on the file system for the NPN Boot Jar file which can be downloaded from
+ * Maven at coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions.
+ * See {@link http://www.eclipse.org/jetty/documentation/current/npn-chapter.html Jetty docs} for more information.
+ * <p>
+ * Once started, you can test the server with your
+ * {@link http://en.wikipedia.org/wiki/SPDY#Browser_support_and_usage SPDY enabled web browser} by navigating
+ * to https://localhost:8443/.
+ */
+public class SpdyServer {
+
+    private final int port;
+
+    public SpdyServer(int port) {
+        this.port = port;
+    }
+
+    public void run() throws Exception {
+        // Configure the server.
+        EventLoopGroup bossGroup = new NioEventLoopGroup();
+        EventLoopGroup workerGroup = new NioEventLoopGroup();
+        try {
+            ServerBootstrap b = new ServerBootstrap();
+            b.option(ChannelOption.SO_BACKLOG, 1024);
+            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
+                    .childHandler(new SpdyServerInitializer());
+
+            Channel ch = b.bind(port).sync().channel();
+            ch.closeFuture().sync();
+        } finally {
+            bossGroup.shutdownGracefully();
+            workerGroup.shutdownGracefully();
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        int port;
+        if (args.length > 0) {
+            port = Integer.parseInt(args[0]);
+        } else {
+            port = 8443;
+        }
+
+        System.out.println("SPDY web server started at port " + port + '.');
+        System.out.println("Open your SPDY enabled browser and navigate to https://localhost:" + port + '/');
+        System.out.println("If using Chrome browser, check your SPDY sessions at chrome://net-internals/#spdy");
+
+        new SpdyServer(port).run();
+    }
+}
diff --git a/example/src/main/java/io/netty/example/spdy/SpdyServerHandler.java b/example/src/main/java/io/netty/example/spdy/SpdyServerHandler.java
new file mode 100644
index 00000000000..889796fb317
--- /dev/null
+++ b/example/src/main/java/io/netty/example/spdy/SpdyServerHandler.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.example.spdy;
+
+import java.util.Date;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerAdapter;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.util.CharsetUtil;
+
+import static io.netty.handler.codec.http.HttpHeaders.Names.*;
+import static io.netty.handler.codec.http.HttpHeaders.*;
+import static io.netty.handler.codec.http.HttpResponseStatus.*;
+import static io.netty.handler.codec.http.HttpVersion.*;
+
+/**
+ * HTTP handler that responds with a "Hello World"
+ */
+public class SpdyServerHandler extends ChannelHandlerAdapter {
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) {
+        ctx.flush();
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        if (msg instanceof HttpRequest) {
+            HttpRequest req = (HttpRequest) msg;
+
+            if (is100ContinueExpected(req)) {
+                ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
+            }
+            boolean keepAlive = isKeepAlive(req);
+
+            ByteBuf content = Unpooled.copiedBuffer("Hello World " + (new Date()).toString(), CharsetUtil.UTF_8);
+
+            FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, content);
+            response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
+            response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
+
+            if (!keepAlive) {
+                ctx.write(response).addListener(ChannelFutureListener.CLOSE);
+            } else {
+                response.headers().set(CONNECTION, Values.KEEP_ALIVE);
+                ctx.write(response);
+            }
+        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        cause.printStackTrace();
+        ctx.close();
+    }
+}
diff --git a/example/src/main/java/io/netty/example/spdy/SpdyServerInitializer.java b/example/src/main/java/io/netty/example/spdy/SpdyServerInitializer.java
new file mode 100644
index 00000000000..f9412055a62
--- /dev/null
+++ b/example/src/main/java/io/netty/example/spdy/SpdyServerInitializer.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.example.spdy;
+
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.npn.NextProtoNego;
+
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.example.securechat.SecureChatSslContextFactory;
+import io.netty.handler.ssl.SslHandler;
+
+/**
+ * Sets up the Netty pipeline
+ */
+public class SpdyServerInitializer extends ChannelInitializer<SocketChannel> {
+    @Override
+    public void initChannel(SocketChannel ch) throws Exception {
+        ChannelPipeline p = ch.pipeline();
+
+        SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine();
+        engine.setUseClientMode(false);
+        p.addLast("ssl", new SslHandler(engine));
+
+        // Setup NextProtoNego with our server provider
+        NextProtoNego.put(engine, new SpdyServerProvider());
+        NextProtoNego.debug = true;
+
+        // Negotiates with the browser if SPDY or HTTP is going to be used
+        p.addLast("handler", new SpdyOrHttpHandler());
+    }
+}
diff --git a/example/src/main/java/io/netty/example/spdy/SpdyServerProvider.java b/example/src/main/java/io/netty/example/spdy/SpdyServerProvider.java
new file mode 100644
index 00000000000..250d7eedcc5
--- /dev/null
+++ b/example/src/main/java/io/netty/example/spdy/SpdyServerProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.example.spdy;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jetty.npn.NextProtoNego.ServerProvider;
+
+/**
+ * The Jetty project provides an implementation of the Transport Layer Security (TLS) extension for Next
+ * Protocol Negotiation (NPN) for OpenJDK 7 or greater. NPN allows the application layer to negotiate which
+ * protocol to use over the secure connection.
+ * <p>
+ * This NPN service provider negotiates using SPDY.
+ * <p>
+ * To enable NPN support, start the JVM with: {@code java -Xbootclasspath/p:<path_to_npn_boot_jar> ...}. The
+ * "path_to_npn_boot_jar" is the path on the file system for the NPN Boot Jar file which can be downloaded from
+ * Maven at coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions.
+ *
+ * @see http://www.eclipse.org/jetty/documentation/current/npn-chapter.html
+ */
+public class SpdyServerProvider implements ServerProvider {
+
+    private String selectedProtocol;
+
+    @Override
+    public void unsupported() {
+        // if unsupported, default to http/1.1
+        selectedProtocol = "http/1.1";
+    }
+
+    @Override
+    public List<String> protocols() {
+        return Arrays.asList("spdy/3.1", "spdy/3", "http/1.1");
+    }
+
+    @Override
+    public void protocolSelected(String protocol) {
+        selectedProtocol = protocol;
+    }
+
+    public String getSelectedProtocol() {
+        return selectedProtocol;
+    }
+}
diff --git a/example/src/main/java/io/netty/example/spdy/package-info.java b/example/src/main/java/io/netty/example/spdy/package-info.java
new file mode 100644
index 00000000000..5155ee2e859
--- /dev/null
+++ b/example/src/main/java/io/netty/example/spdy/package-info.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This package contains an example SPDY HTTP web server.
+ * <p>
+ * This package relies on the Jetty project's implementation of the Transport Layer Security (TLS) extension for Next
+ * Protocol Negotiation (NPN) for OpenJDK 7 is required. NPN allows the application layer to negotiate which
+ * protocol, SPDY or HTTP, to use.
+ * <p>
+ * To start, run {@link SpdyServer} with the JVM parameter: {@code java -Xbootclasspath/p:<path_to_npn_boot_jar> ...}.
+ * The "path_to_npn_boot_jar" is the path on the file system for the NPN Boot Jar file which can be downloaded from
+ * Maven at coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions.
+ * See {@link http://www.eclipse.org/jetty/documentation/current/npn-chapter.html Jetty docs} for more information.
+ * <p>
+ * Once started, you can test the server with your
+ * {@link http://en.wikipedia.org/wiki/SPDY#Browser_support_and_usage SPDY enabled web browser} by navigating
+ * to https://localhost:8443/.
+ */
+package io.netty.example.spdy;
diff --git a/pom.xml b/pom.xml
index 7803018efec..e64fc4d14ee 100644
--- a/pom.xml
+++ b/pom.xml
@@ -143,6 +143,13 @@
         <optional>true</optional>
       </dependency>
     
+      <!-- SPDY Example - completely optional -->
+      <dependency>
+        <groupId>org.eclipse.jetty.npn</groupId>
+        <artifactId>npn-api</artifactId>
+        <version>1.1.0.v20120525</version>
+      </dependency>
+        
       <dependency>
         <groupId>com.google.protobuf</groupId>
         <artifactId>protobuf-java</artifactId>


Test patch: