| #include "call.h" |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "php.h" |
| #include "php_ini.h" |
| #include "ext/standard/info.h" |
| #include "ext/spl/spl_exceptions.h" |
| #include "php_grpc.h" |
| |
| #include "zend_exceptions.h" |
| #include "zend_hash.h" |
| |
| #include <stdbool.h> |
| |
| #include "grpc/support/log.h" |
| #include "grpc/grpc.h" |
| |
| #include "timeval.h" |
| #include "channel.h" |
| #include "completion_queue.h" |
| #include "byte_buffer.h" |
| |
| /* Frees and destroys an instance of wrapped_grpc_call */ |
| void free_wrapped_grpc_call(void *object TSRMLS_DC) { |
| wrapped_grpc_call *call = (wrapped_grpc_call *)object; |
| if (call->owned && call->wrapped != NULL) { |
| grpc_call_destroy(call->wrapped); |
| } |
| efree(call); |
| } |
| |
| /* Initializes an instance of wrapped_grpc_call to be associated with an object |
| * of a class specified by class_type */ |
| zend_object_value create_wrapped_grpc_call(zend_class_entry *class_type |
| TSRMLS_DC) { |
| zend_object_value retval; |
| wrapped_grpc_call *intern; |
| |
| intern = (wrapped_grpc_call *)emalloc(sizeof(wrapped_grpc_call)); |
| memset(intern, 0, sizeof(wrapped_grpc_call)); |
| |
| zend_object_std_init(&intern->std, class_type TSRMLS_CC); |
| object_properties_init(&intern->std, class_type); |
| retval.handle = zend_objects_store_put( |
| intern, (zend_objects_store_dtor_t)zend_objects_destroy_object, |
| free_wrapped_grpc_call, NULL TSRMLS_CC); |
| retval.handlers = zend_get_std_object_handlers(); |
| return retval; |
| } |
| |
| /* Wraps a grpc_call struct in a PHP object. Owned indicates whether the struct |
| should be destroyed at the end of the object's lifecycle */ |
| zval *grpc_php_wrap_call(grpc_call *wrapped, bool owned) { |
| zval *call_object; |
| MAKE_STD_ZVAL(call_object); |
| object_init_ex(call_object, grpc_ce_call); |
| wrapped_grpc_call *call = |
| (wrapped_grpc_call *)zend_object_store_get_object(call_object TSRMLS_CC); |
| call->wrapped = wrapped; |
| return call_object; |
| } |
| |
| zval *grpc_call_create_metadata_array(int count, grpc_metadata *elements) { |
| int i; |
| zval *array; |
| zval **data = NULL; |
| HashTable *array_hash; |
| zval *inner_array; |
| char *str_key; |
| char *str_val; |
| size_t key_len; |
| MAKE_STD_ZVAL(array); |
| array_init(array); |
| array_hash = Z_ARRVAL_P(array); |
| grpc_metadata *elem; |
| for (i = 0; i < count; i++) { |
| elem = &elements[i]; |
| key_len = strlen(elem->key); |
| str_key = ecalloc(key_len + 1, sizeof(char)); |
| memcpy(str_key, elem->key, key_len); |
| str_val = ecalloc(elem->value_length + 1, sizeof(char)); |
| memcpy(str_val, elem->value, elem->value_length); |
| if (zend_hash_find(array_hash, str_key, key_len, (void **)data) == |
| SUCCESS) { |
| switch (Z_TYPE_P(*data)) { |
| case IS_STRING: |
| MAKE_STD_ZVAL(inner_array); |
| array_init(inner_array); |
| add_next_index_zval(inner_array, *data); |
| add_assoc_zval(array, str_key, inner_array); |
| break; |
| case IS_ARRAY: |
| inner_array = *data; |
| break; |
| default: |
| zend_throw_exception(zend_exception_get_default(), |
| "Metadata hash somehow contains wrong types.", |
| 1 TSRMLS_CC); |
| efree(str_key); |
| efree(str_val); |
| return NULL; |
| } |
| add_next_index_stringl(inner_array, str_val, elem->value_length, false); |
| } else { |
| add_assoc_stringl(array, str_key, str_val, elem->value_length, false); |
| } |
| } |
| return array; |
| } |
| |
| int php_grpc_call_add_metadata_array_walk(void *elem TSRMLS_DC, int num_args, |
| va_list args, |
| zend_hash_key *hash_key) { |
| grpc_call_error error_code; |
| zval **data = (zval **)elem; |
| grpc_metadata metadata; |
| grpc_call *call = va_arg(args, grpc_call *); |
| gpr_uint32 flags = va_arg(args, gpr_uint32); |
| const char *key; |
| HashTable *inner_hash; |
| /* We assume that either two args were passed, and we are in the recursive |
| case (and the second argument is the key), or one arg was passed and |
| hash_key is the string key. */ |
| if (num_args > 2) { |
| key = va_arg(args, const char *); |
| } else { |
| /* TODO(mlumish): If possible, check that hash_key is a string */ |
| key = hash_key->arKey; |
| } |
| switch (Z_TYPE_P(*data)) { |
| case IS_STRING: |
| metadata.key = (char *)key; |
| metadata.value = Z_STRVAL_P(*data); |
| metadata.value_length = Z_STRLEN_P(*data); |
| error_code = grpc_call_add_metadata(call, &metadata, 0u); |
| MAYBE_THROW_CALL_ERROR(add_metadata, error_code); |
| break; |
| case IS_ARRAY: |
| inner_hash = Z_ARRVAL_P(*data); |
| zend_hash_apply_with_arguments(inner_hash TSRMLS_CC, |
| php_grpc_call_add_metadata_array_walk, 3, |
| call, flags, key); |
| break; |
| default: |
| zend_throw_exception(zend_exception_get_default(), |
| "Metadata hash somehow contains wrong types.", |
| 1 TSRMLS_CC); |
| } |
| return ZEND_HASH_APPLY_KEEP; |
| } |
| |
| /** |
| * Constructs a new instance of the Call class. |
| * @param Channel $channel The channel to associate the call with. Must not be |
| * closed. |
| * @param string $method The method to call |
| * @param Timeval $absolute_deadline The deadline for completing the call |
| */ |
| PHP_METHOD(Call, __construct) { |
| wrapped_grpc_call *call = |
| (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC); |
| zval *channel_obj; |
| char *method; |
| int method_len; |
| zval *deadline_obj; |
| /* "OsO" == 1 Object, 1 string, 1 Object */ |
| if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "OsO", &channel_obj, |
| grpc_ce_channel, &method, &method_len, |
| &deadline_obj, grpc_ce_timeval) == FAILURE) { |
| zend_throw_exception(spl_ce_InvalidArgumentException, |
| "Call expects a Channel, a String, and a Timeval", |
| 1 TSRMLS_CC); |
| return; |
| } |
| wrapped_grpc_channel *channel = |
| (wrapped_grpc_channel *)zend_object_store_get_object( |
| channel_obj TSRMLS_CC); |
| if (channel->wrapped == NULL) { |
| zend_throw_exception(spl_ce_InvalidArgumentException, |
| "Call cannot be constructed from a closed Channel", |
| 1 TSRMLS_CC); |
| return; |
| } |
| add_property_zval(getThis(), "channel", channel_obj); |
| wrapped_grpc_timeval *deadline = |
| (wrapped_grpc_timeval *)zend_object_store_get_object( |
| deadline_obj TSRMLS_CC); |
| call->wrapped = grpc_channel_create_call(channel->wrapped, method, |
| channel->target, deadline->wrapped); |
| } |
| |
| /** |
| * Add metadata to the call. All array keys must be strings. If the value is a |
| * string, it is added as a key/value pair. If it is an array, each value is |
| * added paired with the same string |
| * @param array $metadata The metadata to add |
| * @param long $flags A bitwise combination of the Grpc\WRITE_* constants |
| * (optional) |
| * @return Void |
| */ |
| PHP_METHOD(Call, add_metadata) { |
| wrapped_grpc_call *call = |
| (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC); |
| zval *array; |
| HashTable *array_hash; |
| long flags = 0; |
| /* "a|l" == 1 array, 1 optional long */ |
| if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a|l", &array, &flags) == |
| FAILURE) { |
| zend_throw_exception(spl_ce_InvalidArgumentException, |
| "add_metadata expects an array and an optional long", |
| 1 TSRMLS_CC); |
| return; |
| } |
| array_hash = Z_ARRVAL_P(array); |
| zend_hash_apply_with_arguments(array_hash TSRMLS_CC, |
| php_grpc_call_add_metadata_array_walk, 2, |
| call->wrapped, (gpr_uint32)flags); |
| } |
| |
| /** |
| * Invoke the RPC. Starts sending metadata and request headers over the wire |
| * @param CompletionQueue $queue The completion queue to use with this call |
| * @param long $invoke_accepted_tag The tag to associate with this invocation |
| * @param long $metadata_tag The tag to associate with returned metadata |
| * @param long $finished_tag The tag to associate with the finished event |
| * @param long $flags A bitwise combination of the Grpc\WRITE_* constants |
| * (optional) |
| * @return Void |
| */ |
| PHP_METHOD(Call, start_invoke) { |
| grpc_call_error error_code; |
| long tag1; |
| long tag2; |
| long tag3; |
| zval *queue_obj; |
| long flags = 0; |
| /* "Olll|l" == 1 Object, 3 mandatory longs, 1 optional long */ |
| if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Olll|l", &queue_obj, |
| grpc_ce_completion_queue, &tag1, &tag2, &tag3, |
| &flags) == FAILURE) { |
| zend_throw_exception( |
| spl_ce_InvalidArgumentException, |
| "start_invoke needs a CompletionQueue, 3 longs, and an optional long", |
| 1 TSRMLS_CC); |
| return; |
| } |
| add_property_zval(getThis(), "completion_queue", queue_obj); |
| wrapped_grpc_call *call = |
| (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC); |
| wrapped_grpc_completion_queue *queue = |
| (wrapped_grpc_completion_queue *)zend_object_store_get_object( |
| queue_obj TSRMLS_CC); |
| error_code = |
| grpc_call_start_invoke(call->wrapped, queue->wrapped, (void *)tag1, |
| (void *)tag2, (void *)tag3, (gpr_uint32)flags); |
| MAYBE_THROW_CALL_ERROR(start_invoke, error_code); |
| } |
| |
| /** |
| * Accept an incoming RPC, binding a completion queue to it. To be called after |
| * adding metadata to the call, but before sending messages. Can only be called |
| * on the server |
| * @param CompletionQueue $queue The completion queue to use with this call |
| * @param long $finished_tag The tag to associate with the finished event |
| * @param long $flags A bitwise combination of the Grpc\WRITE_* constants |
| * (optional) |
| * @return Void |
| */ |
| PHP_METHOD(Call, server_accept) { |
| long tag; |
| zval *queue_obj; |
| grpc_call_error error_code; |
| /* "Ol|l" == 1 Object, 1 long */ |
| if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Ol", &queue_obj, |
| grpc_ce_completion_queue, &tag) == FAILURE) { |
| zend_throw_exception( |
| spl_ce_InvalidArgumentException, |
| "server_accept expects a CompletionQueue, a long, and an optional long", |
| 1 TSRMLS_CC); |
| return; |
| } |
| add_property_zval(getThis(), "completion_queue", queue_obj); |
| wrapped_grpc_call *call = |
| (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC); |
| wrapped_grpc_completion_queue *queue = |
| (wrapped_grpc_completion_queue *)zend_object_store_get_object( |
| queue_obj TSRMLS_CC); |
| error_code = |
| grpc_call_server_accept(call->wrapped, queue->wrapped, (void *)tag); |
| MAYBE_THROW_CALL_ERROR(server_accept, error_code); |
| } |
| |
| PHP_METHOD(Call, server_end_initial_metadata) { |
| grpc_call_error error_code; |
| long flags = 0; |
| /* "|l" == 1 optional long */ |
| if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags) == |
| FAILURE) { |
| zend_throw_exception(spl_ce_InvalidArgumentException, |
| "server_end_initial_metadata expects an optional long", |
| 1 TSRMLS_CC); |
| } |
| wrapped_grpc_call *call = |
| (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC); |
| error_code = grpc_call_server_end_initial_metadata(call->wrapped, flags); |
| MAYBE_THROW_CALL_ERROR(server_end_initial_metadata, error_code); |
| } |
| |
| /** |
| * Called by clients to cancel an RPC on the server. |
| * @return Void |
| */ |
| PHP_METHOD(Call, cancel) { |
| wrapped_grpc_call *call = |
| (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC); |
| grpc_call_error error_code = grpc_call_cancel(call->wrapped); |
| MAYBE_THROW_CALL_ERROR(cancel, error_code); |
| } |
| |
| /** |
| * Queue a byte buffer for writing |
| * @param string $buffer The buffer to queue for writing |
| * @param long $tag The tag to associate with this write |
| * @param long $flags A bitwise combination of the Grpc\WRITE_* constants |
| * (optional) |
| * @return Void |
| */ |
| PHP_METHOD(Call, start_write) { |
| grpc_call_error error_code; |
| wrapped_grpc_call *call = |
| (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC); |
| char *buffer; |
| int buffer_len; |
| long tag; |
| long flags = 0; |
| /* "Ol|l" == 1 Object, 1 mandatory long, 1 optional long */ |
| if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl|l", &buffer, |
| &buffer_len, &tag, &flags) == FAILURE) { |
| zend_throw_exception(spl_ce_InvalidArgumentException, |
| "start_write expects a string and an optional long", |
| 1 TSRMLS_CC); |
| return; |
| } |
| error_code = grpc_call_start_write(call->wrapped, |
| string_to_byte_buffer(buffer, buffer_len), |
| (void *)tag, (gpr_uint32)flags); |
| MAYBE_THROW_CALL_ERROR(start_write, error_code); |
| } |
| |
| /** |
| * Queue a status for writing |
| * @param long $status_code The status code to send |
| * @param string $status_details The status details to send |
| * @param long $tag The tag to associate with this status |
| * @return Void |
| */ |
| PHP_METHOD(Call, start_write_status) { |
| grpc_call_error error_code; |
| wrapped_grpc_call *call = |
| (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC); |
| long status_code; |
| int status_details_length; |
| long tag; |
| char *status_details; |
| /* "lsl" == 1 long, 1 string, 1 long */ |
| if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lsl", &status_code, |
| &status_details, &status_details_length, |
| &tag) == FAILURE) { |
| zend_throw_exception( |
| spl_ce_InvalidArgumentException, |
| "start_write_status expects a long, a string, and a long", 1 TSRMLS_CC); |
| return; |
| } |
| error_code = |
| grpc_call_start_write_status(call->wrapped, (grpc_status_code)status_code, |
| status_details, (void *)tag); |
| MAYBE_THROW_CALL_ERROR(start_write_status, error_code); |
| } |
| |
| /** |
| * Indicate that there are no more messages to send |
| * @return Void |
| */ |
| PHP_METHOD(Call, writes_done) { |
| grpc_call_error error_code; |
| wrapped_grpc_call *call = |
| (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC); |
| long tag; |
| /* "l" == 1 long */ |
| if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &tag) == FAILURE) { |
| zend_throw_exception(spl_ce_InvalidArgumentException, |
| "writes_done expects a long", 1 TSRMLS_CC); |
| return; |
| } |
| error_code = grpc_call_writes_done(call->wrapped, (void *)tag); |
| MAYBE_THROW_CALL_ERROR(writes_done, error_code); |
| } |
| |
| /** |
| * Initiate a read on a call. Output event contains a byte buffer with the |
| * result of the read |
| * @param long $tag The tag to associate with this read |
| * @return Void |
| */ |
| PHP_METHOD(Call, start_read) { |
| grpc_call_error error_code; |
| wrapped_grpc_call *call = |
| (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC); |
| long tag; |
| /* "l" == 1 long */ |
| if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &tag) == FAILURE) { |
| zend_throw_exception(spl_ce_InvalidArgumentException, |
| "start_read expects a long", 1 TSRMLS_CC); |
| return; |
| } |
| error_code = grpc_call_start_read(call->wrapped, (void *)tag); |
| MAYBE_THROW_CALL_ERROR(start_read, error_code); |
| } |
| |
| static zend_function_entry call_methods[] = { |
| PHP_ME(Call, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR) |
| PHP_ME(Call, server_accept, NULL, ZEND_ACC_PUBLIC) |
| PHP_ME(Call, server_end_initial_metadata, NULL, ZEND_ACC_PUBLIC) |
| PHP_ME(Call, add_metadata, NULL, ZEND_ACC_PUBLIC) |
| PHP_ME(Call, cancel, NULL, ZEND_ACC_PUBLIC) |
| PHP_ME(Call, start_invoke, NULL, ZEND_ACC_PUBLIC) |
| PHP_ME(Call, start_read, NULL, ZEND_ACC_PUBLIC) |
| PHP_ME(Call, start_write, NULL, ZEND_ACC_PUBLIC) |
| PHP_ME(Call, start_write_status, NULL, ZEND_ACC_PUBLIC) |
| PHP_ME(Call, writes_done, NULL, ZEND_ACC_PUBLIC) PHP_FE_END}; |
| |
| void grpc_init_call(TSRMLS_D) { |
| zend_class_entry ce; |
| INIT_CLASS_ENTRY(ce, "Grpc\\Call", call_methods); |
| ce.create_object = create_wrapped_grpc_call; |
| grpc_ce_call = zend_register_internal_class(&ce TSRMLS_CC); |
| } |