forked from netty/netty
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a HTTP/2 client example using the newer frames approach the curre…
…nt HTTP/2 client example uses the older 'HTTP/1 <--> HTTP/2' translation approach. (netty#10081) **Motivation:** When I was previously working on a project using Netty's HTTP/2 support, I used the newer frames approach but I struggled to find any good examples or documentation online. I did, however, see a few people ask the same (or similar) questions as me on StackOverflow and a couple of older Netty Github issues. Reading issue [9733](netty#9733) therefore prompted me to pull together a few bits of code into this HTTP/2 frame client example. **Modification:** Populated the previously-empty `example/src/main/java/io/netty/example/http2/helloworld/frame/client/` folder with a HTTP/2 frame client example. **Result:** Gives a clear example of how the newer HTTP/2 support can be used for Netty clients.
- Loading branch information
1 parent
b892722
commit 59c8ce3
Showing
4 changed files
with
248 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
...main/java/io/netty/example/http2/helloworld/frame/client/Http2ClientFrameInitializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/* | ||
* Copyright 2020 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.http2.helloworld.frame.client; | ||
|
||
import io.netty.channel.Channel; | ||
import io.netty.channel.ChannelHandlerContext; | ||
import io.netty.channel.ChannelInitializer; | ||
import io.netty.channel.SimpleChannelInboundHandler; | ||
import io.netty.handler.codec.http2.Http2FrameCodec; | ||
import io.netty.handler.codec.http2.Http2FrameCodecBuilder; | ||
import io.netty.handler.codec.http2.Http2MultiplexHandler; | ||
import io.netty.handler.codec.http2.Http2Settings; | ||
import io.netty.handler.ssl.SslContext; | ||
|
||
/** | ||
* Configures client pipeline to support HTTP/2 frames via {@link Http2FrameCodec} and {@link Http2MultiplexHandler}. | ||
*/ | ||
public final class Http2ClientFrameInitializer extends ChannelInitializer<Channel> { | ||
|
||
private final SslContext sslCtx; | ||
|
||
public Http2ClientFrameInitializer(SslContext sslCtx) { | ||
this.sslCtx = sslCtx; | ||
} | ||
|
||
@Override | ||
protected void initChannel(Channel ch) throws Exception { | ||
// ensure that our 'trust all' SSL handler is the first in the pipeline if SSL is enabled. | ||
if (sslCtx != null) { | ||
ch.pipeline().addFirst(sslCtx.newHandler(ch.alloc())); | ||
} | ||
|
||
final Http2FrameCodec http2FrameCodec = Http2FrameCodecBuilder.forClient() | ||
.initialSettings(Http2Settings.defaultSettings()) // this is the default, but shows it can be changed. | ||
.build(); | ||
ch.pipeline().addLast(http2FrameCodec); | ||
ch.pipeline().addLast(new Http2MultiplexHandler(new SimpleChannelInboundHandler() { | ||
@Override | ||
protected void channelRead0(ChannelHandlerContext ctx, Object msg) { | ||
// NOOP (this is the handler for 'inbound' streams, which is not relevant in this example) | ||
} | ||
})); | ||
} | ||
|
||
} |
61 changes: 61 additions & 0 deletions
61
...io/netty/example/http2/helloworld/frame/client/Http2ClientStreamFrameResponseHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/* | ||
* Copyright 2020 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.http2.helloworld.frame.client; | ||
|
||
import io.netty.channel.ChannelHandlerContext; | ||
import io.netty.channel.SimpleChannelInboundHandler; | ||
import io.netty.handler.codec.http2.Http2DataFrame; | ||
import io.netty.handler.codec.http2.Http2HeadersFrame; | ||
import io.netty.handler.codec.http2.Http2StreamFrame; | ||
|
||
import java.util.concurrent.CountDownLatch; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
/** | ||
* Handles HTTP/2 stream frame responses. This is a useful approach if you specifically want to check | ||
* the main HTTP/2 response DATA/HEADERs, but in this example it's used purely to see whether | ||
* our request (for a specific stream id) has had a final response (for that same stream id). | ||
*/ | ||
public final class Http2ClientStreamFrameResponseHandler extends SimpleChannelInboundHandler<Http2StreamFrame> { | ||
|
||
private final CountDownLatch latch = new CountDownLatch(1); | ||
|
||
@Override | ||
protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame msg) throws Exception { | ||
System.out.println("Received HTTP/2 'stream' frame: " + msg); | ||
|
||
// isEndStream() is not from a common interface, so we currently must check both | ||
if (msg instanceof Http2DataFrame && ((Http2DataFrame) msg).isEndStream()) { | ||
latch.countDown(); | ||
} else if (msg instanceof Http2HeadersFrame && ((Http2HeadersFrame) msg).isEndStream()) { | ||
latch.countDown(); | ||
} | ||
} | ||
|
||
/** | ||
* Waits for the latch to be decremented (i.e. for an end of stream message to be received), or for | ||
* the latch to expire after 5 seconds. | ||
* @return true if a successful HTTP/2 end of stream message was received. | ||
*/ | ||
public boolean responseSuccessfullyCompleted() { | ||
try { | ||
return latch.await(5, TimeUnit.SECONDS); | ||
} catch (InterruptedException ie) { | ||
System.err.println("Latch exception: " + ie.getMessage()); | ||
return false; | ||
} | ||
} | ||
|
||
} |
124 changes: 124 additions & 0 deletions
124
example/src/main/java/io/netty/example/http2/helloworld/frame/client/Http2FrameClient.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/* | ||
* Copyright 2020 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.http2.helloworld.frame.client; | ||
|
||
import io.netty.bootstrap.Bootstrap; | ||
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.NioSocketChannel; | ||
import io.netty.handler.codec.http2.DefaultHttp2Headers; | ||
import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; | ||
import io.netty.handler.codec.http2.Http2HeadersFrame; | ||
import io.netty.handler.codec.http2.Http2SecurityUtil; | ||
import io.netty.handler.codec.http2.Http2StreamChannel; | ||
import io.netty.handler.codec.http2.Http2StreamChannelBootstrap; | ||
import io.netty.handler.ssl.ApplicationProtocolConfig; | ||
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; | ||
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; | ||
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; | ||
import io.netty.handler.ssl.ApplicationProtocolNames; | ||
import io.netty.handler.ssl.SslContext; | ||
import io.netty.handler.ssl.SslContextBuilder; | ||
import io.netty.handler.ssl.SslProvider; | ||
import io.netty.handler.ssl.SupportedCipherSuiteFilter; | ||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory; | ||
|
||
/** | ||
* An HTTP2 client that allows you to send HTTP2 frames to a server using the newer HTTP2 | ||
* approach (via {@link io.netty.handler.codec.http2.Http2FrameCodec}). | ||
* When run from the command-line, sends a single HEADERS frame (with prior knowledge) to | ||
* the server configured at host:port/path. | ||
* You should include {@link io.netty.handler.codec.http2.Http2ClientUpgradeCodec} if the | ||
* HTTP/2 server you are hitting doesn't support h2c/prior knowledge. | ||
*/ | ||
public final class Http2FrameClient { | ||
|
||
static final boolean SSL = System.getProperty("ssl") != null; | ||
static final String HOST = System.getProperty("host", "127.0.0.1"); | ||
static final int PORT = Integer.parseInt(System.getProperty("port", SSL? "8443" : "8080")); | ||
static final String PATH = System.getProperty("path", "/"); | ||
|
||
private Http2FrameClient() { | ||
} | ||
|
||
public static void main(String[] args) throws Exception { | ||
final EventLoopGroup clientWorkerGroup = new NioEventLoopGroup(); | ||
|
||
// Configure SSL. | ||
final SslContext sslCtx; | ||
if (SSL) { | ||
final SslProvider provider = | ||
SslProvider.isAlpnSupported(SslProvider.OPENSSL)? SslProvider.OPENSSL : SslProvider.JDK; | ||
sslCtx = SslContextBuilder.forClient() | ||
.sslProvider(provider) | ||
.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE) | ||
// you probably won't want to use this in production, but it is fine for this example: | ||
.trustManager(InsecureTrustManagerFactory.INSTANCE) | ||
.applicationProtocolConfig(new ApplicationProtocolConfig( | ||
Protocol.ALPN, | ||
SelectorFailureBehavior.NO_ADVERTISE, | ||
SelectedListenerFailureBehavior.ACCEPT, | ||
ApplicationProtocolNames.HTTP_2, | ||
ApplicationProtocolNames.HTTP_1_1)) | ||
.build(); | ||
} else { | ||
sslCtx = null; | ||
} | ||
|
||
try { | ||
final Bootstrap b = new Bootstrap(); | ||
b.group(clientWorkerGroup); | ||
b.channel(NioSocketChannel.class); | ||
b.option(ChannelOption.SO_KEEPALIVE, true); | ||
b.remoteAddress(HOST, PORT); | ||
b.handler(new Http2ClientFrameInitializer(sslCtx)); | ||
|
||
// Start the client. | ||
final Channel channel = b.connect().syncUninterruptibly().channel(); | ||
System.out.println("Connected to [" + HOST + ':' + PORT + ']'); | ||
|
||
final Http2ClientStreamFrameResponseHandler streamFrameResponseHandler = | ||
new Http2ClientStreamFrameResponseHandler(); | ||
|
||
final Http2StreamChannelBootstrap streamChannelBootstrap = new Http2StreamChannelBootstrap(channel); | ||
final Http2StreamChannel streamChannel = streamChannelBootstrap.open().syncUninterruptibly().getNow(); | ||
streamChannel.pipeline().addLast(streamFrameResponseHandler); | ||
|
||
// Send request (a HTTP/2 HEADERS frame - with ':method = GET' in this case) | ||
final DefaultHttp2Headers headers = new DefaultHttp2Headers(); | ||
headers.method("GET"); | ||
headers.path(PATH); | ||
headers.scheme(SSL? "https" : "http"); | ||
final Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(headers); | ||
streamChannel.writeAndFlush(headersFrame); | ||
System.out.println("Sent HTTP/2 GET request to " + PATH); | ||
|
||
// Wait for the responses (or for the latch to expire), then clean up the connections | ||
if (!streamFrameResponseHandler.responseSuccessfullyCompleted()) { | ||
System.err.println("Did not get HTTP/2 response in expected time."); | ||
} | ||
|
||
System.out.println("Finished HTTP/2 request, will close the connection."); | ||
|
||
// Wait until the connection is closed. | ||
channel.close().syncUninterruptibly(); | ||
} finally { | ||
clientWorkerGroup.shutdownGracefully(); | ||
} | ||
} | ||
|
||
} |