Skip to content

Commit

Permalink
Add JNI Http Connection Open/Close APIs (#56)
Browse files Browse the repository at this point in the history
* Refactor CrtResource to enforce more invariants

* Add JNI Http Connection Open/Close APIs
  • Loading branch information
alexw91 authored and Justin Boswell committed May 14, 2019
1 parent 6ac6a87 commit 50254a9
Show file tree
Hide file tree
Showing 7 changed files with 575 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/main/java/software/amazon/awssdk/crt/CRT.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
*/
public final class CRT {
private static final String CRT_LIB_NAME = "aws-crt-jni";
public static final int AWS_CRT_SUCCESS = 0;

static {
try {
Expand Down
197 changes: 197 additions & 0 deletions src/main/java/software/amazon/awssdk/crt/http/HttpConnection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.crt.http;

import software.amazon.awssdk.crt.AsyncCallback;
import software.amazon.awssdk.crt.CrtResource;
import software.amazon.awssdk.crt.CrtRuntimeException;
import software.amazon.awssdk.crt.io.ClientBootstrap;
import software.amazon.awssdk.crt.io.SocketOptions;
import software.amazon.awssdk.crt.io.TlsContext;

import java.net.URI;
import java.util.concurrent.CompletableFuture;

import static software.amazon.awssdk.crt.CRT.AWS_CRT_SUCCESS;


/**
* This class wraps aws-c-http to provide the basic HTTP request/response functionality via the AWS Common Runtime.
*
* HttpConnection represents a single connection to a HTTP service endpoint.
*
* This class is not thread safe and should not be called from different threads.
*/
public class HttpConnection extends CrtResource {
private static final String HTTP = "http";
private static final String HTTPS = "https";
private static final int DEFAULT_HTTP_PORT = 80;
private static final int DEFAULT_HTTPS_PORT = 443;

private final ClientBootstrap clientBootstrap;
private final SocketOptions socketOptions;
private final TlsContext tlsContext;
private final URI uri;
private final int port;
private final boolean useTls;

private final CompletableFuture<HttpConnection> connectedFuture;
private final CompletableFuture<Void> shutdownFuture;

/**
* Creates a new CompletableFuture for a new HttpConnection.
* @param uri Must be non-null and contain a hostname
* @param bootstrap The ClientBootstrap to use for the Connection
* @param socketOptions The SocketOptions to use for the Connection
* @param tlsContext The TlsContext to use for the Connection
* @return CompletableFuture indicating when the connection has completed
* @throws CrtRuntimeException if Native threw a CrtRuntimeException
*/
public static CompletableFuture<HttpConnection> createConnection(URI uri, ClientBootstrap bootstrap,
SocketOptions socketOptions, TlsContext tlsContext) throws CrtRuntimeException {
HttpConnection conn = new HttpConnection(uri, bootstrap, socketOptions, tlsContext);
return conn.connect();
}

/**
* Constructs a new HttpConnection.
* @param uri Must be non-null and contain a hostname
* @param bootstrap The ClientBootstrap to use for the Connection
* @param socketOptions The SocketOptions to use for the Connection
* @param tlsContext The TlsContext to use for the Connection
*/
private HttpConnection(URI uri, ClientBootstrap bootstrap, SocketOptions socketOptions, TlsContext tlsContext) {
if (uri == null) { throw new IllegalArgumentException("URI must not be null"); }
if (uri.getScheme() == null) { throw new IllegalArgumentException("URI does not have a Scheme"); }
if (!HTTP.equals(uri.getScheme()) && !HTTPS.equals(uri.getScheme())) { throw new IllegalArgumentException("URI has unknown Scheme"); }
if (uri.getHost() == null) { throw new IllegalArgumentException("URI does not have a Host name"); }
if (bootstrap == null || bootstrap.isNull()) { throw new IllegalArgumentException("ClientBootstrap must not be null"); }
if (socketOptions == null || socketOptions.isNull()) { throw new IllegalArgumentException("SocketOptions must not be null"); }
if (HTTPS.equals(uri.getScheme()) && tlsContext == null) { throw new IllegalArgumentException("TlsContext must not be null if https is used"); }

int port = uri.getPort();

/* Pick a default port based on the scheme if one wasn't set in the URI */
if (port == -1) {
if (HTTP.equals(uri.getScheme())) { port = DEFAULT_HTTP_PORT; }
if (HTTPS.equals(uri.getScheme())) { port = DEFAULT_HTTPS_PORT; }
}

if (port <= 0 || 65535 < port) {
throw new IllegalArgumentException("Invalid or missing port: " + uri);
}

this.uri = uri;
this.port = port;
this.useTls = HTTPS.equals(uri.getScheme());
this.clientBootstrap = bootstrap;
this.socketOptions = socketOptions;
this.tlsContext = tlsContext;
this.connectedFuture = new CompletableFuture<>();
this.shutdownFuture = new CompletableFuture<>();
}

/**
* Schedules a connection task on the EventLoop to connect to the Http Endpoint
* @return Future indicating when the connection has completed.
*/
private CompletableFuture<HttpConnection> connect() throws CrtRuntimeException {
if (!isNull()) {
return connectedFuture;
}

acquire(httpConnectionNew(this,
clientBootstrap.native_ptr(),
socketOptions.native_ptr(),
useTls ? tlsContext.native_ptr() : 0,
uri.getHost(),
port));

return connectedFuture;
}

/**
* Closes and frees this HttpConnection and any native sub-resources associated with this connection
*/
@Override
public void close() {
if (!isNull()) {
httpConnectionRelease(release());
}

super.close();
}

/** Called from Native EventLoop when the connection is established the first time **/
private void onConnectionComplete(int errorCode) {
if (errorCode == AWS_CRT_SUCCESS) {
connectedFuture.complete(this);
} else {
/* The user can't close this HttpConnection object since they never got a reference to it through the
CompletableFuture, so we need to close ourselves on Connection Error. */
this.close();
connectedFuture.completeExceptionally(new HttpException(errorCode));
}
}

/** Called from Native EventLoop when the connection is shutdown. **/
private void onConnectionShutdown(int errorCode) {
if (errorCode == AWS_CRT_SUCCESS) {
shutdownFuture.complete(null);
} else {
shutdownFuture.completeExceptionally(new HttpException(errorCode));
}
}

/**
* Returns the Shutdown Future
* @return CompletableFuture that will be completed when the connection is shut down.
*/
public CompletableFuture<Void> getShutdownFuture() {
return shutdownFuture;
}

/**
* Schedules a task on the Native EventLoop to shut down the current connection
* @return When this future completes, the shutdown is complete
*/
public CompletableFuture<Void> shutdown() {
if (isNull()) {
return shutdownFuture;
}
try {
httpConnectionShutdown(native_ptr());
} catch (CrtRuntimeException e) {
shutdownFuture.completeExceptionally(e);
}

return shutdownFuture;
}

/*******************************************************************************
* Native methods
******************************************************************************/
private static native long httpConnectionNew(HttpConnection thisObj,
long client_bootstrap,
long socketOptions,
long tlsContext,
String endpoint,
int port) throws CrtRuntimeException;

private static native void httpConnectionShutdown(long connection) throws CrtRuntimeException;
private static native void httpConnectionRelease(long connection);

}
40 changes: 40 additions & 0 deletions src/main/java/software/amazon/awssdk/crt/http/HttpException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.crt.http;

import software.amazon.awssdk.crt.CRT;

/**
* This exception will be thrown by any exceptional cases encountered within the
* JNI bindings to the AWS Common Runtime
*/
public class HttpException extends RuntimeException {
private int errorCode;

public HttpException(int errorCode) {
super(CRT.awsErrorString(errorCode));
this.errorCode = errorCode;
}

/**
* Returns the error code captured when the exception occurred. This can be fed to {@link CRT.awsErrorString} to
* get a user-friendly error string
* @return The error code associated with this exception
*/
int getErrorCode() {
return errorCode;
}
}
10 changes: 9 additions & 1 deletion src/main/java/software/amazon/awssdk/crt/io/TlsContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
public final class TlsContext extends CrtResource {

/**
* Creates a new TlsContext. There are significant native resources consumed to create a TlsContext, so most
* Creates a new Client TlsContext. There are significant native resources consumed to create a TlsContext, so most
* applications will only need to create one and re-use it for all connections.
* @param options A set of options for this context
* @throws CrtRuntimeException If the provided options are malformed or the system is unable
Expand All @@ -34,6 +34,14 @@ public TlsContext(TlsContextOptions options) throws CrtRuntimeException {
acquire(tlsContextNew(options.native_ptr()));
}

/**
* Creates a new Client TlsContext. There are significant native resources consumed to create a TlsContext, so most
* applications will only need to create one and re-use it for all connections.
*/
public TlsContext() throws CrtRuntimeException {
acquire(tlsContextNew(own(new TlsContextOptions()).native_ptr()));
}

/**
* Frees all native resources associated with the context. This object is unusable after close is called.
*/
Expand Down
2 changes: 2 additions & 0 deletions src/native/crt.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,12 @@ static void s_cache_jni_classes(JNIEnv *env) {
extern void s_cache_mqtt_connection(JNIEnv *);
extern void s_cache_message_handler(JNIEnv *);
extern void s_cache_mqtt_exception(JNIEnv *);
extern void s_cache_http_connection(JNIEnv *);
s_cache_mqtt_connection(env);
s_cache_async_callback(env);
s_cache_message_handler(env);
s_cache_mqtt_exception(env);
s_cache_http_connection(env);
}
#if defined(_MSC_VER)
# pragma warning(pop)
Expand Down
Loading

0 comments on commit 50254a9

Please sign in to comment.