blob: e478b55b477b9c358d315ebeeba44ca06917df1f [file] [log] [blame] [edit]
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#include <jni.h>
#include "crt.h"
#include "http_connection_manager.h"
#include "http_request_response.h"
#include "http_request_utils.h"
#include "java_class_ids.h"
#include <aws/common/atomics.h>
#include <aws/common/mutex.h>
#include <aws/http/connection.h>
#include <aws/http/http.h>
#include <aws/http/request_response.h>
#include <aws/io/logging.h>
#include <aws/io/stream.h>
#if _MSC_VER
# pragma warning(disable : 4204) /* non-constant aggregate initializer */
#endif
/* on 32-bit platforms, casting pointers to longs throws a warning we don't need */
#if UINTPTR_MAX == 0xffffffff
# if defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable : 4305) /* 'type cast': truncation from 'jlong' to 'jni_tls_ctx_options *' */
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wpointer-to-int-cast"
# pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
# endif
#endif
jobject aws_java_http_stream_from_native_new(JNIEnv *env, void *opaque, int version) {
jlong jni_native_ptr = (jlong)opaque;
AWS_ASSERT(jni_native_ptr);
jobject stream = NULL;
switch (version) {
case AWS_HTTP_VERSION_2:
stream = (*env)->NewObject(
env, http2_stream_properties.stream_class, http2_stream_properties.constructor, jni_native_ptr);
break;
case AWS_HTTP_VERSION_1_0:
case AWS_HTTP_VERSION_1_1:
stream = (*env)->NewObject(
env, http_stream_properties.stream_class, http_stream_properties.constructor, jni_native_ptr);
break;
default:
aws_jni_throw_runtime_exception(env, "Unsupported HTTP protocol.");
aws_raise_error(AWS_ERROR_UNIMPLEMENTED);
}
return stream;
}
void aws_java_http_stream_from_native_delete(JNIEnv *env, jobject jHttpStream) {
/* Delete our reference to the HttpStream Object from the JVM. */
(*env)->DeleteGlobalRef(env, jHttpStream);
}
/*******************************************************************************
* http_stream_binding - Jni native represent of the Java HTTP stream object
******************************************************************************/
static void s_http_stream_binding_destroy(JNIEnv *env, struct http_stream_binding *binding) {
if (binding->java_http_stream_base) {
aws_java_http_stream_from_native_delete(env, binding->java_http_stream_base);
}
if (binding->java_http_response_stream_handler != NULL) {
(*env)->DeleteGlobalRef(env, binding->java_http_response_stream_handler);
}
if (binding->native_request) {
aws_http_message_release(binding->native_request);
}
aws_byte_buf_clean_up(&binding->headers_buf);
aws_mem_release(aws_jni_get_allocator(), binding);
}
void *aws_http_stream_binding_acquire(struct http_stream_binding *binding) {
if (binding == NULL) {
return NULL;
}
aws_atomic_fetch_add(&binding->ref, 1);
return binding;
}
void *aws_http_stream_binding_release(JNIEnv *env, struct http_stream_binding *binding) {
if (binding == NULL) {
return NULL;
}
size_t pre_ref = aws_atomic_fetch_sub(&binding->ref, 1);
AWS_ASSERT(pre_ref > 0 && "stream binding refcount has gone negative");
if (pre_ref == 1) {
s_http_stream_binding_destroy(env, binding);
}
return NULL;
}
// If error occurs, A Java exception is thrown and NULL is returned.
struct http_stream_binding *aws_http_stream_binding_new(JNIEnv *env, jobject java_callback_handler) {
struct aws_allocator *allocator = aws_jni_get_allocator();
struct http_stream_binding *binding = aws_mem_calloc(allocator, 1, sizeof(struct http_stream_binding));
AWS_FATAL_ASSERT(binding);
// GetJavaVM() reference doesn't need a NewGlobalRef() call since it's global by default
jint jvmresult = (*env)->GetJavaVM(env, &binding->jvm);
(void)jvmresult;
AWS_FATAL_ASSERT(jvmresult == 0);
binding->java_http_response_stream_handler = (*env)->NewGlobalRef(env, java_callback_handler);
AWS_FATAL_ASSERT(binding->java_http_response_stream_handler);
AWS_FATAL_ASSERT(!aws_byte_buf_init(&binding->headers_buf, allocator, 1024));
aws_atomic_init_int(&binding->ref, 1);
return binding;
}
int aws_java_http_stream_on_incoming_headers_fn(
struct aws_http_stream *stream,
enum aws_http_header_block block_type,
const struct aws_http_header *header_array,
size_t num_headers,
void *user_data) {
(void)block_type;
struct http_stream_binding *binding = (struct http_stream_binding *)user_data;
int resp_status = -1;
int err_code = aws_http_stream_get_incoming_response_status(stream, &resp_status);
if (err_code != AWS_OP_SUCCESS) {
AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Invalid Incoming Response Status", (void *)stream);
return AWS_OP_ERR;
}
binding->response_status = resp_status;
if (aws_marshal_http_headers_array_to_dynamic_buffer(&binding->headers_buf, header_array, num_headers)) {
AWS_LOGF_ERROR(
AWS_LS_HTTP_STREAM, "id=%p: Failed to allocate buffer space for incoming headers", (void *)stream);
return AWS_OP_ERR;
}
return AWS_OP_SUCCESS;
}
int aws_java_http_stream_on_incoming_header_block_done_fn(
struct aws_http_stream *stream,
enum aws_http_header_block block_type,
void *user_data) {
(void)stream;
struct http_stream_binding *binding = (struct http_stream_binding *)user_data;
/********** JNI ENV ACQUIRE **********/
JNIEnv *env = aws_jni_acquire_thread_env(binding->jvm);
if (env == NULL) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return AWS_OP_ERR;
}
int result = AWS_OP_ERR;
jint jni_block_type = block_type;
jobject jni_headers_buf =
aws_jni_direct_byte_buffer_from_raw_ptr(env, binding->headers_buf.buffer, binding->headers_buf.len);
(*env)->CallVoidMethod(
env,
binding->java_http_response_stream_handler,
http_stream_response_handler_properties.onResponseHeaders,
binding->java_http_stream_base,
(jint)binding->response_status,
(jint)block_type,
jni_headers_buf);
if (aws_jni_check_and_clear_exception(env)) {
(*env)->DeleteLocalRef(env, jni_headers_buf);
aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE);
goto done;
}
/* instead of cleaning it up here, reset it in case another block is encountered */
aws_byte_buf_reset(&binding->headers_buf, false);
(*env)->DeleteLocalRef(env, jni_headers_buf);
(*env)->CallVoidMethod(
env,
binding->java_http_response_stream_handler,
http_stream_response_handler_properties.onResponseHeadersDone,
binding->java_http_stream_base,
jni_block_type);
if (aws_jni_check_and_clear_exception(env)) {
aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE);
goto done;
}
result = AWS_OP_SUCCESS;
done:
aws_jni_release_thread_env(binding->jvm, env);
/********** JNI ENV RELEASE **********/
return result;
}
int aws_java_http_stream_on_incoming_body_fn(
struct aws_http_stream *stream,
const struct aws_byte_cursor *data,
void *user_data) {
struct http_stream_binding *binding = (struct http_stream_binding *)user_data;
size_t total_window_increment = 0;
/********** JNI ENV ACQUIRE **********/
JNIEnv *env = aws_jni_acquire_thread_env(binding->jvm);
if (env == NULL) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return AWS_OP_ERR;
}
int result = AWS_OP_ERR;
jobject jni_payload = aws_jni_direct_byte_buffer_from_raw_ptr(env, data->ptr, data->len);
jint window_increment = (*env)->CallIntMethod(
env,
binding->java_http_response_stream_handler,
http_stream_response_handler_properties.onResponseBody,
binding->java_http_stream_base,
jni_payload);
(*env)->DeleteLocalRef(env, jni_payload);
if (aws_jni_check_and_clear_exception(env)) {
AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Received Exception from onResponseBody", (void *)stream);
aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE);
goto done;
}
if (window_increment < 0) {
AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Window Increment from onResponseBody < 0", (void *)stream);
aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE);
goto done;
}
total_window_increment += window_increment;
if (total_window_increment > 0) {
aws_http_stream_update_window(stream, total_window_increment);
}
result = AWS_OP_SUCCESS;
done:
aws_jni_release_thread_env(binding->jvm, env);
/********** JNI ENV RELEASE **********/
return result;
}
void aws_java_http_stream_on_stream_complete_fn(struct aws_http_stream *stream, int error_code, void *user_data) {
struct http_stream_binding *binding = (struct http_stream_binding *)user_data;
/********** JNI ENV ACQUIRE **********/
JNIEnv *env = aws_jni_acquire_thread_env(binding->jvm);
if (env == NULL) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return;
}
/* Don't invoke Java callbacks if Java HttpStream failed to completely setup */
jint jErrorCode = error_code;
(*env)->CallVoidMethod(
env,
binding->java_http_response_stream_handler,
http_stream_response_handler_properties.onResponseComplete,
binding->java_http_stream_base,
jErrorCode);
if (aws_jni_check_and_clear_exception(env)) {
/* Close the Connection if the Java Callback throws an Exception */
aws_http_connection_close(aws_http_stream_get_connection(stream));
}
aws_jni_release_thread_env(binding->jvm, env);
/********** JNI ENV RELEASE **********/
}
void aws_java_http_stream_on_stream_destroy_fn(void *user_data) {
struct http_stream_binding *binding = (struct http_stream_binding *)user_data;
/********** JNI ENV ACQUIRE **********/
JNIEnv *env = aws_jni_acquire_thread_env(binding->jvm);
if (env == NULL) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return;
}
/* Native stream destroyed, release the binding. */
aws_http_stream_binding_release(env, binding);
aws_jni_release_thread_env(binding->jvm, env);
/********** JNI ENV RELEASE **********/
}
void aws_java_http_stream_on_stream_metrics_fn(
struct aws_http_stream *stream,
const struct aws_http_stream_metrics *metrics,
void *user_data) {
struct http_stream_binding *binding = (struct http_stream_binding *)user_data;
/********** JNI ENV ACQUIRE **********/
JNIEnv *env = aws_jni_acquire_thread_env(binding->jvm);
if (env == NULL) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return;
}
/* Convert metrics to Java HttpStreamMetrics obj */
jobject jni_metrics = (*env)->NewObject(
env,
http_stream_metrics_properties.http_stream_metrics_class,
http_stream_metrics_properties.constructor_id,
(jlong)metrics->send_start_timestamp_ns,
(jlong)metrics->send_end_timestamp_ns,
(jlong)metrics->sending_duration_ns,
(jlong)metrics->receive_start_timestamp_ns,
(jlong)metrics->receive_end_timestamp_ns,
(jlong)metrics->receiving_duration_ns,
/* Stream IDs are 31-bit unsigned integers, which fits into Java's regular (signed) 32-bit int */
(jint)metrics->stream_id);
(*env)->CallVoidMethod(
env,
binding->java_http_response_stream_handler,
http_stream_response_handler_properties.onMetrics,
binding->java_http_stream_base,
jni_metrics);
/* Delete local reference to metrics object */
(*env)->DeleteLocalRef(env, jni_metrics);
if (aws_jni_check_and_clear_exception(env)) {
/* Close the Connection if the Java Callback throws an Exception */
aws_http_connection_close(aws_http_stream_get_connection(stream));
AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Received Exception from onMetrics", (void *)stream);
aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE);
}
aws_jni_release_thread_env(binding->jvm, env);
/********** JNI ENV RELEASE **********/
}
jobjectArray aws_java_http_headers_from_native(JNIEnv *env, struct aws_http_headers *headers) {
(void)headers;
jobjectArray ret;
const size_t header_count = aws_http_headers_count(headers);
ret = (jobjectArray)(*env)->NewObjectArray(
env, (jsize)header_count, http_header_properties.http_header_class, (void *)NULL);
for (size_t index = 0; index < header_count; index += 1) {
struct aws_http_header header;
aws_http_headers_get_index(headers, index, &header);
jbyteArray header_name = aws_jni_byte_array_from_cursor(env, &header.name);
jbyteArray header_value = aws_jni_byte_array_from_cursor(env, &header.value);
jobject java_http_header = (*env)->NewObject(
env,
http_header_properties.http_header_class,
http_header_properties.constructor_method_id,
header_name,
header_value);
(*env)->SetObjectArrayElement(env, ret, (jsize)index, java_http_header);
}
return (ret);
}
static jobject s_make_request_general(
JNIEnv *env,
jlong jni_connection,
jbyteArray marshalled_request,
jobject jni_http_request_body_stream,
jobject jni_http_response_callback_handler,
enum aws_http_version version) {
struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection;
struct aws_http_connection *native_conn = connection_binding->connection;
if (!native_conn) {
aws_jni_throw_null_pointer_exception(env, "HttpClientConnection.MakeRequest: Invalid aws_http_connection");
return (jobject)NULL;
}
if (!jni_http_response_callback_handler) {
aws_jni_throw_illegal_argument_exception(
env, "HttpClientConnection.MakeRequest: Invalid jni_http_response_callback_handler");
return (jobject)NULL;
}
/* initial refcount created for the Java object */
struct http_stream_binding *stream_binding = aws_http_stream_binding_new(env, jni_http_response_callback_handler);
if (!stream_binding) {
/* Exception already thrown */
return (jobject)NULL;
}
stream_binding->native_request =
aws_http_request_new_from_java_http_request(env, marshalled_request, jni_http_request_body_stream);
if (stream_binding->native_request == NULL) {
/* Exception already thrown */
goto error;
}
struct aws_http_make_request_options request_options = {
.self_size = sizeof(request_options),
.request = stream_binding->native_request,
/* Set Callbacks */
.on_response_headers = aws_java_http_stream_on_incoming_headers_fn,
.on_response_header_block_done = aws_java_http_stream_on_incoming_header_block_done_fn,
.on_response_body = aws_java_http_stream_on_incoming_body_fn,
.on_complete = aws_java_http_stream_on_stream_complete_fn,
.on_destroy = aws_java_http_stream_on_stream_destroy_fn,
.on_metrics = aws_java_http_stream_on_stream_metrics_fn,
.user_data = stream_binding,
};
stream_binding->native_stream = aws_http_connection_make_request(native_conn, &request_options);
if (stream_binding->native_stream == NULL) {
AWS_LOGF_ERROR(AWS_LS_HTTP_CONNECTION, "Stream Request Failed. conn: %p", (void *)native_conn);
aws_jni_throw_runtime_exception(env, "HttpClientConnection.MakeRequest: Unable to Execute Request");
goto error;
}
/* Stream created successfully, acquire on binding for the native stream lifetime. */
aws_http_stream_binding_acquire(stream_binding);
jobject jHttpStreamBase = aws_java_http_stream_from_native_new(env, stream_binding, version);
if (jHttpStreamBase == NULL) {
goto error;
}
AWS_LOGF_TRACE(
AWS_LS_HTTP_CONNECTION,
"Opened new Stream on Connection. conn: %p, stream: %p",
(void *)native_conn,
(void *)stream_binding->native_stream);
return jHttpStreamBase;
error:
aws_http_stream_release(stream_binding->native_stream);
aws_http_stream_binding_release(env, stream_binding);
return NULL;
}
JNIEXPORT jobject JNICALL Java_software_amazon_awssdk_crt_http_HttpClientConnection_httpClientConnectionMakeRequest(
JNIEnv *env,
jclass jni_class,
jlong jni_connection,
jbyteArray marshalled_request,
jobject jni_http_request_body_stream,
jobject jni_http_response_callback_handler) {
(void)jni_class;
aws_cache_jni_ids(env);
return s_make_request_general(
env,
jni_connection,
marshalled_request,
jni_http_request_body_stream,
jni_http_response_callback_handler,
AWS_HTTP_VERSION_1_1);
}
JNIEXPORT jobject JNICALL Java_software_amazon_awssdk_crt_http_Http2ClientConnection_http2ClientConnectionMakeRequest(
JNIEnv *env,
jclass jni_class,
jlong jni_connection,
jbyteArray marshalled_request,
jobject jni_http_request_body_stream,
jobject jni_http_response_callback_handler) {
(void)jni_class;
aws_cache_jni_ids(env);
return s_make_request_general(
env,
jni_connection,
marshalled_request,
jni_http_request_body_stream,
jni_http_response_callback_handler,
AWS_HTTP_VERSION_2);
}
struct http_stream_chunked_callback_data {
struct http_stream_binding *stream_cb_data;
struct aws_byte_buf chunk_data;
struct aws_input_stream *chunk_stream;
jobject completion_callback;
};
static void s_cleanup_chunked_callback_data(
JNIEnv *env,
struct http_stream_chunked_callback_data *chunked_callback_data) {
aws_input_stream_destroy(chunked_callback_data->chunk_stream);
aws_byte_buf_clean_up(&chunked_callback_data->chunk_data);
(*env)->DeleteGlobalRef(env, chunked_callback_data->completion_callback);
aws_mem_release(aws_jni_get_allocator(), chunked_callback_data);
}
static void s_write_chunk_complete(struct aws_http_stream *stream, int error_code, void *user_data) {
(void)stream;
struct http_stream_chunked_callback_data *chunked_callback_data = user_data;
/********** JNI ENV ACQUIRE **********/
JNIEnv *env = aws_jni_acquire_thread_env(chunked_callback_data->stream_cb_data->jvm);
if (env == NULL) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return;
}
(*env)->CallVoidMethod(
env,
chunked_callback_data->completion_callback,
http_stream_write_chunk_completion_properties.callback,
error_code);
aws_jni_check_and_clear_exception(env);
JavaVM *jvm = chunked_callback_data->stream_cb_data->jvm;
s_cleanup_chunked_callback_data(env, chunked_callback_data);
aws_jni_release_thread_env(jvm, env);
/********** JNI ENV RELEASE **********/
}
JNIEXPORT jint JNICALL Java_software_amazon_awssdk_crt_http_HttpStream_httpStreamWriteChunk(
JNIEnv *env,
jclass jni_class,
jlong jni_cb_data,
jbyteArray chunk_data,
jboolean is_final_chunk,
jobject completion_callback) {
(void)jni_class;
aws_cache_jni_ids(env);
struct http_stream_binding *cb_data = (struct http_stream_binding *)jni_cb_data;
struct aws_http_stream *stream = cb_data->native_stream;
struct http_stream_chunked_callback_data *chunked_callback_data =
aws_mem_calloc(aws_jni_get_allocator(), 1, sizeof(struct http_stream_chunked_callback_data));
chunked_callback_data->stream_cb_data = cb_data;
chunked_callback_data->completion_callback = (*env)->NewGlobalRef(env, completion_callback);
struct aws_byte_cursor chunk_cur = aws_jni_byte_cursor_from_jbyteArray_acquire(env, chunk_data);
aws_byte_buf_init_copy_from_cursor(&chunked_callback_data->chunk_data, aws_jni_get_allocator(), chunk_cur);
aws_jni_byte_cursor_from_jbyteArray_release(env, chunk_data, chunk_cur);
struct aws_http1_chunk_options chunk_options = {
.chunk_data_size = chunked_callback_data->chunk_data.len,
.user_data = chunked_callback_data,
.on_complete = s_write_chunk_complete,
};
chunk_cur = aws_byte_cursor_from_buf(&chunked_callback_data->chunk_data);
chunked_callback_data->chunk_stream = aws_input_stream_new_from_cursor(aws_jni_get_allocator(), &chunk_cur);
chunk_options.chunk_data = chunked_callback_data->chunk_stream;
if (aws_http1_stream_write_chunk(stream, &chunk_options)) {
s_cleanup_chunked_callback_data(env, chunked_callback_data);
return AWS_OP_ERR;
}
if (is_final_chunk) {
struct aws_http1_chunk_options final_chunk_options = {
.chunk_data_size = 0,
};
if (aws_http1_stream_write_chunk(stream, &final_chunk_options)) {
return AWS_OP_ERR;
}
}
return AWS_OP_SUCCESS;
}
JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_HttpStreamBase_httpStreamBaseActivate(
JNIEnv *env,
jclass jni_class,
jlong jni_stream_binding,
jobject j_http_stream_base) {
(void)jni_class;
aws_cache_jni_ids(env);
struct http_stream_binding *binding = (struct http_stream_binding *)jni_stream_binding;
struct aws_http_stream *stream = binding->native_stream;
if (stream == NULL) {
aws_jni_throw_runtime_exception(env, "HttpStream is null.");
return;
}
AWS_LOGF_TRACE(AWS_LS_HTTP_STREAM, "Activating Stream. stream: %p", (void *)stream);
/* global ref this because now the callbacks will be firing, and they will release their reference when the
* stream callback sequence completes. */
binding->java_http_stream_base = (*env)->NewGlobalRef(env, j_http_stream_base);
if (aws_http_stream_activate(stream)) {
(*env)->DeleteGlobalRef(env, binding->java_http_stream_base);
aws_jni_throw_runtime_exception(
env, "HttpStream activate failed with error %s\n", aws_error_str(aws_last_error()));
}
}
JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_HttpStreamBase_httpStreamBaseRelease(
JNIEnv *env,
jclass jni_class,
jlong jni_binding) {
(void)jni_class;
aws_cache_jni_ids(env);
struct http_stream_binding *binding = (struct http_stream_binding *)jni_binding;
struct aws_http_stream *stream = binding->native_stream;
if (stream == NULL) {
aws_jni_throw_runtime_exception(env, "HttpStream is null.");
return;
}
AWS_LOGF_TRACE(AWS_LS_HTTP_STREAM, "Releasing Stream. stream: %p", (void *)stream);
aws_http_stream_release(stream);
aws_http_stream_binding_release(env, binding);
}
JNIEXPORT jint JNICALL Java_software_amazon_awssdk_crt_http_HttpStreamBase_httpStreamBaseGetResponseStatusCode(
JNIEnv *env,
jclass jni_class,
jlong jni_binding) {
(void)jni_class;
aws_cache_jni_ids(env);
struct http_stream_binding *binding = (struct http_stream_binding *)jni_binding;
struct aws_http_stream *stream = binding->native_stream;
if (stream == NULL) {
aws_jni_throw_runtime_exception(env, "HttpStream is null.");
return -1;
}
int status = -1;
int err_code = aws_http_stream_get_incoming_response_status(stream, &status);
if (err_code != AWS_OP_SUCCESS) {
aws_jni_throw_runtime_exception(env, "Error Getting Response Status Code from HttpStream.");
return -1;
}
return (jint)status;
}
JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_HttpStreamBase_httpStreamBaseIncrementWindow(
JNIEnv *env,
jclass jni_class,
jlong jni_binding,
jint window_update) {
(void)jni_class;
aws_cache_jni_ids(env);
struct http_stream_binding *binding = (struct http_stream_binding *)jni_binding;
struct aws_http_stream *stream = binding->native_stream;
if (stream == NULL) {
aws_jni_throw_runtime_exception(env, "HttpStream is null.");
return;
}
if (window_update < 0) {
aws_jni_throw_runtime_exception(env, "Window Update is < 0");
return;
}
AWS_LOGF_TRACE(
AWS_LS_HTTP_STREAM, "Updating Stream Window. stream: %p, update: %d", (void *)stream, (int)window_update);
aws_http_stream_update_window(stream, window_update);
}
JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_Http2Stream_http2StreamResetStream(
JNIEnv *env,
jclass jni_class,
jlong jni_cb_data,
jint error_code) {
(void)jni_class;
aws_cache_jni_ids(env);
struct http_stream_binding *binding = (struct http_stream_binding *)jni_cb_data;
struct aws_http_stream *stream = binding->native_stream;
if (stream == NULL) {
aws_jni_throw_null_pointer_exception(env, "Http2Stream is null.");
return;
}
AWS_LOGF_TRACE(AWS_LS_HTTP_STREAM, "Resetting Stream. stream: %p", (void *)stream);
if (aws_http2_stream_reset(stream, error_code)) {
aws_jni_throw_runtime_exception(
env, "reset stream failed with error %d(%s).", aws_last_error(), aws_error_debug_str(aws_last_error()));
return;
}
}
JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_HttpClientConnection_httpClientConnectionShutdown(
JNIEnv *env,
jclass jni_class,
jlong jni_connection) {
(void)jni_class;
aws_cache_jni_ids(env);
struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection;
struct aws_http_connection *native_conn = connection_binding->connection;
if (!native_conn) {
aws_jni_throw_runtime_exception(env, "HttpClientConnection.Shutdown: Invalid aws_http_connection");
return;
}
aws_http_connection_close(native_conn);
}
JNIEXPORT jboolean JNICALL Java_software_amazon_awssdk_crt_http_HttpClientConnection_httpClientConnectionIsOpen(
JNIEnv *env,
jclass jni_class,
jlong jni_connection) {
(void)jni_class;
aws_cache_jni_ids(env);
struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection;
struct aws_http_connection *native_conn = connection_binding->connection;
if (!native_conn) {
aws_jni_throw_runtime_exception(env, "HttpClientConnection.isOpen: Invalid aws_http_connection");
return false;
}
return aws_http_connection_is_open(native_conn);
}
JNIEXPORT jshort JNICALL Java_software_amazon_awssdk_crt_http_HttpClientConnection_httpClientConnectionGetVersion(
JNIEnv *env,
jclass jni_class,
jlong jni_connection) {
(void)jni_class;
aws_cache_jni_ids(env);
struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection;
struct aws_http_connection *native_conn = connection_binding->connection;
if (!native_conn) {
aws_jni_throw_runtime_exception(env, "HttpClientConnection.getVersion: Invalid aws_http_connection");
return 0;
}
return (jshort)aws_http_connection_get_version(native_conn);
}
JNIEXPORT jboolean JNICALL Java_software_amazon_awssdk_crt_http_HttpClientConnection_isErrorRetryable(
JNIEnv *env,
jclass jni_class,
jint error_code) {
(void)jni_class;
(void)env;
aws_cache_jni_ids(env);
switch (error_code) {
case AWS_ERROR_HTTP_HEADER_NOT_FOUND:
case AWS_ERROR_HTTP_INVALID_HEADER_FIELD:
case AWS_ERROR_HTTP_INVALID_HEADER_NAME:
case AWS_ERROR_HTTP_INVALID_HEADER_VALUE:
case AWS_ERROR_HTTP_INVALID_METHOD:
case AWS_ERROR_HTTP_INVALID_PATH:
case AWS_ERROR_HTTP_INVALID_STATUS_CODE:
case AWS_ERROR_HTTP_MISSING_BODY_STREAM:
case AWS_ERROR_HTTP_INVALID_BODY_STREAM:
case AWS_ERROR_HTTP_OUTGOING_STREAM_LENGTH_INCORRECT:
case AWS_ERROR_HTTP_CALLBACK_FAILURE:
case AWS_ERROR_HTTP_STREAM_MANAGER_SHUTTING_DOWN:
case AWS_HTTP2_ERR_CANCEL:
return false;
default:
return true;
}
}
struct aws_http2_callback_data {
JavaVM *jvm;
jobject async_callback;
};
static void s_cleanup_http2_callback_data(struct aws_http2_callback_data *callback_data, JNIEnv *env) {
if (callback_data == NULL || env == NULL) {
return;
}
if (callback_data->async_callback) {
(*env)->DeleteGlobalRef(env, callback_data->async_callback);
}
aws_mem_release(aws_jni_get_allocator(), callback_data);
}
static struct aws_http2_callback_data *s_new_http2_callback_data(
JNIEnv *env,
struct aws_allocator *allocator,
jobject async_callback) {
struct aws_http2_callback_data *callback_data =
aws_mem_calloc(allocator, 1, sizeof(struct aws_http2_callback_data));
jint jvmresult = (*env)->GetJavaVM(env, &callback_data->jvm);
AWS_FATAL_ASSERT(jvmresult == 0);
callback_data->async_callback = async_callback ? (*env)->NewGlobalRef(env, async_callback) : NULL;
AWS_FATAL_ASSERT(callback_data->async_callback != NULL);
return callback_data;
}
static void s_on_settings_completed(struct aws_http_connection *http2_connection, int error_code, void *user_data) {
(void)http2_connection;
struct aws_http2_callback_data *callback_data = user_data;
/********** JNI ENV ACQUIRE **********/
JavaVM *jvm = callback_data->jvm;
JNIEnv *env = aws_jni_acquire_thread_env(jvm);
if (env == NULL) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return;
}
if (error_code) {
jobject crt_exception = aws_jni_new_crt_exception_from_error_code(env, error_code);
(*env)->CallVoidMethod(env, callback_data->async_callback, async_callback_properties.on_failure, crt_exception);
(*env)->DeleteLocalRef(env, crt_exception);
} else {
(*env)->CallVoidMethod(env, callback_data->async_callback, async_callback_properties.on_success);
}
AWS_FATAL_ASSERT(!aws_jni_check_and_clear_exception(env));
s_cleanup_http2_callback_data(callback_data, env);
aws_jni_release_thread_env(jvm, env);
/********** JNI ENV RELEASE **********/
}
JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_Http2ClientConnection_http2ClientConnectionUpdateSettings(
JNIEnv *env,
jclass jni_class,
jlong jni_connection,
jobject java_async_callback,
jlongArray java_marshalled_settings) {
(void)jni_class;
aws_cache_jni_ids(env);
struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection;
struct aws_http_connection *native_conn = connection_binding->connection;
if (!native_conn) {
aws_jni_throw_null_pointer_exception(
env, "Http2ClientConnection.http2ClientConnectionUpdateSettings: Invalid aws_http_connection");
return;
}
if (!java_async_callback) {
aws_jni_throw_illegal_argument_exception(
env, "Http2ClientConnection.http2ClientConnectionUpdateSettings: Invalid async callback");
return;
}
struct aws_allocator *allocator = aws_jni_get_allocator();
struct aws_http2_callback_data *callback_data = s_new_http2_callback_data(env, allocator, java_async_callback);
/* We marshalled each setting to two long integers, the long list will be number of settings times two */
const size_t len = (*env)->GetArrayLength(env, java_marshalled_settings);
AWS_ASSERT(len % 2 == 0);
const size_t settings_len = len / 2;
struct aws_http2_setting *settings =
settings_len ? aws_mem_calloc(allocator, settings_len, sizeof(struct aws_http2_setting)) : NULL;
int success = false;
jlong *marshalled_settings = (*env)->GetLongArrayElements(env, java_marshalled_settings, NULL);
for (size_t i = 0; i < settings_len; i++) {
jlong id = marshalled_settings[i * 2];
settings[i].id = id;
jlong value = marshalled_settings[i * 2 + 1];
settings[i].value = (uint32_t)value;
}
if (aws_http2_connection_change_settings(
native_conn, settings, settings_len, s_on_settings_completed, callback_data)) {
aws_jni_throw_runtime_exception(
env, "Http2ClientConnection.http2ClientConnectionUpdateSettings: failed to change settings");
goto done;
}
success = true;
done:
aws_mem_release(allocator, settings);
(*env)->ReleaseLongArrayElements(env, java_marshalled_settings, (jlong *)marshalled_settings, JNI_ABORT);
if (!success) {
s_cleanup_http2_callback_data(callback_data, env);
}
return;
}
static void s_on_ping_completed(
struct aws_http_connection *http2_connection,
uint64_t round_trip_time_ns,
int error_code,
void *user_data) {
(void)http2_connection;
struct aws_http2_callback_data *callback_data = user_data;
/********** JNI ENV ACQUIRE **********/
JavaVM *jvm = callback_data->jvm;
JNIEnv *env = aws_jni_acquire_thread_env(jvm);
if (env == NULL) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return;
}
if (error_code) {
jobject crt_exception = aws_jni_new_crt_exception_from_error_code(env, error_code);
(*env)->CallVoidMethod(env, callback_data->async_callback, async_callback_properties.on_failure, crt_exception);
(*env)->DeleteLocalRef(env, crt_exception);
} else {
jobject java_round_trip_time_ns = (*env)->NewObject(
env, boxed_long_properties.long_class, boxed_long_properties.constructor, (jlong)round_trip_time_ns);
(*env)->CallVoidMethod(
env,
callback_data->async_callback,
async_callback_properties.on_success_with_object,
java_round_trip_time_ns);
(*env)->DeleteLocalRef(env, java_round_trip_time_ns);
}
AWS_FATAL_ASSERT(!aws_jni_check_and_clear_exception(env));
s_cleanup_http2_callback_data(callback_data, env);
aws_jni_release_thread_env(jvm, env);
/********** JNI ENV RELEASE **********/
}
JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_Http2ClientConnection_http2ClientConnectionSendPing(
JNIEnv *env,
jclass jni_class,
jlong jni_connection,
jobject java_async_callback,
jbyteArray ping_data) {
(void)jni_class;
aws_cache_jni_ids(env);
struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection;
struct aws_http_connection *native_conn = connection_binding->connection;
if (!native_conn) {
aws_jni_throw_null_pointer_exception(
env, "Http2ClientConnection.http2ClientConnectionSendPing: Invalid aws_http_connection");
return;
}
if (!java_async_callback) {
aws_jni_throw_illegal_argument_exception(
env, "Http2ClientConnection.http2ClientConnectionSendPing: Invalid async callback");
return;
}
bool success = false;
struct aws_allocator *allocator = aws_jni_get_allocator();
struct aws_byte_cursor *ping_cur_pointer = NULL;
struct aws_byte_cursor ping_cur;
AWS_ZERO_STRUCT(ping_cur);
struct aws_http2_callback_data *callback_data = s_new_http2_callback_data(env, allocator, java_async_callback);
if (ping_data) {
ping_cur = aws_jni_byte_cursor_from_jbyteArray_acquire(env, ping_data);
ping_cur_pointer = &ping_cur;
}
if (aws_http2_connection_ping(native_conn, ping_cur_pointer, s_on_ping_completed, callback_data)) {
aws_jni_throw_runtime_exception(env, "Failed to send ping");
goto done;
}
success = true;
done:
if (ping_cur_pointer) {
aws_jni_byte_cursor_from_jbyteArray_release(env, ping_data, ping_cur);
}
if (!success) {
s_cleanup_http2_callback_data(callback_data, env);
}
return;
}
JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_Http2ClientConnection_http2ClientConnectionSendGoAway(
JNIEnv *env,
jclass jni_class,
jlong jni_connection,
jlong h2_error_code,
jboolean allow_more_streams,
jbyteArray debug_data) {
(void)jni_class;
aws_cache_jni_ids(env);
struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection;
struct aws_http_connection *native_conn = connection_binding->connection;
struct aws_byte_cursor *debug_cur_pointer = NULL;
struct aws_byte_cursor debug_cur;
AWS_ZERO_STRUCT(debug_cur);
if (!native_conn) {
aws_jni_throw_runtime_exception(
env, "Http2ClientConnection.http2ClientConnectionSendGoAway: Invalid aws_http_connection");
return;
}
if (debug_data) {
debug_cur = aws_jni_byte_cursor_from_jbyteArray_acquire(env, debug_data);
debug_cur_pointer = &debug_cur;
}
aws_http2_connection_send_goaway(native_conn, (uint32_t)h2_error_code, allow_more_streams, debug_cur_pointer);
if (debug_cur_pointer) {
aws_jni_byte_cursor_from_jbyteArray_release(env, debug_data, debug_cur);
}
return;
}
JNIEXPORT void JNICALL
Java_software_amazon_awssdk_crt_http_Http2ClientConnection_http2ClientConnectionUpdateConnectionWindow(
JNIEnv *env,
jclass jni_class,
jlong jni_connection,
jlong increment_size) {
(void)jni_class;
aws_cache_jni_ids(env);
struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection;
struct aws_http_connection *native_conn = connection_binding->connection;
if (!native_conn) {
aws_jni_throw_runtime_exception(
env, "Http2ClientConnection.http2ClientConnectionUpdateConnectionWindow: Invalid aws_http_connection");
return;
}
/* We did range check in Java already. */
aws_http2_connection_update_window(native_conn, (uint32_t)increment_size);
return;
}
#if UINTPTR_MAX == 0xffffffff
# if defined(_MSC_VER)
# pragma warning(pop)
# else
# pragma GCC diagnostic pop
# endif
#endif