I am creating a client to communicate with APNs.
here is my requirement.
- jdk 1.6
- http/2
- tls 1.3
- ALPN
so I decided to make it using Netty.
I don't know if I set the header and data well.
Http2Client.java
public class Http2Client {
// static final boolean SSL = System.getProperty("ssl") != null;
static final boolean SSL = true;
static final String HOST = "api.sandbox.push.apple.com";
static final int PORT = 443;
static final String PATH = "/3/device/00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0";
// private static final AsciiTest APNS_PATH = new AsciiTest("/3/device/00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0");
private static final AsciiTest APNS_EXPIRATION_HEADER = new AsciiTest("apns-expiration");
private static final AsciiTest APNS_TOPIC_HEADER = new AsciiTest("apns-topic");
private static final AsciiTest APNS_PRIORITY_HEADER = new AsciiTest("apns-priority");
private static final AsciiTest APNS_AUTHORIZATION = new AsciiTest("authorization");
private static final AsciiTest APNS_ID_HEADER = new AsciiTest("apns-id");
private static final AsciiTest APNS_PUSH_TYPE_HEADER = new AsciiTest("apns-push-type");
public static void main(String[] args) throws Exception {
EventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
// Configure SSL.
final SslContext sslCtx;
if (SSL) {
SslProvider provider = SslProvider.isAlpnSupported(SslProvider.OPENSSL) ? SslProvider.OPENSSL
: SslProvider.JDK;
sslCtx = SslContextBuilder.forClient()
.sslProvider(provider)
/*
* NOTE: the cipher filter may not include all ciphers required by the HTTP/2
* specification. Please refer to the HTTP/2 specification for cipher
* requirements.
*/
.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.applicationProtocolConfig(new ApplicationProtocolConfig(
Protocol.ALPN,
// NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK
// providers.
SelectorFailureBehavior.NO_ADVERTISE,
// ACCEPT is currently the only mode supported by both OpenSsl and JDK
// providers.
SelectedListenerFailureBehavior.ACCEPT,
ApplicationProtocolNames.HTTP_2,
ApplicationProtocolNames.HTTP_1_1))
.build();
} else {
sslCtx = null;
}
try {
// Configure the client.
Bootstrap b = new Bootstrap();
b.group(clientWorkerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.remoteAddress(HOST, PORT);
b.handler(new Http2ClientInit(sslCtx));
// Start the client.
final Channel channel = b.connect().syncUninterruptibly().channel();
System.out.println("Connected to [" + HOST + ':' + PORT + ']');
final Http2ResponseHandler streamFrameResponseHandler =
new Http2ResponseHandler();
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 = POST' in this case)
final Http2Headers headers = new DefaultHttp2Headers();
headers.method(HttpMethod.POST.asciiName());
headers.path(PATH);
headers.scheme(HttpScheme.HTTPS.name());
headers.add(APNS_TOPIC_HEADER, "com.example.MyApp");
headers.add(APNS_AUTHORIZATION,
"bearer eyAia2lkIjogIjhZTDNHM1JSWDciIH0.eyAiaXNzIjogIkM4Nk5WOUpYM0QiLCAiaWF0IjogIjE0NTkxNDM1ODA2NTAiIH0.MEYCIQDzqyahmH1rz1s-LFNkylXEa2lZ_aOCX4daxxTZkVEGzwIhALvkClnx5m5eAT6Lxw7LZtEQcH6JENhJTMArwLf3sXwi");
headers.add(APNS_ID_HEADER, "eabeae54-14a8-11e5-b60b-1697f925ec7b");
headers.add(APNS_PUSH_TYPE_HEADER, "alert");
headers.add(APNS_EXPIRATION_HEADER, "0");
headers.add(APNS_PRIORITY_HEADER, "10");
final Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(headers, true);
streamChannel.writeAndFlush(headersFrame);
System.out.println("Sent HTTP/2 POST 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();
}
}
}
Http2ResponseHandler.java
public final class Http2ResponseHandler extends SimpleChannelInboundHandler<Http2StreamFrame> {
private final CountDownLatch latch = new CountDownLatch(1);
public void channelActive(ChannelHandlerContext ctx) {
String sendMessage = "{\"aps\":{\"alert\":\"hello\"}}";
ByteBuf messageBuffer = Unpooled.buffer();
messageBuffer.writeBytes(sendMessage.getBytes());
StringBuilder builder = new StringBuilder();
builder.append("request [");
builder.append(sendMessage);
builder.append("]");
System.out.println(builder.toString());
ctx.writeAndFlush(new DefaultHttp2DataFrame(messageBuffer, true));
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame msg) throws Exception {
ByteBuf content = ctx.alloc().buffer();
System.out.println(content);
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()) {
ByteBuf data = ((DefaultHttp2DataFrame) msg).content().alloc().buffer();
System.out.println(data.readCharSequence(256, Charset.forName("utf-8")).toString());
latch.countDown();
} else if (msg instanceof Http2HeadersFrame && ((Http2HeadersFrame) msg).isEndStream()) {
latch.countDown();
}
// String readMessage = ((ByteBuf) msg).toString(CharsetUtil.UTF_8);
//
// StringBuilder builder = new StringBuilder();
// builder.append("receive [");
// builder.append(readMessage);
// builder.append("]");
//
// System.out.println(builder.toString());
}
public void channelReadComplete(ChannelHandlerContext ctx) {
// ctx.flush();
ctx.close();
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
/**
* 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;
}
}
}
console log
Connected to [api.sandbox.push.apple.com:443]
Sent HTTP/2 POST request to /3/device/00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0
PooledUnsafeDirectByteBuf(ridx: 0, widx: 0, cap: 256)
Received HTTP/2 'stream' frame : DefaultHttp2HeadersFrame(stream=3, headers=DefaultHttp2Headers[:status: 403, apns-id: eabeae54-14a8-11e5-b60b-1697f925ec7b], endStream=false, padding=0)
PooledUnsafeDirectByteBuf(ridx: 0, widx: 0, cap: 256)
Received HTTP/2 'stream' frame : DefaultHttp2DataFrame(stream=3, content=UnpooledSlicedByteBuf(ridx: 0, widx: 33, cap: 33/33, unwrapped: PooledUnsafeDirectByteBuf(ridx: 150, widx: 150, cap: 179)), endStream=true, padding=0)
Question
- Did I send the header and data well?
- How can i convert this part to String
DefaultHttp2DataFrame(stream=3, content=UnpooledSlicedByteBuf(ridx: 0, widx: 33, cap: 33/33, unwrapped: PooledUnsafeDirectByteBuf(ridx: 150, widx: 150, cap: 179)), endStream=true, padding=0)
If you know the solution, please help me.