diff --git a/library/common/buffer/utility.cc b/library/common/buffer/utility.cc index 5028bfd0db..7119c9bf7b 100644 --- a/library/common/buffer/utility.cc +++ b/library/common/buffer/utility.cc @@ -10,7 +10,7 @@ namespace Utility { Buffer::InstancePtr transformData(envoy_data) { return nullptr; } // TODO: implement this https://github.com/lyft/envoy-mobile/issues/284. -envoy_data transformData(Buffer::Instance&) { return {0, nullptr}; } +envoy_data transformData(Buffer::Instance&) { return envoy_nodata; } } // namespace Utility } // namespace Buffer diff --git a/library/common/http/dispatcher.cc b/library/common/http/dispatcher.cc index 1c7ac805bf..9d28bd475a 100644 --- a/library/common/http/dispatcher.cc +++ b/library/common/http/dispatcher.cc @@ -16,7 +16,7 @@ void Dispatcher::DirectStreamCallbacks::onHeaders(HeaderMapPtr&& headers, bool e if (end_stream) { http_dispatcher_.removeStream(stream_); } - observer_.on_headers_f(Utility::transformHeaders(*headers), end_stream, observer_.context); + observer_.on_headers(Utility::transformHeaders(*headers), end_stream, observer_.context); } void Dispatcher::DirectStreamCallbacks::onData(Buffer::Instance& data, bool end_stream) { @@ -24,18 +24,18 @@ void Dispatcher::DirectStreamCallbacks::onData(Buffer::Instance& data, bool end_ if (end_stream) { http_dispatcher_.removeStream(stream_); } - observer_.on_data_f(Envoy::Buffer::Utility::transformData(data), end_stream, observer_.context); + observer_.on_data(Envoy::Buffer::Utility::transformData(data), end_stream, observer_.context); } void Dispatcher::DirectStreamCallbacks::onTrailers(HeaderMapPtr&& trailers) { ENVOY_LOG(debug, "response trailers for stream:\n{}", *trailers); http_dispatcher_.removeStream(stream_); - observer_.on_trailers_f(Utility::transformHeaders(*trailers), observer_.context); + observer_.on_trailers(Utility::transformHeaders(*trailers), observer_.context); } void Dispatcher::DirectStreamCallbacks::onReset() { http_dispatcher_.removeStream(stream_); - observer_.on_error_f({ENVOY_STREAM_RESET, {0, nullptr}}, observer_.context); + observer_.on_error({ENVOY_STREAM_RESET, envoy_nodata}, observer_.context); } Dispatcher::DirectStream::DirectStream(AsyncClient::Stream& underlying_stream, diff --git a/library/common/http/header_utility.cc b/library/common/http/header_utility.cc index 69b8a133de..5334b84920 100644 --- a/library/common/http/header_utility.cc +++ b/library/common/http/header_utility.cc @@ -9,7 +9,7 @@ namespace Utility { static inline envoy_data copyEnvoyData(size_t length, const uint8_t* source) { uint8_t* destination = static_cast(malloc(sizeof(uint8_t) * length)); memcpy(destination, source, length); - return {length, destination}; + return {length, destination, nullptr, nullptr}; } std::string convertToString(envoy_data s) { @@ -18,7 +18,7 @@ std::string convertToString(envoy_data s) { HeaderMapPtr transformHeaders(envoy_headers headers) { Http::HeaderMapPtr transformed_headers = std::make_unique(); - for (uint64_t i = 0; i < headers.length; i++) { + for (envoy_header_size_t i = 0; i < headers.length; i++) { transformed_headers->addCopy(LowerCaseString(convertToString(headers.headers[i].key)), convertToString(headers.headers[i].value)); } diff --git a/library/common/include/BUILD b/library/common/include/BUILD index ed28c652b8..32922f9525 100644 --- a/library/common/include/BUILD +++ b/library/common/include/BUILD @@ -6,6 +6,7 @@ envoy_package() envoy_cc_library( name = "c_types_interface", + srcs = ["c_types.cc"], hdrs = ["c_types.h"], repository = "@envoy", ) diff --git a/library/common/include/c_types.cc b/library/common/include/c_types.cc new file mode 100644 index 0000000000..43692d163f --- /dev/null +++ b/library/common/include/c_types.cc @@ -0,0 +1,16 @@ +#include "library/common/include/c_types.h" + +// NOLINT(namespace-envoy) + +void envoy_noop_release(void* context) { (void)context; } + +void release_envoy_headers(envoy_headers headers) { + for (envoy_header_size_t i = 0; i < headers.length; i++) { + envoy_header header = headers.headers[i]; + header.key.release(header.key.context); + header.value.release(header.value.context); + } + free(headers.headers); +} + +const envoy_data envoy_nodata = {0, NULL, envoy_noop_release, NULL}; diff --git a/library/common/include/c_types.h b/library/common/include/c_types.h index 3a5be967e6..eeacb5ba99 100644 --- a/library/common/include/c_types.h +++ b/library/common/include/c_types.h @@ -3,6 +3,7 @@ #include #include #include +#include // NOLINT(namespace-envoy) @@ -28,12 +29,31 @@ typedef enum { ENVOY_SUCCESS, ENVOY_FAILURE } envoy_status_t; */ typedef enum { ENVOY_STREAM_RESET } envoy_error_code_t; +#ifdef __cplusplus +extern "C" { // release function +#endif +/** + * Callback indicating Envoy has drained the associated buffer. + */ +typedef void (*envoy_release_f)(void* context); + +/** + * No-op callback. + */ +void envoy_noop_release(void* context); + +#ifdef __cplusplus +} // release function +#endif + /** * Holds raw binary data as an array of bytes. */ typedef struct { - uint64_t length; + size_t length; const uint8_t* bytes; + envoy_release_f release; + void* context; } envoy_data; /** @@ -56,19 +76,29 @@ typedef struct { envoy_data value; } envoy_header; +/** + * Consistent type for dealing with encodable/processable header counts. + */ +typedef int envoy_header_size_t; + /** * Holds an HTTP header map as an array of envoy_header structs. */ typedef struct { // Number of header elements in the array. - uint64_t length; + envoy_header_size_t length; // Array of headers. envoy_header* headers; } envoy_headers; +/** + * Helper function to free/release memory associated with underlying headers. + */ +void release_envoy_headers(envoy_headers headers); + // Convenience constant to pass to function calls with no data. // For example when sending a headers-only request. -const envoy_data envoy_nodata = {0, NULL}; +extern const envoy_data envoy_nodata; /** * Error struct. @@ -88,7 +118,7 @@ extern "C" { // function pointers * @param context, contains the necessary state to carry out platform-specific dispatch and * execution. */ -typedef void (*on_headers)(envoy_headers headers, bool end_stream, void* context); +typedef void (*envoy_on_headers_f)(envoy_headers headers, bool end_stream, void* context); /** * Called when a data frame gets received on the async HTTP stream. * This callback can be invoked multiple times if the data gets streamed. @@ -97,15 +127,15 @@ typedef void (*on_headers)(envoy_headers headers, bool end_stream, void* context * @param context, contains the necessary state to carry out platform-specific dispatch and * execution. */ -typedef void (*on_data)(envoy_data data, bool end_stream, void* context); +typedef void (*envoy_on_data_f)(envoy_data data, bool end_stream, void* context); /** - * Called when all metadata get received on the async HTTP stream. - * Note that end stream is implied when on_metadata is called. + * Called when a metadata frame gets received on the async HTTP stream. + * Note that metadata frames are prohibited from ending a stream. * @param metadata, the metadata received. * @param context, contains the necessary state to carry out platform-specific dispatch and * execution. */ -typedef void (*on_metadata)(envoy_headers metadata, void* context); +typedef void (*envoy_on_metadata_f)(envoy_headers metadata, void* context); /** * Called when all trailers get received on the async HTTP stream. * Note that end stream is implied when on_trailers is called. @@ -113,14 +143,17 @@ typedef void (*on_metadata)(envoy_headers metadata, void* context); * @param context, contains the necessary state to carry out platform-specific dispatch and * execution. */ -typedef void (*on_trailers)(envoy_headers trailers, void* context); +typedef void (*envoy_on_trailers_f)(envoy_headers trailers, void* context); /** * Called when the async HTTP stream has an error. * @param envoy_error, the error received/caused by the async HTTP stream. * @param context, contains the necessary state to carry out platform-specific dispatch and * execution. */ -typedef void (*on_error)(envoy_error error, void* context); +typedef void (*envoy_on_error_f)(envoy_error error, void* context); + +// FIXME comments +typedef void (*envoy_on_complete_f)(void* context); #ifdef __cplusplus } // function pointers @@ -130,10 +163,11 @@ typedef void (*on_error)(envoy_error error, void* context); * Interface that can handle HTTP callbacks. */ typedef struct { - on_headers on_headers_f; - on_data on_data_f; - on_metadata on_metadata_f; - on_trailers on_trailers_f; - on_error on_error_f; + envoy_on_headers_f on_headers; + envoy_on_data_f on_data; + envoy_on_metadata_f on_metadata; + envoy_on_trailers_f on_trailers; + envoy_on_complete_f on_complete; + envoy_on_error_f on_error; void* context; // Will be passed through to callbacks to provide dispatch and execution state. } envoy_observer; diff --git a/library/common/main_interface.cc b/library/common/main_interface.cc index 9ce9d7bd01..17075fbe72 100644 --- a/library/common/main_interface.cc +++ b/library/common/main_interface.cc @@ -30,7 +30,7 @@ envoy_status_t send_headers(envoy_stream_t stream_id, envoy_headers headers, boo // TODO: implement. envoy_status_t send_data(envoy_stream_t, envoy_data, bool) { return ENVOY_FAILURE; } -envoy_status_t send_metadata(envoy_stream_t, envoy_headers, bool) { return ENVOY_FAILURE; } +envoy_status_t send_metadata(envoy_stream_t, envoy_headers) { return ENVOY_FAILURE; } envoy_status_t send_trailers(envoy_stream_t, envoy_headers) { return ENVOY_FAILURE; } envoy_status_t locally_close_stream(envoy_stream_t) { return ENVOY_FAILURE; } envoy_status_t reset_stream(envoy_stream_t) { return ENVOY_FAILURE; } diff --git a/library/common/main_interface.h b/library/common/main_interface.h index 1d7afddcb9..9c8655e78c 100644 --- a/library/common/main_interface.h +++ b/library/common/main_interface.h @@ -41,10 +41,9 @@ envoy_status_t send_data(envoy_stream_t stream, envoy_data data, bool end_stream * Send metadata over an HTTP stream. This method can be invoked multiple times. * @param stream, the stream to send metadata over. * @param metadata, the metadata to send. - * @param end_stream, supplies whether this is the last data in the stream. * @return envoy_status_t, the resulting status of the operation. */ -envoy_status_t send_metadata(envoy_stream_t stream, envoy_headers metadata, bool end_stream); +envoy_status_t send_metadata(envoy_stream_t stream, envoy_headers metadata); /** * Send trailers over an open HTTP stream. This method can only be invoked once per stream. diff --git a/library/objective-c/BUILD b/library/objective-c/BUILD index 7e5b6a4055..474eff74ec 100644 --- a/library/objective-c/BUILD +++ b/library/objective-c/BUILD @@ -2,18 +2,19 @@ licenses(["notice"]) # Apache 2 exports_files([ "EnvoyEngine.h", - "EnvoyTypes.h", ]) objc_library( name = "envoy_engine_objc_lib", + # TODO(@goaway): The headers here should really be in hdrs, but this causes them to be imported + # twice due to the way they're pulled into our swift framework rule. We should fix this to + # maintain a valid objc target. srcs = [ - "EnvoyEngine.mm", + "EnvoyEngine.m", ], hdrs = [ "EnvoyEngine.h", ], - copts = ["-std=c++14"], visibility = ["//visibility:public"], deps = ["//library/common:envoy_main_interface_lib"], ) diff --git a/library/objective-c/EnvoyEngine.h b/library/objective-c/EnvoyEngine.h index 56338f5890..263a8e5c9a 100644 --- a/library/objective-c/EnvoyEngine.h +++ b/library/objective-c/EnvoyEngine.h @@ -1,80 +1,66 @@ -#import "EnvoyTypes.h" - #import NS_ASSUME_NONNULL_BEGIN -/// Protocol interface for streaming with the Envoy engine. -@protocol EnvoyEngineStreamInterface +// MARK: - Aliases -/** - Open an underlying HTTP stream. +/// A set of headers that may be passed to/from an Envoy stream. +typedef NSDictionary *> EnvoyHeaders; - @param observer the observer that will run the stream callbacks. - @return stream with a handle and success status, or a failure status. - */ -+ (EnvoyStream)startStreamWithObserver:(EnvoyObserver *)observer; +// MARK: - EnvoyObserver -/** - Send headers over the provided stream. +/// Interface that can handle HTTP callbacks. +// FIXME: can we just bridge the swift object here and/or just expose this to swift as the +// ResponseHandler? +@interface EnvoyObserver : NSObject - @param metadata Headers to send over the stream. - @param stream The stream over which to send headers. - @param close True if the stream should be closed after sending. - @return A status indicating if the action was successful. +/** + * Dispatch queue provided to handle callbacks. */ -+ (EnvoyStatus)sendHeaders:(EnvoyHeaders *)headers to:(EnvoyStream *)stream close:(BOOL)close; +@property (nonatomic, assign) dispatch_queue_t dispatchQueue; /** - Send data over the provided stream. - - @param metadata Data to send over the stream. - @param stream The stream over which to send data. - @param close True if the stream should be closed after sending. - @return A status indicating if the action was successful. + * Called when all headers get received on the async HTTP stream. + * @param headers the headers received. + * @param endStream whether the response is headers-only. */ -+ (EnvoyStatus)sendData:(NSData *)data to:(EnvoyStream *)stream close:(BOOL)close; +@property (nonatomic, strong) void (^onHeaders)(EnvoyHeaders *headers, BOOL endStream); /** - Send metadata over the provided stream. - - @param metadata Metadata to send over the stream. - @param stream The stream over which to send metadata. - @param close True if the stream should be closed after sending. - @return A status indicating if the action was successful. + * Called when a data frame gets received on the async HTTP stream. + * This callback can be invoked multiple times if the data gets streamed. + * @param data the data received. + * @param endStream whether the data is the last data frame. */ -+ (EnvoyStatus)sendMetadata:(EnvoyHeaders *)metadata to:(EnvoyStream *)stream close:(BOOL)close; +@property (nonatomic, strong) void (^onData)(NSData *data, BOOL endStream); /** - Send trailers over the provided stream. - - @param trailers Trailers to send over the stream. - @param stream The stream over which to send trailers. - @param close True if the stream should be closed after sending. - @return A status indicating if the action was successful. + * Called when all metadata gets received on the async HTTP stream. + * Note that end stream is implied when on_trailers is called. + * @param metadata the metadata received. */ -+ (EnvoyStatus)sendTrailers:(EnvoyHeaders *)trailers to:(EnvoyStream *)stream close:(BOOL)close; +@property (nonatomic, strong) void (^onMetadata)(EnvoyHeaders *metadata); /** - Cancel and end the stream. - - @param stream The stream to close. - @return The stream to close. + * Called when all trailers get received on the async HTTP stream. + * Note that end stream is implied when on_trailers is called. + * @param trailers the trailers received. */ -+ (EnvoyStatus)locallyCloseStream:(EnvoyStream *)stream; +@property (nonatomic, strong) void (^onTrailers)(EnvoyHeaders *trailers); /** - Reset the stream. - - @param stream The stream to reset. - @return A status indicating if the action was successful. + * Called when the async HTTP stream has an error. + * @param error the error received/caused by the async HTTP stream. */ -+ (EnvoyStatus)resetStream:(EnvoyStream *)stream; +@property (nonatomic, strong) void (^onError)(); + +// FIXME +@property (nonatomic, strong) void (^onCancel)(); @end /// Wrapper layer for calling into Envoy's C/++ API. -@interface EnvoyEngine : NSObject +@interface EnvoyEngine : NSObject /** Run the Envoy engine with the provided config and log level. @@ -82,7 +68,7 @@ NS_ASSUME_NONNULL_BEGIN @param config The configuration file with which to start Envoy. @return A status indicating if the action was successful. */ -+ (EnvoyStatus)runWithConfig:(NSString *)config; ++ (int)runWithConfig:(NSString *)config; /** Run the Envoy engine with the provided config and log level. @@ -91,7 +77,7 @@ NS_ASSUME_NONNULL_BEGIN @param logLevel The log level to use when starting Envoy. @return A status indicating if the action was successful. */ -+ (EnvoyStatus)runWithConfig:(NSString *)config logLevel:(NSString *)logLevel; ++ (int)runWithConfig:(NSString *)config logLevel:(NSString *)logLevel; /// Performs necessary setup after Envoy has initialized and started running. /// TODO: create a post-initialization callback from Envoy to handle this automatically. @@ -99,4 +85,51 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface EnvoyHttpStream : NSObject +/** + Open an underlying HTTP stream. + + @param observer the observer that will run the stream callbacks. + */ +- (instancetype)initWithObserver:(EnvoyObserver *)observer; + +/** + Send headers over the provided stream. + + @param headers Headers to send over the stream. + @param close True if the stream should be closed after sending. + */ +- (void)sendHeaders:(EnvoyHeaders *)headers close:(BOOL)close; + +/** + Send data over the provided stream. + + @param data Data to send over the stream. + @param close True if the stream should be closed after sending. + */ +- (void)sendData:(NSData *)data close:(BOOL)close; + +/** + Send metadata over the provided stream. + + @param metadata Metadata to send over the stream. + */ +- (void)sendMetadata:(EnvoyHeaders *)metadata; + +/** + Send trailers over the provided stream. + + @param trailers Trailers to send over the stream. + */ +- (void)sendTrailers:(EnvoyHeaders *)trailers; + +/** + Cancel the stream. This functions as an interrupt, and aborts further callbacks and handling of the + stream. + @return Success, unless the stream has already been canceled. + */ +- (int)cancel; + +@end + NS_ASSUME_NONNULL_END diff --git a/library/objective-c/EnvoyEngine.m b/library/objective-c/EnvoyEngine.m new file mode 100644 index 0000000000..ff7606ac36 --- /dev/null +++ b/library/objective-c/EnvoyEngine.m @@ -0,0 +1,268 @@ +#import "library/objective-c/EnvoyEngine.h" + +#import "library/common/main_interface.h" +#import "library/common/include/c_types.h" + +#import + +@implementation EnvoyObserver +@end + +@implementation EnvoyEngine + +#pragma mark - class methods ++ (int)runWithConfig:(NSString *)config { + return [self runWithConfig:config logLevel:@"info"]; +} + ++ (int)runWithConfig:(NSString *)config logLevel:(NSString *)logLevel { + // Envoy exceptions will only be caught here when compiled for 64-bit arches. + // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Articles/Exceptions64Bit.html + @try { + return (int)run_engine(config.UTF8String, logLevel.UTF8String); + } @catch (...) { + NSLog(@"Envoy exception caught."); + [NSNotificationCenter.defaultCenter postNotificationName:@"EnvoyException" object:self]; + return 1; + } +} + ++ (void)setupEnvoy { + setup_envoy(); +} + +@end + +#pragma mark - utility functions to move elsewhere +typedef struct { + atomic_bool *canceled; + EnvoyObserver *observer; +} ios_context; + +// static envoy_data toUnmanagedNativeData(NSData *data) { +// // TODO: implement me +// return envoy_nodata; +// } + +static void ios_free_native_data(void *context) { free(context); } + +// static void ios_release_native_data(void *context) { +// // TODO: implement me +// } + +static envoy_data toManagedNativeString(NSString *s) { + size_t length = s.length; + uint8_t *native_string = (uint8_t *)malloc(sizeof(uint8_t) * length); + memcpy(native_string, s.UTF8String, length); + envoy_data ret = {length, native_string, ios_free_native_data, native_string}; + return ret; +} + +static envoy_headers toNativeHeaders(EnvoyHeaders *headers) { + envoy_header_size_t length = 0; + for (id headerList in headers) { + length += [headerList count]; + } + envoy_header *header_array = (envoy_header *)malloc(sizeof(envoy_header) * length); + envoy_header_size_t header_index = 0; + for (id headerKey in headers) { + NSArray *headerList = headers[headerKey]; + for (id headerValue in headerList) { + envoy_header new_header = {toManagedNativeString(headerKey), + toManagedNativeString(headerValue)}; + header_array[header_index++] = new_header; + } + } + // ASSERT(header_index == length); + envoy_headers ret = {length, header_array}; + return ret; +} + +static NSData *to_ios_data(envoy_data data) { + // TODO: investigate buffer ownership + // Possibly extend/subclass NSData to call envoy_data.release on dealloc and have release drain + // the underlying Envoy buffer instance + return [NSData dataWithBytes:(void *)data.bytes length:data.length]; +} + +static EnvoyHeaders *to_ios_headers(envoy_headers headers) { + NSMutableDictionary *headerDict = [NSMutableDictionary new]; + for (envoy_header_size_t i = 0; i < headers.length; i++) { + envoy_header header = headers.headers[i]; + NSString *headerKey = [[NSString alloc] initWithBytes:header.key.bytes + length:header.key.length + encoding:NSUTF8StringEncoding]; + NSString *headerValue = [[NSString alloc] initWithBytes:header.value.bytes + length:header.value.length + encoding:NSUTF8StringEncoding]; + NSMutableArray *headerValueList = headerDict[headerKey]; + if (headerValueList == nil) { + headerValueList = [NSMutableArray new]; + headerDict[headerKey] = headerValueList; + } + [headerValueList addObject:headerValue]; + } + return headerDict; +} + +#pragma mark - c callbacks +static void ios_on_headers(envoy_headers headers, bool end_stream, void *context) { + ios_context *c = (ios_context *)context; + EnvoyObserver *observer = c->observer; + dispatch_async(observer.dispatchQueue, ^{ + if (atomic_load(c->canceled)) { + return; + } + observer.onHeaders(to_ios_headers(headers), end_stream); + }); +} + +static void ios_on_data(envoy_data data, bool end_stream, void *context) { + ios_context *c = (ios_context *)context; + EnvoyObserver *observer = c->observer; + dispatch_async(observer.dispatchQueue, ^{ + if (atomic_load(c->canceled)) { + return; + } + // TODO: retain data + observer.onData(to_ios_data(data), end_stream); + }); +} + +static void ios_on_metadata(envoy_headers metadata, void *context) { + ios_context *c = (ios_context *)context; + EnvoyObserver *observer = c->observer; + dispatch_async(observer.dispatchQueue, ^{ + if (atomic_load(c->canceled)) { + return; + } + observer.onMetadata(to_ios_headers(metadata)); + }); +} + +static void ios_on_trailers(envoy_headers trailers, void *context) { + ios_context *c = (ios_context *)context; + EnvoyObserver *observer = c->observer; + dispatch_async(observer.dispatchQueue, ^{ + if (atomic_load(c->canceled)) { + return; + } + observer.onTrailers(to_ios_headers(trailers)); + }); +} + +static void ios_on_complete(void *context) { + ios_context *c = (ios_context *)context; + EnvoyObserver *observer = c->observer; + dispatch_async(observer.dispatchQueue, ^{ + // TODO: release stream + if (atomic_load(c->canceled)) { + return; + } + }); +} + +static void ios_on_cancel(void *context) { + ios_context *c = (ios_context *)context; + EnvoyObserver *observer = c->observer; + // TODO: release stream + dispatch_async(observer.dispatchQueue, ^{ + // This call is atomically gated at the call-site and will only happen once. + observer.onCancel(); + }); +} + +static void ios_on_error(envoy_error error, void *context) { + ios_context *c = (ios_context *)context; + EnvoyObserver *observer = c->observer; + dispatch_async(observer.dispatchQueue, ^{ + // TODO: release stream + if (atomic_load(c->canceled)) { + return; + } + // FIXME transform error and pass up + observer.onError(); + }); +} + +@implementation EnvoyHttpStream { + EnvoyHttpStream *_strongSelf; + EnvoyObserver *_platformObserver; + envoy_observer *_nativeObserver; + envoy_stream_t _nativeStream; +} + +- (instancetype)initWithObserver:(EnvoyObserver *)observer { + self = [super init]; + if (!self) { + return nil; + } + + // Retain platform observer + _platformObserver = observer; + + // Create callback context + ios_context *context = (ios_context *)malloc(sizeof(ios_context)); + context->observer = observer; + context->canceled = (atomic_bool *)(malloc(sizeof(atomic_bool))); + atomic_store(context->canceled, NO); + + // Create native observer + envoy_observer *native_obs = (envoy_observer *)malloc(sizeof(envoy_observer)); + envoy_observer native_init = {ios_on_headers, ios_on_data, ios_on_trailers, ios_on_metadata, + ios_on_complete, ios_on_error, context}; + memcpy(native_obs, &native_init, sizeof(envoy_observer)); + _nativeObserver = native_obs; + + envoy_stream result = start_stream(*native_obs); + if (result.status != ENVOY_SUCCESS) { + return nil; + } + + _nativeStream = result.stream; + _strongSelf = self; + return self; +} + +- (void)dealloc { + envoy_observer *native_obs = _nativeObserver; + _nativeObserver = nil; + ios_context *context = native_obs->context; + free(context->canceled); + free(context); + free(native_obs); +} + +- (void)sendHeaders:(EnvoyHeaders *)headers close:(BOOL)close { + send_headers(_nativeStream, toNativeHeaders(headers), close); +} + +- (void)sendData:(NSData *)data close:(BOOL)close { + // TODO: implement + // send_data(_nativeStream, toNativeData(data), close); +} + +- (void)sendMetadata:(EnvoyHeaders *)metadata { + send_metadata(_nativeStream, toNativeHeaders(metadata)); +} + +- (void)sendTrailers:(EnvoyHeaders *)trailers { + send_trailers(_nativeStream, toNativeHeaders(trailers)); +} + +- (int)cancel { + ios_context *context = _nativeObserver->context; + // Step 1: atomically and synchronously prevent the execution of further callbacks other than + // on_cancel. + if (!atomic_exchange(context->canceled, YES)) { + // Step 2: directly fire the cancel callback. + ios_on_cancel(context); + // Step 3: propagate the reset into native code. + reset_stream(_nativeStream); + return 0; + } else { + return 1; + } +} + +@end diff --git a/library/objective-c/EnvoyEngine.mm b/library/objective-c/EnvoyEngine.mm deleted file mode 100644 index a9f1cb30a3..0000000000 --- a/library/objective-c/EnvoyEngine.mm +++ /dev/null @@ -1,67 +0,0 @@ -#import "library/objective-c/EnvoyEngine.h" - -#import "library/common/include/c_types.h" -#import "library/common/main_interface.h" - -@implementation EnvoyEngine - -#pragma mark - class methods -+ (EnvoyStatus)runWithConfig:(NSString *)config { - return [self runWithConfig:config logLevel:@"info"]; -} - -+ (EnvoyStatus)runWithConfig:(NSString *)config logLevel:(NSString *)logLevel { - try { - return (EnvoyStatus)run_engine(config.UTF8String, logLevel.UTF8String); - } catch (NSException *e) { - NSLog(@"Envoy exception: %@", e); - NSDictionary *userInfo = @{@"exception" : e}; - [NSNotificationCenter.defaultCenter postNotificationName:@"EnvoyException" - object:self - userInfo:userInfo]; - return EnvoyStatusFailure; - } -} - -+ (EnvoyStream)startStreamWithObserver:(EnvoyObserver *)observer { - NSLog(@"%@ not implemented, returning failure", NSStringFromSelector((SEL) __func__)); - EnvoyStream stream; - stream.status = EnvoyStatusFailure; - return stream; -} - -+ (EnvoyStatus)sendHeaders:(EnvoyHeaders *)headers to:(EnvoyStream *)stream close:(BOOL)close { - NSLog(@"%@ not implemented, returning failure", NSStringFromSelector((SEL) __func__)); - return EnvoyStatusFailure; -} - -+ (EnvoyStatus)sendData:(NSData *)data to:(EnvoyStream *)stream close:(BOOL)close { - NSLog(@"%@ not implemented, returning failure", NSStringFromSelector((SEL) __func__)); - return EnvoyStatusFailure; -} - -+ (EnvoyStatus)sendMetadata:(EnvoyHeaders *)metadata to:(EnvoyStream *)stream close:(BOOL)close { - NSLog(@"%@ not implemented, returning failure", NSStringFromSelector((SEL) __func__)); - return EnvoyStatusFailure; -} - -+ (EnvoyStatus)sendTrailers:(EnvoyHeaders *)trailers to:(EnvoyStream *)stream close:(BOOL)close { - NSLog(@"%@ not implemented, returning failure", NSStringFromSelector((SEL) __func__)); - return EnvoyStatusFailure; -} - -+ (EnvoyStatus)locallyCloseStream:(EnvoyStream *)stream { - NSLog(@"%@ not implemented, returning failure", NSStringFromSelector((SEL) __func__)); - return EnvoyStatusFailure; -} - -+ (EnvoyStatus)resetStream:(EnvoyStream *)stream { - NSLog(@"%@ not implemented, returning failure", NSStringFromSelector((SEL) __func__)); - return EnvoyStatusFailure; -} - -+ (void)setupEnvoy { - setup_envoy(); -} - -@end diff --git a/library/objective-c/EnvoyTypes.h b/library/objective-c/EnvoyTypes.h deleted file mode 100644 index e1fcaca7c0..0000000000 --- a/library/objective-c/EnvoyTypes.h +++ /dev/null @@ -1,96 +0,0 @@ -#import - -NS_ASSUME_NONNULL_BEGIN - -// MARK: - Aliases - -/// Handle to an outstanding Envoy HTTP stream. Valid only for the duration of the stream and not -/// intended for any external interpretation or use. -typedef UInt64 EnvoyStreamID; - -/// A set of headers that may be passed to/from an Envoy stream. -typedef NSArray *> EnvoyHeaders; - -// MARK: - EnvoyEngineErrorCode - -/// Error code associated with terminal status of a HTTP stream. -typedef NS_ENUM(NSUInteger, EnvoyEngineErrorCode) { - EnvoyEngineErrorCodeStreamReset = 0, -}; - -// MARK: - EnvoyEngineError - -/// Error structure. -@interface EnvoyEngineError : NSError - -/// Message with additional details on the error. -@property (nonatomic, copy) NSString *message; - -/// Error code representing the Envoy error. -@property (nonatomic, assign) EnvoyEngineErrorCode errorCode; - -@end - -// MARK: - EnvoyStatus - -/// Result codes returned by all calls made to this interface. -typedef NS_CLOSED_ENUM(NSUInteger, EnvoyStatus){ - EnvoyStatusSuccess = 0, - EnvoyStatusFailure = 1, -}; - -// MARK: - EnvoyStream - -/// Holds data about an HTTP stream. -typedef struct { - /// Status of the Envoy HTTP stream. Note that the stream might have failed inline. - /// Thus the status should be checked before pursuing other operations on the stream. - EnvoyStatus status; - - /// Handle to the Envoy HTTP stream. - EnvoyStreamID streamID; -} EnvoyStream; - -// MARK: - EnvoyObserver - -/// Interface that can handle HTTP callbacks. -@interface EnvoyObserver : NSObject - -/** - * Called when all headers get received on the async HTTP stream. - * @param headers the headers received. - * @param endStream whether the response is headers-only. - */ -@property (nonatomic, strong) void (^onHeaders)(EnvoyHeaders *headers, BOOL endStream); - -/** - * Called when a data frame gets received on the async HTTP stream. - * This callback can be invoked multiple times if the data gets streamed. - * @param data the data received. - * @param endStream whether the data is the last data frame. - */ -@property (nonatomic, strong) void (^onData)(NSData *data, BOOL endStream); - -/** - * Called when all metadata gets received on the async HTTP stream. - * Note that end stream is implied when on_trailers is called. - * @param metadata the metadata received. - */ -@property (nonatomic, strong) void (^onMetadata)(EnvoyHeaders *metadata); - -/** - * Called when all trailers get received on the async HTTP stream. - * Note that end stream is implied when on_trailers is called. - * @param trailers the trailers received. - */ -@property (nonatomic, strong) void (^onTrailers)(EnvoyHeaders *trailers); - -/** - * Called when the async HTTP stream has an error. - * @param error the error received/caused by the async HTTP stream. - */ -@property (nonatomic, strong) void (^onError)(EnvoyEngineError *error); - -@end - -NS_ASSUME_NONNULL_END diff --git a/library/swift/src/BUILD b/library/swift/src/BUILD index f31c3c8c40..60b7945fec 100644 --- a/library/swift/src/BUILD +++ b/library/swift/src/BUILD @@ -19,7 +19,9 @@ swift_static_framework( "StreamEmitter.swift", ], module_name = "Envoy", - objc_includes = ["//library/objective-c:EnvoyEngine.h"], + objc_includes = [ + "//library/objective-c:EnvoyEngine.h", + ], visibility = ["//visibility:public"], deps = ["//library/objective-c:envoy_engine_objc_lib"], ) diff --git a/library/swift/src/ResponseHandler.swift b/library/swift/src/ResponseHandler.swift index bd3a8a01d7..28380827fc 100644 --- a/library/swift/src/ResponseHandler.swift +++ b/library/swift/src/ResponseHandler.swift @@ -3,6 +3,9 @@ import Foundation /// Callback interface for receiving stream events. @objc public protocol ResponseHandler { + /// Dispatch queue upon which callbacks will be called. + var dispatchQueue: DispatchQueue { get } + /// Called when response headers are received by the stream. /// /// - parameter headers: The headers of the response. diff --git a/test/common/http/header_utility_test.cc b/test/common/http/header_utility_test.cc index 8cb58959cb..da539ebfb6 100644 --- a/test/common/http/header_utility_test.cc +++ b/test/common/http/header_utility_test.cc @@ -8,7 +8,7 @@ namespace Envoy { namespace Http { envoy_data envoyString(std::string& s) { - return {s.size(), reinterpret_cast(s.c_str())}; + return {s.size(), reinterpret_cast(s.c_str()), envoy_noop_release, nullptr}; } TEST(HeaderDataConstructorTest, FromCToCppEmpty) { @@ -35,13 +35,13 @@ TEST(HeaderDataConstructorTest, FromCToCpp) { }; } - envoy_headers c_headers = {headers.size(), header_array}; + envoy_headers c_headers = {static_cast(headers.size()), header_array}; HeaderMapPtr cpp_headers = Utility::transformHeaders(c_headers); ASSERT_EQ(cpp_headers->size(), c_headers.length); - for (uint64_t i = 0; i < c_headers.length; i++) { + for (envoy_header_size_t i = 0; i < c_headers.length; i++) { auto expected_key = LowerCaseString(Utility::convertToString(c_headers.headers[i].key)); auto expected_value = Utility::convertToString(c_headers.headers[i].value); @@ -68,9 +68,9 @@ TEST(HeaderDataConstructorTest, FromCppToC) { envoy_headers c_headers = Utility::transformHeaders(std::move(cpp_headers)); - ASSERT_EQ(c_headers.length, cpp_headers.size()); + ASSERT_EQ(c_headers.length, static_cast(cpp_headers.size())); - for (uint64_t i = 0; i < c_headers.length; i++) { + for (envoy_header_size_t i = 0; i < c_headers.length; i++) { auto actual_key = LowerCaseString(Utility::convertToString(c_headers.headers[i].key)); auto actual_value = Utility::convertToString(c_headers.headers[i].value);