I have been searching how to implement Conscrypt SSL provider using conscrypt-openjdk-uber-1.4.1.jar for jdk8 to support ALPN for making a http2(using apache httpclient 5) connection to a server as jdk8 does not support ALPN by default or the other solution is to migrate to jdk9(or higher) which is not feasible for now as our product is heavily dependent on jdk8
I have been searching extensively for some docs or examples to implement but I could not find one.
I have tried to insert conscrypt provider as default and my program takes it as default provider but still it fails to connect with http2 server, my example is as follows,
public static void main(final String[] args) throws Exception {
Security.insertProviderAt(new OpenSSLProvider(), 1);
final SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(new TrustAllStrategy()).build();
final PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create().setTlsStrategy(new H2TlsStrategy(sslContext, NoopHostnameVerifier.INSTANCE)).build();
final IOReactorConfig ioReactorConfig = IOReactorConfig.custom().setSoTimeout(Timeout.ofSeconds(5)).build();
final MinimalHttpAsyncClient client = HttpAsyncClients.createMinimal(HttpVersionPolicy.FORCE_HTTP_2, H2Config.DEFAULT, null, ioReactorConfig, connectionManager);
client.start();
final HttpHost target = new HttpHost("localhost", 8082, "https");
final Future<AsyncClientEndpoint> leaseFuture = client.lease(target, null);
final AsyncClientEndpoint endpoint = leaseFuture.get(10, TimeUnit.SECONDS);
try {
String[] requestUris = new String[] {"/"};
CountDownLatch latch = new CountDownLatch(requestUris.length);
for (final String requestUri: requestUris) {
SimpleHttpRequest request = SimpleHttpRequest.get(target, requestUri);
endpoint.execute(SimpleRequestProducer.create(request), SimpleResponseConsumer.create(), new FutureCallback<SimpleHttpResponse>() {
#Override
public void completed(final SimpleHttpResponse response) {
latch.countDown();
System.out.println(requestUri + "->" + response.getCode());
System.out.println(response.getBody());
}
#Override
public void failed(final Exception ex) {
latch.countDown();
System.out.println(requestUri + "->" + ex);
ex.printStackTrace();
}
#Override
public void cancelled() {
latch.countDown();
System.out.println(requestUri + " cancelled");
}
});
}
latch.await();
} catch (Exception e) {
e.printStackTrace();
}finally {
endpoint.releaseAndReuse();
}
client.shutdown(ShutdownType.GRACEFUL);
}
this programs gives the output as
org.apache.hc.core5.http.ConnectionClosedException: Connection closed
org.apache.hc.core5.http.ConnectionClosedException: Connection closed
at org.apache.hc.core5.http2.impl.nio.FrameInputBuffer.read(FrameInputBuffer.java:146)
at org.apache.hc.core5.http2.impl.nio.AbstractHttp2StreamMultiplexer.onInput(AbstractHttp2StreamMultiplexer.java:415)
at org.apache.hc.core5.http2.impl.nio.AbstractHttp2IOEventHandler.inputReady(AbstractHttp2IOEventHandler.java:63)
at org.apache.hc.core5.http2.impl.nio.ClientHttp2IOEventHandler.inputReady(ClientHttp2IOEventHandler.java:38)
at org.apache.hc.core5.reactor.InternalDataChannel.onIOEvent(InternalDataChannel.java:117)
at org.apache.hc.core5.reactor.InternalChannel.handleIOEvent(InternalChannel.java:50)
at org.apache.hc.core5.reactor.SingleCoreIOReactor.processEvents(SingleCoreIOReactor.java:173)
at org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:123)
at org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:80)
at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44)
at java.lang.Thread.run(Thread.java:748)
If I print the provider and version it prints as Conscrypt version 1.0 and JDK 1.8.0_162, but still it fails to connect with a http2 endpoint
same chunk of code works perfectly if I connect using jdk9 with default provider, what I m missing here in conscrypt configuration?
Any help is appreciated
Thanks in advance
Just replacing the default JSSE provider with Conscrypt is not enough. One also needs a custom TlsStrategy that can take advantage of Conscrypt APIs.
This what works for me with Java 1.8 and Conscrypt 1.4.1
static class ConscriptClientTlsStrategy implements TlsStrategy {
private final SSLContext sslContext;
public ConscriptClientTlsStrategy(final SSLContext sslContext) {
this.sslContext = Args.notNull(sslContext, "SSL context");
}
#Override
public boolean upgrade(
final TransportSecurityLayer tlsSession,
final HttpHost host,
final SocketAddress localAddress,
final SocketAddress remoteAddress,
final Object attachment) {
final String scheme = host != null ? host.getSchemeName() : null;
if (URIScheme.HTTPS.same(scheme)) {
tlsSession.startTls(
sslContext,
host,
SSLBufferMode.STATIC,
(endpoint, sslEngine) -> {
final SSLParameters sslParameters = sslEngine.getSSLParameters();
sslParameters.setProtocols(H2TlsSupport.excludeBlacklistedProtocols(sslParameters.getProtocols()));
sslParameters.setCipherSuites(H2TlsSupport.excludeBlacklistedCiphers(sslParameters.getCipherSuites()));
H2TlsSupport.setEnableRetransmissions(sslParameters, false);
final HttpVersionPolicy versionPolicy = attachment instanceof HttpVersionPolicy ?
(HttpVersionPolicy) attachment : HttpVersionPolicy.NEGOTIATE;
final String[] appProtocols;
switch (versionPolicy) {
case FORCE_HTTP_1:
appProtocols = new String[] { ApplicationProtocols.HTTP_1_1.id };
break;
case FORCE_HTTP_2:
appProtocols = new String[] { ApplicationProtocols.HTTP_2.id };
break;
default:
appProtocols = new String[] { ApplicationProtocols.HTTP_2.id, ApplicationProtocols.HTTP_1_1.id };
}
if (Conscrypt.isConscrypt(sslEngine)) {
sslEngine.setSSLParameters(sslParameters);
Conscrypt.setApplicationProtocols(sslEngine, appProtocols);
} else {
H2TlsSupport.setApplicationProtocols(sslParameters, appProtocols);
sslEngine.setSSLParameters(sslParameters);
}
},
(endpoint, sslEngine) -> {
if (Conscrypt.isConscrypt(sslEngine)) {
return new TlsDetails(sslEngine.getSession(), Conscrypt.getApplicationProtocol(sslEngine));
}
return null;
});
return true;
}
return false;
}
}
public static void main(String[] args) throws Exception {
final SSLContext sslContext = SSLContexts.custom()
.setProvider(Conscrypt.newProvider())
.build();
final PoolingAsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create()
.setTlsStrategy(new ConscriptClientTlsStrategy(sslContext))
.build();
try (CloseableHttpAsyncClient client = HttpAsyncClients.custom()
.setVersionPolicy(HttpVersionPolicy.NEGOTIATE)
.setConnectionManager(cm)
.build()) {
client.start();
final HttpHost target = new HttpHost("nghttp2.org", 443, "https");
final String requestUri = "/httpbin";
final HttpClientContext clientContext = HttpClientContext.create();
final SimpleHttpRequest request = SimpleHttpRequests.GET.create(target, requestUri);
final Future<SimpleHttpResponse> future = client.execute(
SimpleRequestProducer.create(request),
SimpleResponseConsumer.create(),
clientContext,
new FutureCallback<SimpleHttpResponse>() {
#Override
public void completed(final SimpleHttpResponse response) {
System.out.println(requestUri + "->" + response.getCode() + " " +
clientContext.getProtocolVersion());
System.out.println(response.getBody());
final SSLSession sslSession = clientContext.getSSLSession();
if (sslSession != null) {
System.out.println("SSL protocol " + sslSession.getProtocol());
System.out.println("SSL cipher suite " + sslSession.getCipherSuite());
}
}
#Override
public void failed(final Exception ex) {
System.out.println(requestUri + "->" + ex);
}
#Override
public void cancelled() {
System.out.println(requestUri + " cancelled");
}
});
future.get();
System.out.println("Shutting down");
client.shutdown(CloseMode.GRACEFUL);
}
}
Related
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.
Answer myself.
Http2ResponseHandler.java
public final class ResponseHandler extends SimpleChannelInboundHandler<Http2StreamFrame> {
private final CountDownLatch latch = new CountDownLatch(1);
#Override
protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame msg) throws Exception {
if (msg instanceof Http2DataFrame && ((Http2DataFrame) msg).isEndStream()) {
DefaultHttp2DataFrame dataFrame = (DefaultHttp2DataFrame) msg;
ByteBuf dataContent = dataFrame.content();
String data = dataContent.toString(Charset.forName("utf-8"));
System.out.println(data);
latch.countDown();
} else if (msg instanceof Http2HeadersFrame && ((Http2HeadersFrame) msg).isEndStream()) {
DefaultHttp2HeadersFrame headerFrame = (DefaultHttp2HeadersFrame) msg;
DefaultHttp2Headers header = (DefaultHttp2Headers) headerFrame.headers();
System.out.println(header.get("apns-id"));
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;
}
}
}
Question
Did I send the header and data well?
-> Answer
final Http2Headers headers = new DefaultHttp2Headers();
headers.method(HttpMethod.POST.asciiName());
headers.scheme(HttpScheme.HTTPS.name());
headers.path(PATH + notification.getToken());
headers.add("apns-topic", topic);
headers.add("key", "value");
// if you have a data frame you have to put false.
final Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(headers, false);
How can i convert this part to String
-> Answer
DefaultHttp2DataFrame dataFrame = (DefaultHttp2DataFrame) msg;
ByteBuf dataContent = dataFrame.content();
String data = dataContent.toString(Charset.forName("utf-8"));
I hope it will be helpful to people who have the same curiosity as me.
I am writing a client using HttpClient 5.0 beta to request/load a secure URL/resource in Tomcat which supports HTTP2.
The program is as below. It is taken from Apache httpclient 5 examples (the code is exactly same except the way the SSL context is created with loadTrustMaterial)
public class Http2TlsAlpnRequestExecutionExample {
public final static void main(final String[] args) throws Exception {
String trustStorePath = "C:\\cert\\keystore.jks";
String trustStorePassword = "password";
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(new File(trustStorePath), trustStorePassword.toCharArray()).build();
// Create and start requester
H2Config h2Config = H2Config.custom()
.setPushEnabled(false)
.build();
final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap().setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2)
.setH2Config(h2Config)
.setTlsStrategy(new H2ClientTlsStrategy(sslContext, new SSLSessionVerifier() {
public TlsDetails verify(final NamedEndpoint endpoint, final SSLEngine sslEngine) throws SSLException {
return null;
}
}))
.setStreamListener(new Http2StreamListener() {
public void onHeaderInput(final HttpConnection connection, final int streamId, final List<? extends Header> headers) {
for (int i = 0; i < headers.size(); i++) {
System.out.println(connection + " (" + streamId + ") << " + headers.get(i));
}
}
public void onHeaderOutput(final HttpConnection connection, final int streamId, final List<? extends Header> headers) {
for (int i = 0; i < headers.size(); i++) {
System.out.println(connection + " (" + streamId + ") >> " + headers.get(i));
}
}
public void onFrameInput(final HttpConnection connection, final int streamId, final RawFrame frame) {
}
public void onFrameOutput(final HttpConnection connection, final int streamId, final RawFrame frame) {
}
public void onInputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) {
}
public void onOutputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) {
}
})
.create();
Runtime.getRuntime().addShutdownHook(new Thread() {
#Override
public void run() {
System.out.println("HTTP requester shutting down");
requester.shutdown(ShutdownType.GRACEFUL);
}
});
requester.start();
HttpHost target = new HttpHost("localhost", 1090, "https");
String[] requestUris = new String[] {"/rest/rest/helloWorld"};
final CountDownLatch latch = new CountDownLatch(requestUris.length);
for (final String requestUri: requestUris) {
final Future<AsyncClientEndpoint> future = requester.connect(target, Timeout.ofSeconds(5));
final AsyncClientEndpoint clientEndpoint = future.get();
clientEndpoint.execute(
new BasicRequestProducer("GET", target, requestUri),
new BasicResponseConsumer<String>(new StringAsyncEntityConsumer()),
new FutureCallback<Message<HttpResponse, String>>() {
public void completed(final Message<HttpResponse, String> message) {
clientEndpoint.releaseAndReuse();
HttpResponse response = message.getHead();
String body = message.getBody();
System.out.println(requestUri + "->" + response.getCode() + " " + response.getVersion());
System.out.println(body);
latch.countDown();
}
public void failed(final Exception ex) {
clientEndpoint.releaseAndDiscard();
System.out.println(requestUri + "->" + ex);
ex.printStackTrace();
latch.countDown();
}
public void cancelled() {
clientEndpoint.releaseAndDiscard();
System.out.println(requestUri + " cancelled");
latch.countDown();
}
});
}
latch.await();
System.out.println("Shutting down I/O reactor");
requester.initiateShutdown();
}
}
I get following exception when I run the code.
java.io.IOException: An existing connection was forcibly closed by the remote host
at sun.nio.ch.SocketDispatcher.read0(Native Method)
at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:43)
at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
at sun.nio.ch.IOUtil.read(IOUtil.java:197)
at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:380)
at org.apache.hc.core5.reactor.ssl.SSLIOSession.receiveEncryptedData(SSLIOSession.java:443)
at org.apache.hc.core5.reactor.ssl.SSLIOSession.isAppInputReady(SSLIOSession.java:498)
at org.apache.hc.core5.reactor.InternalDataChannel.onIOEvent(InternalDataChannel.java:112)
at org.apache.hc.core5.reactor.InternalChannel.handleIOEvent(InternalChannel.java:50)
at org.apache.hc.core5.reactor.SingleCoreIOReactor.processEvents(SingleCoreIOReactor.java:173)
at org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:123)
at org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:80)
at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44)
at java.lang.Thread.run(Thread.java:745)
If I do not set set version policy as HttpVersionPolicy.FORCE_HTTP_2 on H2RequesterBootstrap, it works but in that case it uses HTTP/1.1. But I have to use HTTP2 only.
Just to check, I tried it with Jetty's HTTP2 client and it worked. But I have to use Apache HttpClient 5.0 only.
Could you help, please? Thanks.
I think the server you're trying to connect is not http2 enabled, you need to configure your webapp to accept http2 requests. If you're using apache tomcat, you need tomcat 9.x with JDK9 or above to support http2. Check here to configure http2 for apache tomcat. You also need to install openssl and apr libraries which supports http2.
Follow this link for installing apr and openssl libraries
I'm trying to set up a connection to Google Cloud Messaging via Cloud Connection Server (XMPP-Connection) using Smack 4.1.2.
I was already able to establish connection and receive incoming messages. But my problem is the StanzaListener isn't triggered by each message (but only every second one). The Smack Debugging Console shows all "Raw Received Packets", so the sending from Device-to-Cloud works for each message.
Thank you for your help!
Here's my code from the Server App:
My Class GCMServer with Main:
public class GCMServer {
public static final Logger logger = Logger.getLogger(GCMServer.class.getName());
public static SSLContext sslCtx;
public static XMPPTCPConnection connection;
private static final String GCM_SERVER = "gcm.googleapis.com";
private static final int GCM_PORT = 5235;
private static final String GCM_ELEMENT_NAME = "gcm";
private static final String GCM_NAMESPACE = "google:mobile:data";
private static final String YOUR_PROJECT_ID = "xxxxxxxxxxxx";
private static final String YOUR_API_KEY = "xxxx";
public static void main(String[] args) {
ConnectionListener cl;
try {
KeyStore windowsRootTruststore = KeyStore.getInstance("Windows-ROOT", "SunMSCAPI");
windowsRootTruststore.load(null, null);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(windowsRootTruststore);
sslCtx = SSLContext.getInstance("TLS");
sslCtx.init(null, tmf.getTrustManagers(), null);
} catch (KeyStoreException | NoSuchProviderException | IOException | NoSuchAlgorithmException | CertificateException | KeyManagementException ex) {
Logger.getLogger(GCMServer.class.getName()).log(Level.SEVERE, null, ex);
}
XMPPTCPConnectionConfiguration conf = XMPPTCPConnectionConfiguration.builder()
.setSecurityMode(SecurityMode.ifpossible)
.setUsernameAndPassword(YOUR_PROJECT_ID, YOUR_API_KEY)
.setHost(GCM_SERVER)
.setServiceName(GCM_SERVER)
.setPort(5235)
.setDebuggerEnabled(true)
.setCompressionEnabled(false)
.setSocketFactory(sslCtx.getSocketFactory())
.build();
cl = new ConnectionListener() {
#Override
public void connected(XMPPConnection xmppc) {
Logger.getLogger(GCMServer.class.getName()).log(Level.INFO, "connected");
System.out.println("Conncetion is secure: "+connection.isSecureConnection());
}
#Override
public void authenticated(XMPPConnection xmppc, boolean bln) {
Logger.getLogger(GCMServer.class.getName()).log(Level.INFO, "authenticated");
}
#Override
public void connectionClosed() {
Logger.getLogger(GCMServer.class.getName()).log(Level.INFO, "connection closed");
}
#Override
public void connectionClosedOnError(Exception excptn) {
Logger.getLogger(GCMServer.class.getName()).log(Level.INFO, "conncetion closed on error");
}
#Override
public void reconnectionSuccessful() {
Logger.getLogger(GCMServer.class.getName()).log(Level.INFO, "reconnection successful");
}
#Override
public void reconnectingIn(int i) {
Logger.getLogger(GCMServer.class.getName()).log(Level.INFO, "reconnecting..");
}
#Override
public void reconnectionFailed(Exception excptn) {
Logger.getLogger(GCMServer.class.getName()).log(Level.INFO, "reconnection failed");
}
};
connection = new XMPPTCPConnection(conf);
//disable Roster; it seems it's not supported by GCM
Roster roster = Roster.getInstanceFor(connection);
roster.setRosterLoadedAtLogin(false);
try {
connection.connect();
connection.addAsyncStanzaListener(new MyStanzaListener(),new StanzaTypeFilter(Message.class));
connection.addConnectionListener(cl);
connection.login(YOUR_PROJECT_ID + "#gcm.googleapis.com", YOUR_API_KEY);
} catch (SmackException ex) {
Logger.getLogger(GCMServer.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(GCMServer.class.getName()).log(Level.SEVERE, null, ex);
} catch (XMPPException ex) {
Logger.getLogger(GCMServer.class.getName()).log(Level.SEVERE, null, ex);
}
}
Class MyStanzaListener:
private static class MyStanzaListener implements StanzaListener{
#Override
public void processPacket(Stanza stanza) {
System.out.println("hei ho, new message: " + stanza.toXML());
Message incomingMessage = (Message) stanza;
GcmPacketExtension gcmPacket = (GcmPacketExtension) incomingMessage.getExtension(GCM_NAMESPACE);
String json = gcmPacket.getJson();
try {
Map<String, Object> jsonObject = (Map<String, Object>) JSONValue.parseWithException(json);
Object messageType = jsonObject.get("message_type");
String from = (String)jsonObject.get("from");
String messageId = (String)jsonObject.get("message_id");
String category = (String)jsonObject.get("category");
if(messageType == null) {
String ack = createJsonAck(from, messageId);
System.out.println(ack);
send(ack);
handleMessage(jsonObject);
}
else{
Logger.getLogger(GCMServer.class.getName()).log(Level.SEVERE, "ERROR IN HANDLING MESSAGE");
}
} catch (ParseException ex) {
Logger.getLogger(GCMServer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
Methods HandleMessage, CreateJSONAck, Send:
public static void handleMessage(Map<String, Object> jsonObject) {
DBConnect db = new DBConnect();
Map<String, Object> messageData = (Map<String, Object>) jsonObject.get("data");
String phoneNumber = (String)messageData.get("phoneNumber");
String text = (String)messageData.get("message");
db.inBoundMessage(phoneNumber, text);
}
public static String createJsonAck(String to, String messageId) {
Map<String, Object> message = new LinkedHashMap<String, Object>();
message.put("to", to);
message.put("message_id", messageId);
message.put("message_type", "ack");
return JSONValue.toJSONString(message);
}
/**
* Sends a downstream GCM message.
*/
public static void send(String jsonRequest) {
Stanza request = (Stanza)new GcmPacketExtension(jsonRequest).toPacket();
try {
connection.sendStanza(request);
} catch (NotConnectedException ex) {
Logger.getLogger(GCMServer.class.getName()).log(Level.SEVERE, "ERROR WHILE SENDING: ", ex);
}
}
This is a bug (SMACK-695) in Smack 4.1.3, which is fixed/will be fixed in 4.1.4. For more information see https://community.igniterealtime.org/thread/56610.
Camel http component does not close connections properly?
Having below route I have observed that connections are being created on the server, but not terminated.
After a while this is causing a problem
java.io.IOException: Too many open files
route:
from("seda:testSeda?concurrentConsumers=20")
.setHeader("Connection", constant("Close"))
.to("http://testServer/testFile.xml?authMethod=Basic&throwExceptionOnFailure=false&authUsername=user&authPassword=password")
.to("file://abc")
.end();
connections are in Close_Wait state any ideas?
I am using camel-http lib in version 2.14
You can override default HttpClient used by Apache Camel and define a custom Keep Alive Strategy.
https://howtodoinjava.com/spring-boot2/resttemplate/resttemplate-httpclient-java-config/
The code bellow resolved my issue in production:
#Configuration
public class AppConfiguration {
#Autowired
private PoolingHttpClientConnectionManager poolingConnectionManager;
#Autowired
private ConnectionKeepAliveStrategy connectionKeepAliveStrategy;
#Autowired
private SSLConnectionSocketFactory sslContext;
#Bean
CamelContextConfiguration contextConfiguration() {
return new CamelContextConfiguration() {
#Override
public void beforeApplicationStart(CamelContext context) {
HttpComponent httpComponent = context.getComponent("https4", HttpComponent.class);
httpComponent.setHttpClientConfigurer(new HttpClientConfigurer() {
#Override
public void configureHttpClient(HttpClientBuilder builder) {
builder.setSSLSocketFactory(sslContext);
RegistryBuilder.<ConnectionSocketFactory>create().register("https", sslContext).build();
builder.setConnectionManager(poolingConnectionManager);
builder.setKeepAliveStrategy(connectionKeepAliveStrategy);
}
});
}
#Override
public void afterApplicationStart(CamelContext arg0) {
}
};
}
}
#Configuration
public class HttpClientConfig {
private static final int DEFAULT_KEEP_ALIVE_TIME_MILLIS = 20 * 1000;
private static final int CLOSE_IDLE_CONNECTION_WAIT_TIME_SECS = 30;
#Value("${pathCertificado}")
private String pathCertificado;
private Logger logger = LoggerFactory.getLogger(HttpClientConfig.class);
#Bean
public PoolingHttpClientConnectionManager poolingConnectionManager() {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(200);
connectionManager.setDefaultMaxPerRoute(20);
return connectionManager;
}
#Bean
public CloseableHttpClient httpClient() {
RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(5000).setConnectTimeout(5000)
.setSocketTimeout(15000).build();
return HttpClientBuilder.create().setSSLSocketFactory(this.getSSLContext())
.setConnectionManager(this.poolingConnectionManager()).setDefaultRequestConfig(config)
.setKeepAliveStrategy(this.connectionKeepAliveStrategy()).build();
}
#Bean
public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
return new ConnectionKeepAliveStrategy() {
#Override
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
HeaderElementIterator it = new BasicHeaderElementIterator(
response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
return Long.parseLong(value) * 1000;
}
}
return DEFAULT_KEEP_ALIVE_TIME_MILLIS;
}
};
}
#Bean
public Runnable idleConnectionMonitor(final PoolingHttpClientConnectionManager connectionManager) {
return new Runnable() {
#Override
#Scheduled(fixedDelay = 10000)
public void run() {
if (connectionManager != null) {
connectionManager.closeExpiredConnections();
connectionManager.closeIdleConnections(CLOSE_IDLE_CONNECTION_WAIT_TIME_SECS, TimeUnit.SECONDS);
}
}
};
}
#Bean
public SSLConnectionSocketFactory getSSLContext() {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try (FileInputStream jksFile = new FileInputStream(this.pathCertificado)) {
keyStore.load(jksFile, "xxxxxx".toCharArray());
}
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(keyStore, acceptingTrustStrategy).build();
return new SSLConnectionSocketFactory(sslContext);
} catch (Exception e) {
logger.error("Keystore load failed: " + this.pathCertificado, e);
return null;
}
}
}
I am working on a project that requires real-time interaction between users. I want to have a HTML5 web client (simple enough) and also a local client (preferably Java) with both being able to connect to the server. I have done some research and have not found a conclusive answer to whether or not the local client can connect to the server without a browser.
Question: Is there any way to connect from a local Java client to a websocket server without a browse? I have seen some browser wrappers in other languages that might make this possible. If not, I am open to suggestions.
Thanks.
You might also consider using JSR 356 - Java API for WebSocket. It is part of Java EE 7, but client can be run from plain Java SE without any issues. There are multiple implementations available right now and following will work in all of them:
programmatic API:
final WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer();
Session session = webSocketContainer.connectToServer(new Endpoint() {
#Override
public void onOpen(Session session, EndpointConfig config) {
// session.addMessageHandler( ... );
}
}, URI.create("ws://some.uri"));
annotated API:
public static void main(String[] args) throws IOException, DeploymentException {
final WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer();
webSocketContainer.connectToServer(MyEndpoint.class, URI.create("ws://some.uri"));
}
#ClientEndpoint
public static class MyEndpoint {
// text
#OnMessage
void onMessage(Session session, String message) {
// ...
}
// binary
#OnMessage
void onMessage(Session session, ByteBuffer message) {
// ...
}
// #OnClose, #OnOpen, #OnError
}
please see linked page for further details (full specification).
There are various implementations out here, basically every Java container has one. I am working on Glassfish/WebLogic implementation and its called Tyrus, feel free to try it out (we provide easy to use all in one bundle, see http://search.maven.org/...).
You most certainly CAN utilize WebSockets from desktop applications in Java, outside the browser sandbox. The thinking behind this is that you can create thick clients that create TCP connections, so of course they should be able to create WebSocket connections on top of those TCP connections.
One of the newest and best APIs for doing so is written by Kaazing, taking the point of view that a WebSocket is just like a socket and can be created using simple "ws://" URIs.
The API is discussed in detail on the Kaazing Gateway 5.0 Java WebSocket Documentation site. You can download the plain Gateway from Kaazing here
Create a websocket:
import com.kaazing.net.ws.WebSocket;
import com.kaazing.net.ws.WebSocketFactory;
wsFactory = WebSocketFactory.createWebSocketFactory();
ws = wsFactory.createWebSocket(URI.create("ws://example.com:8001/path"));
ws.connect(); // This will block or throw an exception if failed.
To send messages, add a WebSocketMessageWriter object:
WebSocketMessageWriter writer = ws.getMessageWriter();
String text = "Hello WebSocket!";
writer.writeText(text); // Send text message
To receive or consume messages, add WebSocket and WebSocketMessageReader objects:
wsFactory = WebSocketFactory.createWebSocketFactory();
ws = wsFactory.createWebSocket(URI.create("ws://example.com:8001/path"));
ws.connect(); // This will block or throw an exception if failed.
WebSocketMessageReader reader = ws.getMessageReader();
WebSocketMessageType type = null; // Block till a message arrives
// Loop till the connection goes away
while ((type = reader.next()) != WebSocketMessageType.EOS) {
switch (type) { // Handle both text and binary messages
case TEXT:
CharSequence text = reader.getText();
log("RECEIVED TEXT MESSAGE: " + text.toString());
break;
case BINARY:
ByteBuffer buffer = reader.getBinary();
log("RECEIVED BINARY MESSAGE: " + getHexDump(buffer));
break;
}
}
(Full Disclosure: I used to work at Kaazing Corporation as a server engineer.)
Vert.x has a java websocket client:
VertxFactory.newVertx()
.createHttpClient()
.setHost("localhost")
.setPort(8080)
.connectWebsocket("/ws", new Handler<WebSocket>() {
#Override
public void handle(final WebSocket webSocket) {
// Listen
webSocket.dataHandler(new Handler<Buffer>() {
#Override
public void handle(Buffer buff) {
log.info("Received {}", buff.toString());
}
});
// Publish
webSocket.writeTextFrame("Heya");
}
});
Netty is a good choice for such task, it's a high performance network application framework and it supports SSL elegantly, here is netty websocket client example from netty github:
public final class WebSocketClient {
static final String URL = System.getProperty("url", "ws://127.0.0.1:8080/websocket");
public static void main(String[] args) throws Exception {
URI uri = new URI(URL);
String scheme = uri.getScheme() == null? "ws" : uri.getScheme();
final String host = uri.getHost() == null? "127.0.0.1" : uri.getHost();
final int port;
if (uri.getPort() == -1) {
if ("ws".equalsIgnoreCase(scheme)) {
port = 80;
} else if ("wss".equalsIgnoreCase(scheme)) {
port = 443;
} else {
port = -1;
}
} else {
port = uri.getPort();
}
if (!"ws".equalsIgnoreCase(scheme) && !"wss".equalsIgnoreCase(scheme)) {
System.err.println("Only WS(S) is supported.");
return;
}
final boolean ssl = "wss".equalsIgnoreCase(scheme);
final SslContext sslCtx;
if (ssl) {
sslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).build();
} else {
sslCtx = null;
}
EventLoopGroup group = new NioEventLoopGroup();
try {
// Connect with V13 (RFC 6455 aka HyBi-17). You can change it to V08 or V00.
// If you change it to V00, ping is not supported and remember to change
// HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline.
final WebSocketClientHandler handler =
new WebSocketClientHandler(
WebSocketClientHandshakerFactory.newHandshaker(
uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders()));
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
#Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc(), host, port));
}
p.addLast(
new HttpClientCodec(),
new HttpObjectAggregator(8192),
WebSocketClientCompressionHandler.INSTANCE,
handler);
}
});
Channel ch = b.connect(uri.getHost(), port).sync().channel();
handler.handshakeFuture().sync();
BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String msg = console.readLine();
if (msg == null) {
break;
} else if ("bye".equals(msg.toLowerCase())) {
ch.writeAndFlush(new CloseWebSocketFrame());
ch.closeFuture().sync();
break;
} else if ("ping".equals(msg.toLowerCase())) {
WebSocketFrame frame = new PingWebSocketFrame(Unpooled.wrappedBuffer(new byte[] { 8, 1, 8, 1 }));
ch.writeAndFlush(frame);
} else {
WebSocketFrame frame = new TextWebSocketFrame(msg);
ch.writeAndFlush(frame);
}
}
} finally {
group.shutdownGracefully();
}
}
}
public class WebSocketClientHandler extends SimpleChannelInboundHandler<Object> {
private final WebSocketClientHandshaker handshaker;
private ChannelPromise handshakeFuture;
public WebSocketClientHandler(WebSocketClientHandshaker handshaker) {
this.handshaker = handshaker;
}
public ChannelFuture handshakeFuture() {
return handshakeFuture;
}
#Override
public void handlerAdded(ChannelHandlerContext ctx) {
handshakeFuture = ctx.newPromise();
}
#Override
public void channelActive(ChannelHandlerContext ctx) {
handshaker.handshake(ctx.channel());
}
#Override
public void channelInactive(ChannelHandlerContext ctx) {
System.out.println("WebSocket Client disconnected!");
}
#Override
public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel ch = ctx.channel();
if (!handshaker.isHandshakeComplete()) {
handshaker.finishHandshake(ch, (FullHttpResponse) msg);
System.out.println("WebSocket Client connected!");
handshakeFuture.setSuccess();
return;
}
if (msg instanceof FullHttpResponse) {
FullHttpResponse response = (FullHttpResponse) msg;
throw new IllegalStateException(
"Unexpected FullHttpResponse (getStatus=" + response.status() +
", content=" + response.content().toString(CharsetUtil.UTF_8) + ')');
}
WebSocketFrame frame = (WebSocketFrame) msg;
if (frame instanceof TextWebSocketFrame) {
TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
System.out.println("WebSocket Client received message: " + textFrame.text());
} else if (frame instanceof PongWebSocketFrame) {
System.out.println("WebSocket Client received pong");
} else if (frame instanceof CloseWebSocketFrame) {
System.out.println("WebSocket Client received closing");
ch.close();
}
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
if (!handshakeFuture.isDone()) {
handshakeFuture.setFailure(cause);
}
ctx.close();
}
}