diff options
34 files changed, 1700 insertions, 373 deletions
diff --git a/examples/cpp/helloworld/cocoapods/HelloWorldCpp/ViewController.mm b/examples/cpp/helloworld/cocoapods/HelloWorldCpp/ViewController.mm index 6ff1ca593d..18a0972e32 100644 --- a/examples/cpp/helloworld/cocoapods/HelloWorldCpp/ViewController.mm +++ b/examples/cpp/helloworld/cocoapods/HelloWorldCpp/ViewController.mm @@ -17,9 +17,9 @@ */ #import "ViewController.h" -#import <grpc++/grpc++.h> -#include <grpc++/generic/generic_stub.h> -#include <grpc++/generic/async_generic_service.h> +#import <grpcpp/grpcpp.h> +#include <grpcpp/generic/generic_stub.h> +#include <grpcpp/generic/async_generic_service.h> static void* tag(int i) { return (void*)(intptr_t)i; } diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec index 4c24ec2ff2..0d2aa4c456 100644 --- a/gRPC-C++.podspec +++ b/gRPC-C++.podspec @@ -24,7 +24,7 @@ Pod::Spec.new do |s| s.name = 'gRPC-C++' # TODO (mxyan): use version that match gRPC version when pod is stabilized # version = '1.10.0-dev' - version = '0.0.1' + version = '0.0.2' s.version = version s.summary = 'gRPC C++ library' s.homepage = 'https://grpc.io' @@ -42,8 +42,14 @@ Pod::Spec.new do |s| s.osx.deployment_target = '10.9' s.requires_arc = false - # Add include prefix `grpc++` (i.e. `#include <grpc++/xxx.h>`). - s.header_dir = 'grpc++' + name = 'grpcpp' + # Use `grpcpp` as framework name so that `#include <grpcpp/xxx.h>` works when built as + # framework. + s.module_name = name + + # Add include prefix `grpcpp` so that `#include <grpcpp/xxx.h>` works when built as static + # library. + s.header_dir = name s.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '"$(inherited)" "$(PODS_TARGET_SRCROOT)/include"', @@ -63,55 +69,12 @@ Pod::Spec.new do |s| s.default_subspecs = 'Interface', 'Implementation' + s.header_mappings_dir = 'include/grpcpp' + s.subspec 'Interface' do |ss| - ss.header_mappings_dir = 'include/grpc++' + ss.header_mappings_dir = 'include/grpcpp' - ss.source_files = 'include/grpc++/alarm.h', - 'include/grpc++/channel.h', - 'include/grpc++/client_context.h', - 'include/grpc++/completion_queue.h', - 'include/grpc++/create_channel.h', - 'include/grpc++/create_channel_posix.h', - 'include/grpc++/ext/health_check_service_server_builder_option.h', - 'include/grpc++/generic/async_generic_service.h', - 'include/grpc++/generic/generic_stub.h', - 'include/grpc++/grpc++.h', - 'include/grpc++/health_check_service_interface.h', - 'include/grpc++/impl/call.h', - 'include/grpc++/impl/channel_argument_option.h', - 'include/grpc++/impl/client_unary_call.h', - 'include/grpc++/impl/codegen/core_codegen.h', - 'include/grpc++/impl/grpc_library.h', - 'include/grpc++/impl/method_handler_impl.h', - 'include/grpc++/impl/rpc_method.h', - 'include/grpc++/impl/rpc_service_method.h', - 'include/grpc++/impl/serialization_traits.h', - 'include/grpc++/impl/server_builder_option.h', - 'include/grpc++/impl/server_builder_plugin.h', - 'include/grpc++/impl/server_initializer.h', - 'include/grpc++/impl/service_type.h', - 'include/grpc++/resource_quota.h', - 'include/grpc++/security/auth_context.h', - 'include/grpc++/security/auth_metadata_processor.h', - 'include/grpc++/security/credentials.h', - 'include/grpc++/security/server_credentials.h', - 'include/grpc++/server.h', - 'include/grpc++/server_builder.h', - 'include/grpc++/server_context.h', - 'include/grpc++/server_posix.h', - 'include/grpc++/support/async_stream.h', - 'include/grpc++/support/async_unary_call.h', - 'include/grpc++/support/byte_buffer.h', - 'include/grpc++/support/channel_arguments.h', - 'include/grpc++/support/config.h', - 'include/grpc++/support/slice.h', - 'include/grpc++/support/status.h', - 'include/grpc++/support/status_code_enum.h', - 'include/grpc++/support/string_ref.h', - 'include/grpc++/support/stub_options.h', - 'include/grpc++/support/sync_stream.h', - 'include/grpc++/support/time.h', - 'include/grpcpp/alarm.h', + ss.source_files = 'include/grpcpp/alarm.h', 'include/grpcpp/channel.h', 'include/grpcpp/client_context.h', 'include/grpcpp/completion_queue.h', @@ -156,36 +119,6 @@ Pod::Spec.new do |s| 'include/grpcpp/support/stub_options.h', 'include/grpcpp/support/sync_stream.h', 'include/grpcpp/support/time.h', - 'include/grpc++/impl/codegen/async_stream.h', - 'include/grpc++/impl/codegen/async_unary_call.h', - 'include/grpc++/impl/codegen/byte_buffer.h', - 'include/grpc++/impl/codegen/call.h', - 'include/grpc++/impl/codegen/call_hook.h', - 'include/grpc++/impl/codegen/channel_interface.h', - 'include/grpc++/impl/codegen/client_context.h', - 'include/grpc++/impl/codegen/client_unary_call.h', - 'include/grpc++/impl/codegen/completion_queue.h', - 'include/grpc++/impl/codegen/completion_queue_tag.h', - 'include/grpc++/impl/codegen/config.h', - 'include/grpc++/impl/codegen/core_codegen_interface.h', - 'include/grpc++/impl/codegen/create_auth_context.h', - 'include/grpc++/impl/codegen/grpc_library.h', - 'include/grpc++/impl/codegen/metadata_map.h', - 'include/grpc++/impl/codegen/method_handler_impl.h', - 'include/grpc++/impl/codegen/rpc_method.h', - 'include/grpc++/impl/codegen/rpc_service_method.h', - 'include/grpc++/impl/codegen/security/auth_context.h', - 'include/grpc++/impl/codegen/serialization_traits.h', - 'include/grpc++/impl/codegen/server_context.h', - 'include/grpc++/impl/codegen/server_interface.h', - 'include/grpc++/impl/codegen/service_type.h', - 'include/grpc++/impl/codegen/slice.h', - 'include/grpc++/impl/codegen/status.h', - 'include/grpc++/impl/codegen/status_code_enum.h', - 'include/grpc++/impl/codegen/string_ref.h', - 'include/grpc++/impl/codegen/stub_options.h', - 'include/grpc++/impl/codegen/sync_stream.h', - 'include/grpc++/impl/codegen/time.h', 'include/grpcpp/impl/codegen/async_stream.h', 'include/grpcpp/impl/codegen/async_unary_call.h', 'include/grpcpp/impl/codegen/byte_buffer.h', @@ -224,8 +157,7 @@ Pod::Spec.new do |s| ss.dependency 'gRPC-Core', grpc_version ss.dependency 'nanopb', '~> 0.3' - ss.source_files = 'include/grpc++/impl/codegen/core_codegen.h', - 'include/grpcpp/impl/codegen/core_codegen.h', + ss.source_files = 'include/grpcpp/impl/codegen/core_codegen.h', 'src/cpp/client/secure_credentials.h', 'src/cpp/common/secure_auth_context.h', 'src/cpp/server/secure_server_credentials.h', @@ -519,8 +451,7 @@ Pod::Spec.new do |s| 'src/core/ext/filters/workarounds/workaround_cronet_compression_filter.h', 'src/core/ext/filters/workarounds/workaround_utils.h' - ss.private_header_files = 'include/grpc++/impl/codegen/core_codegen.h', - 'include/grpcpp/impl/codegen/core_codegen.h', + ss.private_header_files = 'include/grpcpp/impl/codegen/core_codegen.h', 'src/cpp/client/secure_credentials.h', 'src/cpp/common/secure_auth_context.h', 'src/cpp/server/secure_server_credentials.h', @@ -684,113 +615,6 @@ Pod::Spec.new do |s| 'src/core/ext/transport/inproc/inproc_transport.h' end - s.subspec 'Tests' do |ss| - ss.header_mappings_dir = '.' - - ss.dependency "#{s.name}/Interface", version - ss.dependency "#{s.name}/Implementation", version - ss.dependency "gRPC-Core/Tests", grpc_version - - ss.source_files = 'test/cpp/util/create_test_channel.cc', - 'test/cpp/util/string_ref_helper.cc', - 'test/cpp/util/subprocess.cc', - 'test/cpp/util/test_credentials_provider.cc', - 'test/cpp/util/create_test_channel.h', - 'test/cpp/util/string_ref_helper.h', - 'test/cpp/util/subprocess.h', - 'test/cpp/util/test_credentials_provider.h', - 'test/core/util/test_config.h', - 'test/core/end2end/data/ssl_test_data.h', - 'test/core/security/oauth2_utils.h', - 'src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h', - 'test/core/end2end/cq_verifier.h', - 'test/core/end2end/fixtures/http_proxy_fixture.h', - 'test/core/end2end/fixtures/proxy.h', - 'test/core/iomgr/endpoint_tests.h', - 'test/core/util/debugger_macros.h', - 'test/core/util/grpc_profiler.h', - 'test/core/util/histogram.h', - 'test/core/util/memory_counters.h', - 'test/core/util/mock_endpoint.h', - 'test/core/util/parse_hexstring.h', - 'test/core/util/passthru_endpoint.h', - 'test/core/util/port.h', - 'test/core/util/port_server_client.h', - 'test/core/util/slice_splitter.h', - 'test/core/util/subprocess.h', - 'test/core/util/tracer_util.h', - 'test/core/util/trickle_endpoint.h', - 'test/core/util/cmdline.h', - 'src/core/lib/gpr/arena.h', - 'src/core/lib/gpr/env.h', - 'src/core/lib/gpr/fork.h', - 'src/core/lib/gpr/host_port.h', - 'src/core/lib/gpr/mpscq.h', - 'src/core/lib/gpr/murmur_hash.h', - 'src/core/lib/gpr/spinlock.h', - 'src/core/lib/gpr/string.h', - 'src/core/lib/gpr/string_windows.h', - 'src/core/lib/gpr/time_precise.h', - 'src/core/lib/gpr/tls.h', - 'src/core/lib/gpr/tls_gcc.h', - 'src/core/lib/gpr/tls_msvc.h', - 'src/core/lib/gpr/tls_pthread.h', - 'src/core/lib/gpr/tmpfile.h', - 'src/core/lib/gpr/useful.h', - 'src/core/lib/gprpp/abstract.h', - 'src/core/lib/gprpp/atomic.h', - 'src/core/lib/gprpp/atomic_with_atm.h', - 'src/core/lib/gprpp/atomic_with_std.h', - 'src/core/lib/gprpp/manual_constructor.h', - 'src/core/lib/gprpp/memory.h', - 'src/core/lib/gprpp/thd.h', - 'src/core/lib/profiling/timers.h', - 'src/core/ext/filters/client_channel/backup_poller.h', - 'src/core/ext/filters/client_channel/client_channel.h', - 'src/core/ext/filters/client_channel/client_channel_factory.h', - 'src/core/ext/filters/client_channel/connector.h', - 'src/core/ext/filters/client_channel/http_connect_handshaker.h', - 'src/core/ext/filters/client_channel/http_proxy.h', - 'src/core/ext/filters/client_channel/lb_policy.h', - 'src/core/ext/filters/client_channel/lb_policy_factory.h', - 'src/core/ext/filters/client_channel/lb_policy_registry.h', - 'src/core/ext/filters/client_channel/parse_address.h', - 'src/core/ext/filters/client_channel/proxy_mapper.h', - 'src/core/ext/filters/client_channel/proxy_mapper_registry.h', - 'src/core/ext/filters/client_channel/resolver.h', - 'src/core/ext/filters/client_channel/resolver_factory.h', - 'src/core/ext/filters/client_channel/resolver_registry.h', - 'src/core/ext/filters/client_channel/retry_throttle.h', - 'src/core/ext/filters/client_channel/subchannel.h', - 'src/core/ext/filters/client_channel/subchannel_index.h', - 'src/core/ext/filters/client_channel/uri_parser.h', - 'src/core/ext/filters/deadline/deadline_filter.h', - 'src/core/ext/transport/chttp2/transport/bin_decoder.h', - 'src/core/ext/transport/chttp2/transport/bin_encoder.h', - 'src/core/ext/transport/chttp2/transport/chttp2_transport.h', - 'src/core/ext/transport/chttp2/transport/flow_control.h', - 'src/core/ext/transport/chttp2/transport/frame.h', - 'src/core/ext/transport/chttp2/transport/frame_data.h', - 'src/core/ext/transport/chttp2/transport/frame_goaway.h', - 'src/core/ext/transport/chttp2/transport/frame_ping.h', - 'src/core/ext/transport/chttp2/transport/frame_rst_stream.h', - 'src/core/ext/transport/chttp2/transport/frame_settings.h', - 'src/core/ext/transport/chttp2/transport/frame_window_update.h', - 'src/core/ext/transport/chttp2/transport/hpack_encoder.h', - 'src/core/ext/transport/chttp2/transport/hpack_parser.h', - 'src/core/ext/transport/chttp2/transport/hpack_table.h', - 'src/core/ext/transport/chttp2/transport/http2_settings.h', - 'src/core/ext/transport/chttp2/transport/huffsyms.h', - 'src/core/ext/transport/chttp2/transport/incoming_metadata.h', - 'src/core/ext/transport/chttp2/transport/internal.h', - 'src/core/ext/transport/chttp2/transport/stream_map.h', - 'src/core/ext/transport/chttp2/transport/varint.h', - 'src/core/ext/transport/chttp2/alpn/alpn.h', - 'src/core/ext/filters/http/client/http_client_filter.h', - 'src/core/ext/filters/http/message_compress/message_compress_filter.h', - 'src/core/ext/filters/http/server/http_server_filter.h' - end - s.prepare_command = <<-END_OF_COMMAND find src/cpp/ -type f -exec sed -E -i'.back' 's;#include "third_party/nanopb/(.*)";#include <nanopb/\\1>;g' {} \\\; find src/cpp/ -name "*.back" -type f -delete diff --git a/include/grpc/support/log.h b/include/grpc/support/log.h index a8371cbd48..658e251545 100644 --- a/include/grpc/support/log.h +++ b/include/grpc/support/log.h @@ -19,12 +19,11 @@ #ifndef GRPC_SUPPORT_LOG_H #define GRPC_SUPPORT_LOG_H +#include <grpc/impl/codegen/port_platform.h> #include <inttypes.h> #include <stdarg.h> #include <stdlib.h> /* for abort() */ -#include <grpc/impl/codegen/port_platform.h> - #ifdef __cplusplus extern "C" { #endif diff --git a/src/core/ext/filters/client_channel/client_channel.cc b/src/core/ext/filters/client_channel/client_channel.cc index 174a15b447..9a8f25b630 100644 --- a/src/core/ext/filters/client_channel/client_channel.cc +++ b/src/core/ext/filters/client_channel/client_channel.cc @@ -446,7 +446,6 @@ static void on_resolver_result_changed_locked(void* arg, grpc_error* error) { chand->lb_policy->UpdateLocked(*chand->resolver_result); } else { // Instantiate new LB policy. - lb_policy_created = true; grpc_core::LoadBalancingPolicy::Args lb_policy_args; lb_policy_args.combiner = chand->combiner; lb_policy_args.client_channel_factory = chand->client_channel_factory; @@ -458,6 +457,7 @@ static void on_resolver_result_changed_locked(void* arg, grpc_error* error) { gpr_log(GPR_ERROR, "could not create LB policy \"%s\"", lb_policy_name); } else { + lb_policy_created = true; reresolution_request_args* args = static_cast<reresolution_request_args*>( gpr_zalloc(sizeof(*args))); diff --git a/src/core/ext/transport/chttp2/transport/bin_decoder.cc b/src/core/ext/transport/chttp2/transport/bin_decoder.cc index 91980e6974..831a36961e 100644 --- a/src/core/ext/transport/chttp2/transport/bin_decoder.cc +++ b/src/core/ext/transport/chttp2/transport/bin_decoder.cc @@ -75,6 +75,32 @@ static bool input_is_valid(uint8_t* input_ptr, size_t length) { #define COMPOSE_OUTPUT_BYTE_2(input_ptr) \ (uint8_t)((decode_table[input_ptr[2]] << 6) | decode_table[input_ptr[3]]) +// By RFC 4648, if the length of the encoded string without padding is 4n+r, +// the length of decoded string is: 1) 3n if r = 0, 2) 3n + 1 if r = 2, 3, or +// 3) invalid if r = 1. +size_t grpc_chttp2_base64_infer_length_after_decode(const grpc_slice& slice) { + size_t len = GRPC_SLICE_LENGTH(slice); + const uint8_t* bytes = GRPC_SLICE_START_PTR(slice); + while (len > 0 && bytes[len - 1] == '=') { + len--; + } + if (GRPC_SLICE_LENGTH(slice) - len > 2) { + gpr_log(GPR_ERROR, + "Base64 decoding failed. Input has more than 2 paddings."); + return 0; + } + size_t tuples = len / 4; + size_t tail_case = len % 4; + if (tail_case == 1) { + gpr_log(GPR_ERROR, + "Base64 decoding failed. Input has a length of %zu (without" + " padding), which is invalid.\n", + len); + return 0; + } + return tuples * 3 + tail_xtra[tail_case]; +} + bool grpc_base64_decode_partial(struct grpc_base64_decode_context* ctx) { size_t input_tail; diff --git a/src/core/ext/transport/chttp2/transport/bin_decoder.h b/src/core/ext/transport/chttp2/transport/bin_decoder.h index 9cb75ccd81..a0d74fb20d 100644 --- a/src/core/ext/transport/chttp2/transport/bin_decoder.h +++ b/src/core/ext/transport/chttp2/transport/bin_decoder.h @@ -48,4 +48,7 @@ grpc_slice grpc_chttp2_base64_decode(grpc_slice input); grpc_slice grpc_chttp2_base64_decode_with_length(grpc_slice input, size_t output_length); +/* Infer the length of decoded data from encoded data. */ +size_t grpc_chttp2_base64_infer_length_after_decode(const grpc_slice& slice); + #endif /* GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_BIN_DECODER_H */ diff --git a/src/core/ext/transport/cronet/transport/cronet_transport.cc b/src/core/ext/transport/cronet/transport/cronet_transport.cc index 8904c122a7..c367f9c465 100644 --- a/src/core/ext/transport/cronet/transport/cronet_transport.cc +++ b/src/core/ext/transport/cronet/transport/cronet_transport.cc @@ -24,6 +24,8 @@ #include <grpc/support/log.h> #include <grpc/support/string_util.h> +#include "src/core/ext/transport/chttp2/transport/bin_decoder.h" +#include "src/core/ext/transport/chttp2/transport/bin_encoder.h" #include "src/core/ext/transport/chttp2/transport/incoming_metadata.h" #include "src/core/ext/transport/cronet/transport/cronet_transport.h" #include "src/core/lib/gpr/host_port.h" @@ -393,6 +395,29 @@ static void execute_from_storage(stream_obj* s) { gpr_mu_unlock(&s->mu); } +static void convert_cronet_array_to_metadata( + const bidirectional_stream_header_array* header_array, + grpc_chttp2_incoming_metadata_buffer* mds) { + for (size_t i = 0; i < header_array->count; i++) { + CRONET_LOG(GPR_DEBUG, "header key=%s, value=%s", + header_array->headers[i].key, header_array->headers[i].value); + grpc_slice key = grpc_slice_intern( + grpc_slice_from_static_string(header_array->headers[i].key)); + grpc_slice value; + if (grpc_is_binary_header(key)) { + value = grpc_slice_from_static_string(header_array->headers[i].value); + value = grpc_slice_intern(grpc_chttp2_base64_decode_with_length( + value, grpc_chttp2_base64_infer_length_after_decode(value))); + } else { + value = grpc_slice_intern( + grpc_slice_from_static_string(header_array->headers[i].value)); + } + GRPC_LOG_IF_ERROR("convert_cronet_array_to_metadata", + grpc_chttp2_incoming_metadata_buffer_add( + mds, grpc_mdelem_from_slices(key, value))); + } +} + /* Cronet callback */ @@ -517,16 +542,7 @@ static void on_response_headers_received( sizeof(s->state.rs.initial_metadata)); grpc_chttp2_incoming_metadata_buffer_init(&s->state.rs.initial_metadata, s->arena); - for (size_t i = 0; i < headers->count; i++) { - GRPC_LOG_IF_ERROR("on_response_headers_received", - grpc_chttp2_incoming_metadata_buffer_add( - &s->state.rs.initial_metadata, - grpc_mdelem_from_slices( - grpc_slice_intern(grpc_slice_from_static_string( - headers->headers[i].key)), - grpc_slice_intern(grpc_slice_from_static_string( - headers->headers[i].value))))); - } + convert_cronet_array_to_metadata(headers, &s->state.rs.initial_metadata); s->state.state_callback_received[OP_RECV_INITIAL_METADATA] = true; if (!(s->state.state_op_done[OP_CANCEL_ERROR] || s->state.state_callback_received[OP_FAILED])) { @@ -621,18 +637,11 @@ static void on_response_trailers_received( s->state.rs.trailing_metadata_valid = false; grpc_chttp2_incoming_metadata_buffer_init(&s->state.rs.trailing_metadata, s->arena); - for (size_t i = 0; i < trailers->count; i++) { - CRONET_LOG(GPR_DEBUG, "trailer key=%s, value=%s", trailers->headers[i].key, - trailers->headers[i].value); - GRPC_LOG_IF_ERROR("on_response_trailers_received", - grpc_chttp2_incoming_metadata_buffer_add( - &s->state.rs.trailing_metadata, - grpc_mdelem_from_slices( - grpc_slice_intern(grpc_slice_from_static_string( - trailers->headers[i].key)), - grpc_slice_intern(grpc_slice_from_static_string( - trailers->headers[i].value))))); + convert_cronet_array_to_metadata(trailers, &s->state.rs.trailing_metadata); + if (trailers->count > 0) { s->state.rs.trailing_metadata_valid = true; + } + for (size_t i = 0; i < trailers->count; i++) { if (0 == strcmp(trailers->headers[i].key, "grpc-status") && 0 != strcmp(trailers->headers[i].value, "0")) { s->state.fail_state = true; @@ -721,7 +730,14 @@ static void convert_metadata_to_cronet_headers( grpc_mdelem mdelem = curr->md; curr = curr->next; char* key = grpc_slice_to_c_string(GRPC_MDKEY(mdelem)); - char* value = grpc_slice_to_c_string(GRPC_MDVALUE(mdelem)); + char* value; + if (grpc_is_binary_header(GRPC_MDKEY(mdelem))) { + grpc_slice wire_value = grpc_chttp2_base64_encode(GRPC_MDVALUE(mdelem)); + value = grpc_slice_to_c_string(wire_value); + grpc_slice_unref(wire_value); + } else { + value = grpc_slice_to_c_string(GRPC_MDVALUE(mdelem)); + } if (grpc_slice_eq(GRPC_MDKEY(mdelem), GRPC_MDSTR_SCHEME) || grpc_slice_eq(GRPC_MDKEY(mdelem), GRPC_MDSTR_AUTHORITY)) { /* Cronet populates these fields on its own */ diff --git a/src/csharp/Grpc.Core.Tests/Interceptors/ClientInterceptorTest.cs b/src/csharp/Grpc.Core.Tests/Interceptors/ClientInterceptorTest.cs new file mode 100644 index 0000000000..02f6f6ffc6 --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/Interceptors/ClientInterceptorTest.cs @@ -0,0 +1,228 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License 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. + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Grpc.Core; +using Grpc.Core.Interceptors; +using Grpc.Core.Internal; +using Grpc.Core.Utils; +using Grpc.Core.Tests; +using NUnit.Framework; + +namespace Grpc.Core.Interceptors.Tests +{ + public class ClientInterceptorTest + { + const string Host = "127.0.0.1"; + + [Test] + public void AddRequestHeaderInClientInterceptor() + { + const string HeaderKey = "x-client-interceptor"; + const string HeaderValue = "hello-world"; + var helper = new MockServiceHelper(Host); + helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) => + { + var interceptorHeader = context.RequestHeaders.Last(m => (m.Key == HeaderKey)).Value; + Assert.AreEqual(interceptorHeader, HeaderValue); + return Task.FromResult("PASS"); + }); + var server = helper.GetServer(); + server.Start(); + var callInvoker = helper.GetChannel().Intercept(metadata => + { + metadata = metadata ?? new Metadata(); + metadata.Add(new Metadata.Entry(HeaderKey, HeaderValue)); + return metadata; + }); + Assert.AreEqual("PASS", callInvoker.BlockingUnaryCall(new Method<string, string>(MethodType.Unary, MockServiceHelper.ServiceName, "Unary", Marshallers.StringMarshaller, Marshallers.StringMarshaller), Host, new CallOptions(), "")); + } + + [Test] + public void CheckInterceptorOrderInClientInterceptors() + { + var helper = new MockServiceHelper(Host); + helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) => + { + return Task.FromResult("PASS"); + }); + var server = helper.GetServer(); + server.Start(); + var stringBuilder = new StringBuilder(); + var callInvoker = helper.GetChannel().Intercept(metadata => { + stringBuilder.Append("interceptor1"); + return metadata; + }).Intercept(new CallbackInterceptor(() => stringBuilder.Append("array1")), + new CallbackInterceptor(() => stringBuilder.Append("array2")), + new CallbackInterceptor(() => stringBuilder.Append("array3"))) + .Intercept(metadata => + { + stringBuilder.Append("interceptor2"); + return metadata; + }).Intercept(metadata => + { + stringBuilder.Append("interceptor3"); + return metadata; + }); + Assert.AreEqual("PASS", callInvoker.BlockingUnaryCall(new Method<string, string>(MethodType.Unary, MockServiceHelper.ServiceName, "Unary", Marshallers.StringMarshaller, Marshallers.StringMarshaller), Host, new CallOptions(), "")); + Assert.AreEqual("interceptor3interceptor2array1array2array3interceptor1", stringBuilder.ToString()); + } + + [Test] + public void CheckNullInterceptorRegistrationFails() + { + var helper = new MockServiceHelper(Host); + helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) => + { + return Task.FromResult("PASS"); + }); + Assert.Throws<ArgumentNullException>(() => helper.GetChannel().Intercept(default(Interceptor))); + Assert.Throws<ArgumentNullException>(() => helper.GetChannel().Intercept(new[]{default(Interceptor)})); + Assert.Throws<ArgumentNullException>(() => helper.GetChannel().Intercept(new[]{new CallbackInterceptor(()=>{}), null})); + Assert.Throws<ArgumentNullException>(() => helper.GetChannel().Intercept(default(Interceptor[]))); + } + + [Test] + public async Task CountNumberOfRequestsInClientInterceptors() + { + var helper = new MockServiceHelper(Host); + helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) => + { + var stringBuilder = new StringBuilder(); + await requestStream.ForEachAsync(request => + { + stringBuilder.Append(request); + return TaskUtils.CompletedTask; + }); + await Task.Delay(100); + return stringBuilder.ToString(); + }); + + var callInvoker = helper.GetChannel().Intercept(new ClientStreamingCountingInterceptor()); + + var server = helper.GetServer(); + server.Start(); + var call = callInvoker.AsyncClientStreamingCall(new Method<string, string>(MethodType.ClientStreaming, MockServiceHelper.ServiceName, "ClientStreaming", Marshallers.StringMarshaller, Marshallers.StringMarshaller), Host, new CallOptions()); + await call.RequestStream.WriteAllAsync(new string[] { "A", "B", "C" }); + Assert.AreEqual("3", await call.ResponseAsync); + + Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode); + Assert.IsNotNull(call.GetTrailers()); + } + + private class CallbackInterceptor : Interceptor + { + readonly Action callback; + + public CallbackInterceptor(Action callback) + { + this.callback = GrpcPreconditions.CheckNotNull(callback, nameof(callback)); + } + + public override TResponse BlockingUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, BlockingUnaryCallContinuation<TRequest, TResponse> continuation) + { + callback(); + return continuation(request, context); + } + + public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation) + { + callback(); + return continuation(request, context); + } + + public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation) + { + callback(); + return continuation(request, context); + } + + public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation) + { + callback(); + return continuation(context); + } + + public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncDuplexStreamingCallContinuation<TRequest, TResponse> continuation) + { + callback(); + return continuation(context); + } + } + + private class ClientStreamingCountingInterceptor : Interceptor + { + public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation) + { + var response = continuation(context); + int counter = 0; + var requestStream = new WrappedClientStreamWriter<TRequest>(response.RequestStream, + message => { counter++; return message; }, null); + var responseAsync = response.ResponseAsync.ContinueWith( + unaryResponse => (TResponse)(object)counter.ToString() // Cast to object first is needed to satisfy the type-checker + ); + return new AsyncClientStreamingCall<TRequest, TResponse>(requestStream, responseAsync, response.ResponseHeadersAsync, response.GetStatus, response.GetTrailers, response.Dispose); + } + } + + private class WrappedClientStreamWriter<T> : IClientStreamWriter<T> + { + readonly IClientStreamWriter<T> writer; + readonly Func<T, T> onMessage; + readonly Action onResponseStreamEnd; + public WrappedClientStreamWriter(IClientStreamWriter<T> writer, Func<T, T> onMessage, Action onResponseStreamEnd) + { + this.writer = writer; + this.onMessage = onMessage; + this.onResponseStreamEnd = onResponseStreamEnd; + } + public Task CompleteAsync() + { + if (onResponseStreamEnd != null) + { + return writer.CompleteAsync().ContinueWith(x => onResponseStreamEnd()); + } + return writer.CompleteAsync(); + } + public Task WriteAsync(T message) + { + if (onMessage != null) + { + message = onMessage(message); + } + return writer.WriteAsync(message); + } + public WriteOptions WriteOptions + { + get + { + return writer.WriteOptions; + } + set + { + writer.WriteOptions = value; + } + } + } + } +} diff --git a/src/csharp/Grpc.Core.Tests/Interceptors/ServerInterceptorTest.cs b/src/csharp/Grpc.Core.Tests/Interceptors/ServerInterceptorTest.cs new file mode 100644 index 0000000000..e76f21d098 --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/Interceptors/ServerInterceptorTest.cs @@ -0,0 +1,126 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License 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. + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Grpc.Core; +using Grpc.Core.Interceptors; +using Grpc.Core.Internal; +using Grpc.Core.Tests; +using Grpc.Core.Utils; +using NUnit.Framework; + +namespace Grpc.Core.Interceptors.Tests +{ + public class ServerInterceptorTest + { + const string Host = "127.0.0.1"; + + [Test] + public void AddRequestHeaderInServerInterceptor() + { + var helper = new MockServiceHelper(Host); + const string MetadataKey = "x-interceptor"; + const string MetadataValue = "hello world"; + var interceptor = new ServerCallContextInterceptor(ctx => ctx.RequestHeaders.Add(new Metadata.Entry(MetadataKey, MetadataValue))); + helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) => + { + var interceptorHeader = context.RequestHeaders.Last(m => (m.Key == MetadataKey)).Value; + Assert.AreEqual(interceptorHeader, MetadataValue); + return Task.FromResult("PASS"); + }); + helper.ServiceDefinition = helper.ServiceDefinition.Intercept(interceptor); + var server = helper.GetServer(); + server.Start(); + var channel = helper.GetChannel(); + Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "")); + } + + [Test] + public void VerifyInterceptorOrdering() + { + var helper = new MockServiceHelper(Host); + helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) => + { + return Task.FromResult("PASS"); + }); + var stringBuilder = new StringBuilder(); + helper.ServiceDefinition = helper.ServiceDefinition + .Intercept(new ServerCallContextInterceptor(ctx => stringBuilder.Append("A"))) + .Intercept(new ServerCallContextInterceptor(ctx => stringBuilder.Append("B1")), + new ServerCallContextInterceptor(ctx => stringBuilder.Append("B2")), + new ServerCallContextInterceptor(ctx => stringBuilder.Append("B3"))) + .Intercept(new ServerCallContextInterceptor(ctx => stringBuilder.Append("C"))); + var server = helper.GetServer(); + server.Start(); + var channel = helper.GetChannel(); + Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "")); + Assert.AreEqual("CB1B2B3A", stringBuilder.ToString()); + } + + [Test] + public void CheckNullInterceptorRegistrationFails() + { + var helper = new MockServiceHelper(Host); + var sd = helper.ServiceDefinition; + Assert.Throws<ArgumentNullException>(() => sd.Intercept(default(Interceptor))); + Assert.Throws<ArgumentNullException>(() => sd.Intercept(new[]{default(Interceptor)})); + Assert.Throws<ArgumentNullException>(() => sd.Intercept(new[]{new ServerCallContextInterceptor(ctx=>{}), null})); + Assert.Throws<ArgumentNullException>(() => sd.Intercept(default(Interceptor[]))); + } + + private class ServerCallContextInterceptor : Interceptor + { + readonly Action<ServerCallContext> interceptor; + + public ServerCallContextInterceptor(Action<ServerCallContext> interceptor) + { + GrpcPreconditions.CheckNotNull(interceptor, nameof(interceptor)); + this.interceptor = interceptor; + } + + public override Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation) + { + interceptor(context); + return continuation(request, context); + } + + public override Task<TResponse> ClientStreamingServerHandler<TRequest, TResponse>(IAsyncStreamReader<TRequest> requestStream, ServerCallContext context, ClientStreamingServerMethod<TRequest, TResponse> continuation) + { + interceptor(context); + return continuation(requestStream, context); + } + + public override Task ServerStreamingServerHandler<TRequest, TResponse>(TRequest request, IServerStreamWriter<TResponse> responseStream, ServerCallContext context, ServerStreamingServerMethod<TRequest, TResponse> continuation) + { + interceptor(context); + return continuation(request, responseStream, context); + } + + public override Task DuplexStreamingServerHandler<TRequest, TResponse>(IAsyncStreamReader<TRequest> requestStream, IServerStreamWriter<TResponse> responseStream, ServerCallContext context, DuplexStreamingServerMethod<TRequest, TResponse> continuation) + { + interceptor(context); + return continuation(requestStream, responseStream, context); + } + } + } +} diff --git a/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs index 7f4677d57f..a925f865ff 100644 --- a/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs +++ b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs @@ -37,7 +37,6 @@ namespace Grpc.Core.Tests public const string ServiceName = "tests.Test"; readonly string host; - readonly ServerServiceDefinition serviceDefinition; readonly IEnumerable<ChannelOption> channelOptions; readonly Method<string, string> unaryMethod; @@ -87,7 +86,7 @@ namespace Grpc.Core.Tests marshaller, marshaller); - serviceDefinition = ServerServiceDefinition.CreateBuilder() + ServiceDefinition = ServerServiceDefinition.CreateBuilder() .AddMethod(unaryMethod, (request, context) => unaryHandler(request, context)) .AddMethod(clientStreamingMethod, (requestStream, context) => clientStreamingHandler(requestStream, context)) .AddMethod(serverStreamingMethod, (request, responseStream, context) => serverStreamingHandler(request, responseStream, context)) @@ -131,7 +130,7 @@ namespace Grpc.Core.Tests // Disable SO_REUSEPORT to prevent https://github.com/grpc/grpc/issues/10755 server = new Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) { - Services = { serviceDefinition }, + Services = { ServiceDefinition }, Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } } }; } @@ -178,13 +177,7 @@ namespace Grpc.Core.Tests } } - public ServerServiceDefinition ServiceDefinition - { - get - { - return this.serviceDefinition; - } - } + public ServerServiceDefinition ServiceDefinition { get; set; } public UnaryServerMethod<string, string> UnaryHandler { diff --git a/src/csharp/Grpc.Core/ClientBase.cs b/src/csharp/Grpc.Core/ClientBase.cs index 2d41b29fa0..fac34071be 100644 --- a/src/csharp/Grpc.Core/ClientBase.cs +++ b/src/csharp/Grpc.Core/ClientBase.cs @@ -16,6 +16,8 @@ #endregion +using System; +using Grpc.Core.Interceptors; using Grpc.Core.Internal; using Grpc.Core.Utils; @@ -147,6 +149,52 @@ namespace Grpc.Core /// </summary> protected internal class ClientBaseConfiguration { + private class ClientBaseConfigurationInterceptor : Interceptor + { + readonly Func<IMethod, string, CallOptions, Tuple<string, CallOptions>> interceptor; + + /// <summary> + /// Creates a new instance of ClientBaseConfigurationInterceptor given the specified header and host interceptor function. + /// </summary> + public ClientBaseConfigurationInterceptor(Func<IMethod, string, CallOptions, Tuple<string, CallOptions>> interceptor) + { + this.interceptor = GrpcPreconditions.CheckNotNull(interceptor, nameof(interceptor)); + } + + private ClientInterceptorContext<TRequest, TResponse> GetNewContext<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context) + where TRequest : class + where TResponse : class + { + var newHostAndCallOptions = interceptor(context.Method, context.Host, context.Options); + return new ClientInterceptorContext<TRequest, TResponse>(context.Method, newHostAndCallOptions.Item1, newHostAndCallOptions.Item2); + } + + public override TResponse BlockingUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, BlockingUnaryCallContinuation<TRequest, TResponse> continuation) + { + return continuation(request, GetNewContext(context)); + } + + public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation) + { + return continuation(request, GetNewContext(context)); + } + + public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation) + { + return continuation(request, GetNewContext(context)); + } + + public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation) + { + return continuation(GetNewContext(context)); + } + + public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncDuplexStreamingCallContinuation<TRequest, TResponse> continuation) + { + return continuation(GetNewContext(context)); + } + } + readonly CallInvoker undecoratedCallInvoker; readonly string host; @@ -158,12 +206,12 @@ namespace Grpc.Core internal CallInvoker CreateDecoratedCallInvoker() { - return new InterceptingCallInvoker(undecoratedCallInvoker, hostInterceptor: (h) => host); + return undecoratedCallInvoker.Intercept(new ClientBaseConfigurationInterceptor((method, host, options) => Tuple.Create(this.host, options))); } internal ClientBaseConfiguration WithHost(string host) { - GrpcPreconditions.CheckNotNull(host, "host"); + GrpcPreconditions.CheckNotNull(host, nameof(host)); return new ClientBaseConfiguration(this.undecoratedCallInvoker, host); } } diff --git a/src/csharp/Grpc.Core/Interceptors/CallInvokerExtensions.cs b/src/csharp/Grpc.Core/Interceptors/CallInvokerExtensions.cs new file mode 100644 index 0000000000..421b5d379e --- /dev/null +++ b/src/csharp/Grpc.Core/Interceptors/CallInvokerExtensions.cs @@ -0,0 +1,144 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License 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. + +#endregion + +using System; +using System.Linq; +using Grpc.Core.Utils; + +namespace Grpc.Core.Interceptors +{ + /// <summary> + /// Extends the CallInvoker class to provide the interceptor facility on the client side. + /// This is an EXPERIMENTAL API. + /// </summary> + public static class CallInvokerExtensions + { + /// <summary> + /// Returns a <see cref="Grpc.Core.CallInvoker" /> instance that intercepts + /// the invoker with the given interceptor. + /// </summary> + /// <param name="invoker">The underlying invoker to intercept.</param> + /// <param name="interceptor">The interceptor to intercept calls to the invoker with.</param> + /// <remarks> + /// Multiple interceptors can be added on top of each other by calling + /// "invoker.Intercept(a, b, c)". The order of invocation will be "a", "b", and then "c". + /// Interceptors can be later added to an existing intercepted CallInvoker, effectively + /// building a chain like "invoker.Intercept(c).Intercept(b).Intercept(a)". Note that + /// in this case, the last interceptor added will be the first to take control. + /// </remarks> + public static CallInvoker Intercept(this CallInvoker invoker, Interceptor interceptor) + { + return new InterceptingCallInvoker(invoker, interceptor); + } + + /// <summary> + /// Returns a <see cref="Grpc.Core.CallInvoker" /> instance that intercepts + /// the invoker with the given interceptors. + /// </summary> + /// <param name="invoker">The channel to intercept.</param> + /// <param name="interceptors"> + /// An array of interceptors to intercept the calls to the invoker with. + /// Control is passed to the interceptors in the order specified. + /// </param> + /// <remarks> + /// Multiple interceptors can be added on top of each other by calling + /// "invoker.Intercept(a, b, c)". The order of invocation will be "a", "b", and then "c". + /// Interceptors can be later added to an existing intercepted CallInvoker, effectively + /// building a chain like "invoker.Intercept(c).Intercept(b).Intercept(a)". Note that + /// in this case, the last interceptor added will be the first to take control. + /// </remarks> + public static CallInvoker Intercept(this CallInvoker invoker, params Interceptor[] interceptors) + { + GrpcPreconditions.CheckNotNull(invoker, nameof(invoker)); + GrpcPreconditions.CheckNotNull(interceptors, nameof(interceptors)); + + foreach (var interceptor in interceptors.Reverse()) + { + invoker = Intercept(invoker, interceptor); + } + + return invoker; + } + + /// <summary> + /// Returns a <see cref="Grpc.Core.CallInvoker" /> instance that intercepts + /// the invoker with the given interceptor. + /// </summary> + /// <param name="invoker">The underlying invoker to intercept.</param> + /// <param name="interceptor"> + /// An interceptor delegate that takes the request metadata to be sent with an outgoing call + /// and returns a <see cref="Grpc.Core.Metadata" /> instance that will replace the existing + /// invocation metadata. + /// </param> + /// <remarks> + /// Multiple interceptors can be added on top of each other by + /// building a chain like "invoker.Intercept(c).Intercept(b).Intercept(a)". Note that + /// in this case, the last interceptor added will be the first to take control. + /// </remarks> + public static CallInvoker Intercept(this CallInvoker invoker, Func<Metadata, Metadata> interceptor) + { + return new InterceptingCallInvoker(invoker, new MetadataInterceptor(interceptor)); + } + + private class MetadataInterceptor : Interceptor + { + readonly Func<Metadata, Metadata> interceptor; + + /// <summary> + /// Creates a new instance of MetadataInterceptor given the specified interceptor function. + /// </summary> + public MetadataInterceptor(Func<Metadata, Metadata> interceptor) + { + this.interceptor = GrpcPreconditions.CheckNotNull(interceptor, nameof(interceptor)); + } + + private ClientInterceptorContext<TRequest, TResponse> GetNewContext<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context) + where TRequest : class + where TResponse : class + { + var metadata = context.Options.Headers ?? new Metadata(); + return new ClientInterceptorContext<TRequest, TResponse>(context.Method, context.Host, context.Options.WithHeaders(interceptor(metadata))); + } + + public override TResponse BlockingUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, BlockingUnaryCallContinuation<TRequest, TResponse> continuation) + { + return continuation(request, GetNewContext(context)); + } + + public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation) + { + return continuation(request, GetNewContext(context)); + } + + public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation) + { + return continuation(request, GetNewContext(context)); + } + + public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation) + { + return continuation(GetNewContext(context)); + } + + public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncDuplexStreamingCallContinuation<TRequest, TResponse> continuation) + { + return continuation(GetNewContext(context)); + } + } + } +} diff --git a/src/csharp/Grpc.Core/Interceptors/ChannelExtensions.cs b/src/csharp/Grpc.Core/Interceptors/ChannelExtensions.cs new file mode 100644 index 0000000000..00b2fa8bec --- /dev/null +++ b/src/csharp/Grpc.Core/Interceptors/ChannelExtensions.cs @@ -0,0 +1,88 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License 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. + +#endregion + +using System; + +namespace Grpc.Core.Interceptors +{ + /// <summary> + /// Provides extension methods to make it easy to register interceptors on Channel objects. + /// This is an EXPERIMENTAL API. + /// </summary> + public static class ChannelExtensions + { + /// <summary> + /// Returns a <see cref="Grpc.Core.CallInvoker" /> instance that intercepts + /// the channel with the given interceptor. + /// </summary> + /// <param name="channel">The channel to intercept.</param> + /// <param name="interceptor">The interceptor to intercept the channel with.</param> + /// <remarks> + /// Multiple interceptors can be added on top of each other by calling + /// "channel.Intercept(a, b, c)". The order of invocation will be "a", "b", and then "c". + /// Interceptors can be later added to an existing intercepted channel, effectively + /// building a chain like "channel.Intercept(c).Intercept(b).Intercept(a)". Note that + /// in this case, the last interceptor added will be the first to take control. + /// </remarks> + public static CallInvoker Intercept(this Channel channel, Interceptor interceptor) + { + return new DefaultCallInvoker(channel).Intercept(interceptor); + } + + /// <summary> + /// Returns a <see cref="Grpc.Core.CallInvoker" /> instance that intercepts + /// the channel with the given interceptors. + /// </summary> + /// <param name="channel">The channel to intercept.</param> + /// <param name="interceptors"> + /// An array of interceptors to intercept the channel with. + /// Control is passed to the interceptors in the order specified. + /// </param> + /// <remarks> + /// Multiple interceptors can be added on top of each other by calling + /// "channel.Intercept(a, b, c)". The order of invocation will be "a", "b", and then "c". + /// Interceptors can be later added to an existing intercepted channel, effectively + /// building a chain like "channel.Intercept(c).Intercept(b).Intercept(a)". Note that + /// in this case, the last interceptor added will be the first to take control. + /// </remarks> + public static CallInvoker Intercept(this Channel channel, params Interceptor[] interceptors) + { + return new DefaultCallInvoker(channel).Intercept(interceptors); + } + + /// <summary> + /// Returns a <see cref="Grpc.Core.CallInvoker" /> instance that intercepts + /// the invoker with the given interceptor. + /// </summary> + /// <param name="channel">The channel to intercept.</param> + /// <param name="interceptor"> + /// An interceptor delegate that takes the request metadata to be sent with an outgoing call + /// and returns a <see cref="Grpc.Core.Metadata" /> instance that will replace the existing + /// invocation metadata. + /// </param> + /// <remarks> + /// Multiple interceptors can be added on top of each other by + /// building a chain like "channel.Intercept(c).Intercept(b).Intercept(a)". Note that + /// in this case, the last interceptor added will be the first to take control. + /// </remarks> + public static CallInvoker Intercept(this Channel channel, Func<Metadata, Metadata> interceptor) + { + return new DefaultCallInvoker(channel).Intercept(interceptor); + } + } +} diff --git a/src/csharp/Grpc.Core/Interceptors/ClientInterceptorContext.cs b/src/csharp/Grpc.Core/Interceptors/ClientInterceptorContext.cs new file mode 100644 index 0000000000..de06a77077 --- /dev/null +++ b/src/csharp/Grpc.Core/Interceptors/ClientInterceptorContext.cs @@ -0,0 +1,65 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License 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. + +#endregion + +using System; +using System.Reflection; +using System.Threading.Tasks; +using Grpc.Core.Internal; + +namespace Grpc.Core.Interceptors +{ + /// <summary> + /// Carries along the context associated with intercepted invocations on the client side. + /// This is an EXPERIMENTAL API. + /// </summary> + public struct ClientInterceptorContext<TRequest, TResponse> + where TRequest : class + where TResponse : class + { + /// <summary> + /// Creates a new instance of <see cref="Grpc.Core.Interceptors.ClientInterceptorContext{TRequest, TResponse}" /> + /// with the specified method, host, and call options. + /// </summary> + /// <param name="method">A <see cref="Grpc.Core.Method{TRequest, TResponse}"/> object representing the method to be invoked.</param> + /// <param name="host">The host to dispatch the current call to.</param> + /// <param name="options">A <see cref="Grpc.Core.CallOptions"/> instance containing the call options of the current call.</param> + public ClientInterceptorContext(Method<TRequest, TResponse> method, string host, CallOptions options) + { + Method = method; + Host = host; + Options = options; + } + + /// <summary> + /// Gets the <see cref="Grpc.Core.Method{TRequest, TResponse}"/> instance + /// representing the method to be invoked. + /// </summary> + public Method<TRequest, TResponse> Method { get; } + + /// <summary> + /// Gets the host that the currect invocation will be dispatched to. + /// </summary> + public string Host { get; } + + /// <summary> + /// Gets the <see cref="Grpc.Core.CallOptions"/> structure representing the + /// call options associated with the current invocation. + /// </summary> + public CallOptions Options { get; } + } +} diff --git a/src/csharp/Grpc.Core/Interceptors/InterceptingCallInvoker.cs b/src/csharp/Grpc.Core/Interceptors/InterceptingCallInvoker.cs new file mode 100644 index 0000000000..84d2a0b958 --- /dev/null +++ b/src/csharp/Grpc.Core/Interceptors/InterceptingCallInvoker.cs @@ -0,0 +1,96 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License 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. + +#endregion + +using System; +using Grpc.Core.Utils; + +namespace Grpc.Core.Interceptors +{ + /// <summary> + /// Decorates an underlying <see cref="Grpc.Core.CallInvoker" /> to + /// intercept calls through a given interceptor. + /// </summary> + internal class InterceptingCallInvoker : CallInvoker + { + readonly CallInvoker invoker; + readonly Interceptor interceptor; + + /// <summary> + /// Creates a new instance of <see cref="Grpc.Core.Interceptors.InterceptingCallInvoker" /> + /// with the given underlying invoker and interceptor instances. + /// </summary> + public InterceptingCallInvoker(CallInvoker invoker, Interceptor interceptor) + { + this.invoker = GrpcPreconditions.CheckNotNull(invoker, nameof(invoker)); + this.interceptor = GrpcPreconditions.CheckNotNull(interceptor, nameof(interceptor)); + } + + /// <summary> + /// Intercepts a simple blocking call with the registered interceptor. + /// </summary> + public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) + { + return interceptor.BlockingUnaryCall( + request, + new ClientInterceptorContext<TRequest, TResponse>(method, host, options), + (req, ctx) => invoker.BlockingUnaryCall(ctx.Method, ctx.Host, ctx.Options, req)); + } + + /// <summary> + /// Intercepts a simple asynchronous call with the registered interceptor. + /// </summary> + public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) + { + return interceptor.AsyncUnaryCall( + request, + new ClientInterceptorContext<TRequest, TResponse>(method, host, options), + (req, ctx) => invoker.AsyncUnaryCall(ctx.Method, ctx.Host, ctx.Options, req)); + } + + /// <summary> + /// Intercepts an asynchronous server streaming call with the registered interceptor. + /// </summary> + public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) + { + return interceptor.AsyncServerStreamingCall( + request, + new ClientInterceptorContext<TRequest, TResponse>(method, host, options), + (req, ctx) => invoker.AsyncServerStreamingCall(ctx.Method, ctx.Host, ctx.Options, req)); + } + + /// <summary> + /// Intercepts an asynchronous client streaming call with the registered interceptor. + /// </summary> + public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options) + { + return interceptor.AsyncClientStreamingCall( + new ClientInterceptorContext<TRequest, TResponse>(method, host, options), + ctx => invoker.AsyncClientStreamingCall(ctx.Method, ctx.Host, ctx.Options)); + } + + /// <summary> + /// Intercepts an asynchronous duplex streaming call with the registered interceptor. + /// </summary> + public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options) + { + return interceptor.AsyncDuplexStreamingCall( + new ClientInterceptorContext<TRequest, TResponse>(method, host, options), + ctx => invoker.AsyncDuplexStreamingCall(ctx.Method, ctx.Host, ctx.Options)); + } + } +} diff --git a/src/csharp/Grpc.Core/Interceptors/Interceptor.cs b/src/csharp/Grpc.Core/Interceptors/Interceptor.cs new file mode 100644 index 0000000000..56a30c34af --- /dev/null +++ b/src/csharp/Grpc.Core/Interceptors/Interceptor.cs @@ -0,0 +1,406 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License 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. + +#endregion + +using System; +using System.Reflection; +using System.Threading.Tasks; +using Grpc.Core.Internal; + +namespace Grpc.Core.Interceptors +{ + /// <summary> + /// Serves as the base class for gRPC interceptors. + /// This is an EXPERIMENTAL API. + /// </summary> + public abstract class Interceptor + { + /// <summary> + /// Represents a continuation for intercepting simple blocking invocations. + /// A delegate of this type is passed to the BlockingUnaryCall method + /// when an outgoing invocation is being intercepted and calling the + /// delegate will invoke the next interceptor in the chain, or the underlying + /// call invoker if called from the last interceptor. The interceptor is + /// allowed to call it zero, one, or multiple times, passing it the appropriate + /// context and request values as it sees fit. + /// </summary> + /// <typeparam name="TRequest">Request message type for this invocation.</typeparam> + /// <typeparam name="TResponse">Response message type for this invocation.</typeparam> + /// <param name="request">The request value to continue the invocation with.</param> + /// <param name="context"> + /// The <see cref="Grpc.Core.Interceptors.ClientInterceptorContext{TRequest, TResponse}"/> + /// instance to pass to the next step in the invocation process. + /// </param> + /// <returns> + /// The response value of the invocation to return to the caller. + /// The interceptor can choose to return the return value of the + /// continuation delegate or an arbitrary value as it sees fit. + /// </returns> + public delegate TResponse BlockingUnaryCallContinuation<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context) + where TRequest : class + where TResponse : class; + + /// <summary> + /// Represents a continuation for intercepting simple asynchronous invocations. + /// A delegate of this type is passed to the AsyncUnaryCall method + /// when an outgoing invocation is being intercepted and calling the + /// delegate will invoke the next interceptor in the chain, or the underlying + /// call invoker if called from the last interceptor. The interceptor is + /// allowed to call it zero, one, or multiple times, passing it the appropriate + /// request value and context as it sees fit. + /// </summary> + /// <typeparam name="TRequest">Request message type for this invocation.</typeparam> + /// <typeparam name="TResponse">Response message type for this invocation.</typeparam> + /// <param name="request">The request value to continue the invocation with.</param> + /// <param name="context"> + /// The <see cref="Grpc.Core.Interceptors.ClientInterceptorContext{TRequest, TResponse}"/> + /// instance to pass to the next step in the invocation process. + /// </param> + /// <returns> + /// An instance of <see cref="Grpc.Core.AsyncUnaryCall{TResponse}" /> + /// representing an asynchronous invocation of a unary RPC. + /// The interceptor can choose to return the same object returned from + /// the continuation delegate or an arbitrarily constructed instance as it sees fit. + /// </returns> + public delegate AsyncUnaryCall<TResponse> AsyncUnaryCallContinuation<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context) + where TRequest : class + where TResponse : class; + + /// <summary> + /// Represents a continuation for intercepting asynchronous server-streaming invocations. + /// A delegate of this type is passed to the AsyncServerStreamingCall method + /// when an outgoing invocation is being intercepted and calling the + /// delegate will invoke the next interceptor in the chain, or the underlying + /// call invoker if called from the last interceptor. The interceptor is + /// allowed to call it zero, one, or multiple times, passing it the appropriate + /// request value and context as it sees fit. + /// </summary> + /// <typeparam name="TRequest">Request message type for this invocation.</typeparam> + /// <typeparam name="TResponse">Response message type for this invocation.</typeparam> + /// <param name="request">The request value to continue the invocation with.</param> + /// <param name="context"> + /// The <see cref="Grpc.Core.Interceptors.ClientInterceptorContext{TRequest, TResponse}"/> + /// instance to pass to the next step in the invocation process. + /// </param> + /// <returns> + /// An instance of <see cref="Grpc.Core.AsyncServerStreamingCall{TResponse}" /> + /// representing an asynchronous invocation of a server-streaming RPC. + /// The interceptor can choose to return the same object returned from + /// the continuation delegate or an arbitrarily constructed instance as it sees fit. + /// </returns> + public delegate AsyncServerStreamingCall<TResponse> AsyncServerStreamingCallContinuation<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context) + where TRequest : class + where TResponse : class; + + /// <summary> + /// Represents a continuation for intercepting asynchronous client-streaming invocations. + /// A delegate of this type is passed to the AsyncClientStreamingCall method + /// when an outgoing invocation is being intercepted and calling the + /// delegate will invoke the next interceptor in the chain, or the underlying + /// call invoker if called from the last interceptor. The interceptor is + /// allowed to call it zero, one, or multiple times, passing it the appropriate + /// request value and context as it sees fit. + /// </summary> + /// <typeparam name="TRequest">Request message type for this invocation.</typeparam> + /// <typeparam name="TResponse">Response message type for this invocation.</typeparam> + /// <param name="context"> + /// The <see cref="Grpc.Core.Interceptors.ClientInterceptorContext{TRequest, TResponse}"/> + /// instance to pass to the next step in the invocation process. + /// </param> + /// <returns> + /// An instance of <see cref="Grpc.Core.AsyncClientStreamingCall{TRequest, TResponse}" /> + /// representing an asynchronous invocation of a client-streaming RPC. + /// The interceptor can choose to return the same object returned from + /// the continuation delegate or an arbitrarily constructed instance as it sees fit. + /// </returns> + public delegate AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCallContinuation<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context) + where TRequest : class + where TResponse : class; + + /// <summary> + /// Represents a continuation for intercepting asynchronous duplex invocations. + /// A delegate of this type is passed to the AsyncDuplexStreamingCall method + /// when an outgoing invocation is being intercepted and calling the + /// delegate will invoke the next interceptor in the chain, or the underlying + /// call invoker if called from the last interceptor. The interceptor is + /// allowed to call it zero, one, or multiple times, passing it the appropriate + /// request value and context as it sees fit. + /// </summary> + /// <param name="context"> + /// The <see cref="Grpc.Core.Interceptors.ClientInterceptorContext{TRequest, TResponse}"/> + /// instance to pass to the next step in the invocation process. + /// </param> + /// <returns> + /// An instance of <see cref="Grpc.Core.AsyncDuplexStreamingCall{TRequest, TResponse}" /> + /// representing an asynchronous invocation of a duplex-streaming RPC. + /// The interceptor can choose to return the same object returned from + /// the continuation delegate or an arbitrarily constructed instance as it sees fit. + /// </returns> + public delegate AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCallContinuation<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context) + where TRequest : class + where TResponse : class; + + /// <summary> + /// Intercepts a blocking invocation of a simple remote call. + /// </summary> + /// <param name="request">The request message of the invocation.</param> + /// <param name="context"> + /// The <see cref="Grpc.Core.Interceptors.ClientInterceptorContext{TRequest, TResponse}"/> + /// associated with the current invocation. + /// </param> + /// <param name="continuation"> + /// The callback that continues the invocation process. + /// This can be invoked zero or more times by the interceptor. + /// The interceptor can invoke the continuation passing the given + /// request value and context arguments, or substitute them as it sees fit. + /// </param> + /// <returns> + /// The response message of the current invocation. + /// The interceptor can simply return the return value of the + /// continuation delegate passed to it intact, or an arbitrary + /// value as it sees fit. + /// </returns> + public virtual TResponse BlockingUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, BlockingUnaryCallContinuation<TRequest, TResponse> continuation) + where TRequest : class + where TResponse : class + { + return continuation(request, context); + } + + /// <summary> + /// Intercepts an asynchronous invocation of a simple remote call. + /// </summary> + /// <param name="request">The request message of the invocation.</param> + /// <param name="context"> + /// The <see cref="Grpc.Core.Interceptors.ClientInterceptorContext{TRequest, TResponse}"/> + /// associated with the current invocation. + /// </param> + /// <param name="continuation"> + /// The callback that continues the invocation process. + /// This can be invoked zero or more times by the interceptor. + /// The interceptor can invoke the continuation passing the given + /// request value and context arguments, or substitute them as it sees fit. + /// </param> + /// <returns> + /// An instance of <see cref="Grpc.Core.AsyncUnaryCall{TResponse}" /> + /// representing an asynchronous unary invocation. + /// The interceptor can simply return the return value of the + /// continuation delegate passed to it intact, or construct its + /// own substitute as it sees fit. + /// </returns> + public virtual AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation) + where TRequest : class + where TResponse : class + { + return continuation(request, context); + } + + /// <summary> + /// Intercepts an asynchronous invocation of a streaming remote call. + /// </summary> + /// <param name="request">The request message of the invocation.</param> + /// <param name="context"> + /// The <see cref="Grpc.Core.Interceptors.ClientInterceptorContext{TRequest, TResponse}"/> + /// associated with the current invocation. + /// </param> + /// <param name="continuation"> + /// The callback that continues the invocation process. + /// This can be invoked zero or more times by the interceptor. + /// The interceptor can invoke the continuation passing the given + /// request value and context arguments, or substitute them as it sees fit. + /// </param> + /// <returns> + /// An instance of <see cref="Grpc.Core.AsyncServerStreamingCall{TResponse}" /> + /// representing an asynchronous server-streaming invocation. + /// The interceptor can simply return the return value of the + /// continuation delegate passed to it intact, or construct its + /// own substitute as it sees fit. + /// </returns> + public virtual AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation) + where TRequest : class + where TResponse : class + { + return continuation(request, context); + } + + /// <summary> + /// Intercepts an asynchronous invocation of a client streaming call. + /// </summary> + /// <param name="context"> + /// The <see cref="Grpc.Core.Interceptors.ClientInterceptorContext{TRequest, TResponse}"/> + /// associated with the current invocation. + /// </param> + /// <param name="continuation"> + /// The callback that continues the invocation process. + /// This can be invoked zero or more times by the interceptor. + /// The interceptor can invoke the continuation passing the given + /// context argument, or substitute as it sees fit. + /// </param> + /// <returns> + /// An instance of <see cref="Grpc.Core.AsyncClientStreamingCall{TRequest, TResponse}" /> + /// representing an asynchronous client-streaming invocation. + /// The interceptor can simply return the return value of the + /// continuation delegate passed to it intact, or construct its + /// own substitute as it sees fit. + /// </returns> + public virtual AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation) + where TRequest : class + where TResponse : class + { + return continuation(context); + } + + /// <summary> + /// Intercepts an asynchronous invocation of a duplex streaming call. + /// </summary> + /// <param name="context"> + /// The <see cref="Grpc.Core.Interceptors.ClientInterceptorContext{TRequest, TResponse}"/> + /// associated with the current invocation. + /// </param> + /// <param name="continuation"> + /// The callback that continues the invocation process. + /// This can be invoked zero or more times by the interceptor. + /// The interceptor can invoke the continuation passing the given + /// context argument, or substitute as it sees fit. + /// </param> + /// <returns> + /// An instance of <see cref="Grpc.Core.AsyncDuplexStreamingCall{TRequest, TResponse}" /> + /// representing an asynchronous duplex-streaming invocation. + /// The interceptor can simply return the return value of the + /// continuation delegate passed to it intact, or construct its + /// own substitute as it sees fit. + /// </returns> + public virtual AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncDuplexStreamingCallContinuation<TRequest, TResponse> continuation) + where TRequest : class + where TResponse : class + { + return continuation(context); + } + + /// <summary> + /// Server-side handler for intercepting and incoming unary call. + /// </summary> + /// <typeparam name="TRequest">Request message type for this method.</typeparam> + /// <typeparam name="TResponse">Response message type for this method.</typeparam> + /// <param name="request">The request value of the incoming invocation.</param> + /// <param name="context"> + /// An instance of <see cref="Grpc.Core.ServerCallContext" /> representing + /// the context of the invocation. + /// </param> + /// <param name="continuation"> + /// A delegate that asynchronously proceeds with the invocation, calling + /// the next interceptor in the chain, or the service request handler, + /// in case of the last interceptor and return the response value of + /// the RPC. The interceptor can choose to call it zero or more times + /// at its discretion. + /// </param> + /// <returns> + /// A future representing the response value of the RPC. The interceptor + /// can simply return the return value from the continuation intact, + /// or an arbitrary response value as it sees fit. + /// </returns> + public virtual Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation) + where TRequest : class + where TResponse : class + { + return continuation(request, context); + } + + /// <summary> + /// Server-side handler for intercepting client streaming call. + /// </summary> + /// <typeparam name="TRequest">Request message type for this method.</typeparam> + /// <typeparam name="TResponse">Response message type for this method.</typeparam> + /// <param name="requestStream">The request stream of the incoming invocation.</param> + /// <param name="context"> + /// An instance of <see cref="Grpc.Core.ServerCallContext" /> representing + /// the context of the invocation. + /// </param> + /// <param name="continuation"> + /// A delegate that asynchronously proceeds with the invocation, calling + /// the next interceptor in the chain, or the service request handler, + /// in case of the last interceptor and return the response value of + /// the RPC. The interceptor can choose to call it zero or more times + /// at its discretion. + /// </param> + /// <returns> + /// A future representing the response value of the RPC. The interceptor + /// can simply return the return value from the continuation intact, + /// or an arbitrary response value as it sees fit. The interceptor has + /// the ability to wrap or substitute the request stream when calling + /// the continuation. + /// </returns> + public virtual Task<TResponse> ClientStreamingServerHandler<TRequest, TResponse>(IAsyncStreamReader<TRequest> requestStream, ServerCallContext context, ClientStreamingServerMethod<TRequest, TResponse> continuation) + where TRequest : class + where TResponse : class + { + return continuation(requestStream, context); + } + + /// <summary> + /// Server-side handler for intercepting server streaming call. + /// </summary> + /// <typeparam name="TRequest">Request message type for this method.</typeparam> + /// <typeparam name="TResponse">Response message type for this method.</typeparam> + /// <param name="request">The request value of the incoming invocation.</param> + /// <param name="responseStream">The response stream of the incoming invocation.</param> + /// <param name="context"> + /// An instance of <see cref="Grpc.Core.ServerCallContext" /> representing + /// the context of the invocation. + /// </param> + /// <param name="continuation"> + /// A delegate that asynchronously proceeds with the invocation, calling + /// the next interceptor in the chain, or the service request handler, + /// in case of the last interceptor and the interceptor can choose to + /// call it zero or more times at its discretion. The interceptor has + /// the ability to wrap or substitute the request value and the response stream + /// when calling the continuation. + /// </param> + public virtual Task ServerStreamingServerHandler<TRequest, TResponse>(TRequest request, IServerStreamWriter<TResponse> responseStream, ServerCallContext context, ServerStreamingServerMethod<TRequest, TResponse> continuation) + where TRequest : class + where TResponse : class + { + return continuation(request, responseStream, context); + } + + /// <summary> + /// Server-side handler for intercepting bidirectional streaming calls. + /// </summary> + /// <typeparam name="TRequest">Request message type for this method.</typeparam> + /// <typeparam name="TResponse">Response message type for this method.</typeparam> + /// <param name="requestStream">The request stream of the incoming invocation.</param> + /// <param name="responseStream">The response stream of the incoming invocation.</param> + /// <param name="context"> + /// An instance of <see cref="Grpc.Core.ServerCallContext" /> representing + /// the context of the invocation. + /// </param> + /// <param name="continuation"> + /// A delegate that asynchronously proceeds with the invocation, calling + /// the next interceptor in the chain, or the service request handler, + /// in case of the last interceptor and the interceptor can choose to + /// call it zero or more times at its discretion. The interceptor has + /// the ability to wrap or substitute the request and response streams + /// when calling the continuation. + /// </param> + public virtual Task DuplexStreamingServerHandler<TRequest, TResponse>(IAsyncStreamReader<TRequest> requestStream, IServerStreamWriter<TResponse> responseStream, ServerCallContext context, DuplexStreamingServerMethod<TRequest, TResponse> continuation) + where TRequest : class + where TResponse : class + { + return continuation(requestStream, responseStream, context); + } + } +} diff --git a/src/csharp/Grpc.Core/Interceptors/ServerServiceDefinitionExtensions.cs b/src/csharp/Grpc.Core/Interceptors/ServerServiceDefinitionExtensions.cs new file mode 100644 index 0000000000..b9b53247ce --- /dev/null +++ b/src/csharp/Grpc.Core/Interceptors/ServerServiceDefinitionExtensions.cs @@ -0,0 +1,82 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License 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. + +#endregion + +using System; +using System.Linq; +using Grpc.Core.Utils; + +namespace Grpc.Core.Interceptors +{ + /// <summary> + /// Extends the ServerServiceDefinition class to add methods used to register interceptors on the server side. + /// This is an EXPERIMENTAL API. + /// </summary> + public static class ServerServiceDefinitionExtensions + { + /// <summary> + /// Returns a <see cref="Grpc.Core.ServerServiceDefinition" /> instance that + /// intercepts incoming calls to the underlying service handler through the given interceptor. + /// This is an EXPERIMENTAL API. + /// </summary> + /// <param name="serverServiceDefinition">The <see cref="Grpc.Core.ServerServiceDefinition" /> instance to register interceptors on.</param> + /// <param name="interceptor">The interceptor to intercept the incoming invocations with.</param> + /// <remarks> + /// Multiple interceptors can be added on top of each other by calling + /// "serverServiceDefinition.Intercept(a, b, c)". The order of invocation will be "a", "b", and then "c". + /// Interceptors can be later added to an existing intercepted service definition, effectively + /// building a chain like "serverServiceDefinition.Intercept(c).Intercept(b).Intercept(a)". Note that + /// in this case, the last interceptor added will be the first to take control. + /// </remarks> + public static ServerServiceDefinition Intercept(this ServerServiceDefinition serverServiceDefinition, Interceptor interceptor) + { + GrpcPreconditions.CheckNotNull(serverServiceDefinition, nameof(serverServiceDefinition)); + GrpcPreconditions.CheckNotNull(interceptor, nameof(interceptor)); + return new ServerServiceDefinition(serverServiceDefinition.CallHandlers.ToDictionary(x => x.Key, x => x.Value.Intercept(interceptor))); + } + + /// <summary> + /// Returns a <see cref="Grpc.Core.ServerServiceDefinition" /> instance that + /// intercepts incoming calls to the underlying service handler through the given interceptors. + /// This is an EXPERIMENTAL API. + /// </summary> + /// <param name="serverServiceDefinition">The <see cref="Grpc.Core.ServerServiceDefinition" /> instance to register interceptors on.</param> + /// <param name="interceptors"> + /// An array of interceptors to intercept the incoming invocations with. + /// Control is passed to the interceptors in the order specified. + /// </param> + /// <remarks> + /// Multiple interceptors can be added on top of each other by calling + /// "serverServiceDefinition.Intercept(a, b, c)". The order of invocation will be "a", "b", and then "c". + /// Interceptors can be later added to an existing intercepted service definition, effectively + /// building a chain like "serverServiceDefinition.Intercept(c).Intercept(b).Intercept(a)". Note that + /// in this case, the last interceptor added will be the first to take control. + /// </remarks> + public static ServerServiceDefinition Intercept(this ServerServiceDefinition serverServiceDefinition, params Interceptor[] interceptors) + { + GrpcPreconditions.CheckNotNull(serverServiceDefinition, nameof(serverServiceDefinition)); + GrpcPreconditions.CheckNotNull(interceptors, nameof(interceptors)); + + foreach (var interceptor in interceptors.Reverse()) + { + serverServiceDefinition = Intercept(serverServiceDefinition, interceptor); + } + + return serverServiceDefinition; + } + } +}
\ No newline at end of file diff --git a/src/csharp/Grpc.Core/Internal/InterceptingCallInvoker.cs b/src/csharp/Grpc.Core/Internal/InterceptingCallInvoker.cs deleted file mode 100644 index eb4c7d97a7..0000000000 --- a/src/csharp/Grpc.Core/Internal/InterceptingCallInvoker.cs +++ /dev/null @@ -1,119 +0,0 @@ -#region Copyright notice and license - -// Copyright 2015-2016 gRPC authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License 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. - -#endregion - -using System; -using System.Threading.Tasks; -using Grpc.Core; -using Grpc.Core.Utils; - -namespace Grpc.Core.Internal -{ - /// <summary> - /// Decorates an underlying <c>CallInvoker</c> to intercept call invocations. - /// </summary> - internal class InterceptingCallInvoker : CallInvoker - { - readonly CallInvoker callInvoker; - readonly Func<string, string> hostInterceptor; - readonly Func<CallOptions, CallOptions> callOptionsInterceptor; - - /// <summary> - /// Initializes a new instance of the <see cref="Grpc.Core.Internal.InterceptingCallInvoker"/> class. - /// </summary> - public InterceptingCallInvoker(CallInvoker callInvoker, - Func<string, string> hostInterceptor = null, - Func<CallOptions, CallOptions> callOptionsInterceptor = null) - { - this.callInvoker = GrpcPreconditions.CheckNotNull(callInvoker); - this.hostInterceptor = hostInterceptor; - this.callOptionsInterceptor = callOptionsInterceptor; - } - - /// <summary> - /// Intercepts a unary call. - /// </summary> - public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) - { - host = InterceptHost(host); - options = InterceptCallOptions(options); - return callInvoker.BlockingUnaryCall(method, host, options, request); - } - - /// <summary> - /// Invokes a simple remote call asynchronously. - /// </summary> - public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) - { - host = InterceptHost(host); - options = InterceptCallOptions(options); - return callInvoker.AsyncUnaryCall(method, host, options, request); - } - - /// <summary> - /// Invokes a server streaming call asynchronously. - /// In server streaming scenario, client sends on request and server responds with a stream of responses. - /// </summary> - public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) - { - host = InterceptHost(host); - options = InterceptCallOptions(options); - return callInvoker.AsyncServerStreamingCall(method, host, options, request); - } - - /// <summary> - /// Invokes a client streaming call asynchronously. - /// In client streaming scenario, client sends a stream of requests and server responds with a single response. - /// </summary> - public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options) - { - host = InterceptHost(host); - options = InterceptCallOptions(options); - return callInvoker.AsyncClientStreamingCall(method, host, options); - } - - /// <summary> - /// Invokes a duplex streaming call asynchronously. - /// In duplex streaming scenario, client sends a stream of requests and server responds with a stream of responses. - /// The response stream is completely independent and both side can be sending messages at the same time. - /// </summary> - public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options) - { - host = InterceptHost(host); - options = InterceptCallOptions(options); - return callInvoker.AsyncDuplexStreamingCall(method, host, options); - } - - private string InterceptHost(string host) - { - if (hostInterceptor == null) - { - return host; - } - return hostInterceptor(host); - } - - private CallOptions InterceptCallOptions(CallOptions options) - { - if (callOptionsInterceptor == null) - { - return options; - } - return callOptionsInterceptor(options); - } - } -} diff --git a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs index 98995a0862..81522cf8fe 100644 --- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs +++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Grpc.Core.Interceptors; using Grpc.Core.Internal; using Grpc.Core.Logging; using Grpc.Core.Utils; @@ -30,6 +31,7 @@ namespace Grpc.Core.Internal internal interface IServerCallHandler { Task HandleCall(ServerRpcNew newRpc, CompletionQueueSafeHandle cq); + IServerCallHandler Intercept(Interceptor interceptor); } internal class UnaryServerCallHandler<TRequest, TResponse> : IServerCallHandler @@ -74,7 +76,7 @@ namespace Grpc.Core.Internal { if (!(e is RpcException)) { - Logger.Warning(e, "Exception occured in handler."); + Logger.Warning(e, "Exception occurred in the handler or an interceptor."); } status = HandlerUtils.GetStatusFromExceptionAndMergeTrailers(e, context.ResponseTrailers); } @@ -89,6 +91,11 @@ namespace Grpc.Core.Internal } await finishedTask.ConfigureAwait(false); } + + public IServerCallHandler Intercept(Interceptor interceptor) + { + return new UnaryServerCallHandler<TRequest, TResponse>(method, (request, context) => interceptor.UnaryServerHandler(request, context, handler)); + } } internal class ServerStreamingServerCallHandler<TRequest, TResponse> : IServerCallHandler @@ -131,7 +138,7 @@ namespace Grpc.Core.Internal { if (!(e is RpcException)) { - Logger.Warning(e, "Exception occured in handler."); + Logger.Warning(e, "Exception occurred in the handler or an interceptor."); } status = HandlerUtils.GetStatusFromExceptionAndMergeTrailers(e, context.ResponseTrailers); } @@ -147,6 +154,11 @@ namespace Grpc.Core.Internal } await finishedTask.ConfigureAwait(false); } + + public IServerCallHandler Intercept(Interceptor interceptor) + { + return new ServerStreamingServerCallHandler<TRequest, TResponse>(method, (request, responseStream, context) => interceptor.ServerStreamingServerHandler(request, responseStream, context, handler)); + } } internal class ClientStreamingServerCallHandler<TRequest, TResponse> : IServerCallHandler @@ -189,7 +201,7 @@ namespace Grpc.Core.Internal { if (!(e is RpcException)) { - Logger.Warning(e, "Exception occured in handler."); + Logger.Warning(e, "Exception occurred in the handler or an interceptor."); } status = HandlerUtils.GetStatusFromExceptionAndMergeTrailers(e, context.ResponseTrailers); } @@ -205,6 +217,11 @@ namespace Grpc.Core.Internal } await finishedTask.ConfigureAwait(false); } + + public IServerCallHandler Intercept(Interceptor interceptor) + { + return new ClientStreamingServerCallHandler<TRequest, TResponse>(method, (requestStream, context) => interceptor.ClientStreamingServerHandler(requestStream, context, handler)); + } } internal class DuplexStreamingServerCallHandler<TRequest, TResponse> : IServerCallHandler @@ -245,7 +262,7 @@ namespace Grpc.Core.Internal { if (!(e is RpcException)) { - Logger.Warning(e, "Exception occured in handler."); + Logger.Warning(e, "Exception occurred in the handler or an interceptor."); } status = HandlerUtils.GetStatusFromExceptionAndMergeTrailers(e, context.ResponseTrailers); } @@ -260,6 +277,11 @@ namespace Grpc.Core.Internal } await finishedTask.ConfigureAwait(false); } + + public IServerCallHandler Intercept(Interceptor interceptor) + { + return new DuplexStreamingServerCallHandler<TRequest, TResponse>(method, (requestStream, responseStream, context) => interceptor.DuplexStreamingServerHandler(requestStream, responseStream, context, handler)); + } } internal class UnimplementedMethodCallHandler : IServerCallHandler @@ -288,6 +310,11 @@ namespace Grpc.Core.Internal { return callHandlerImpl.HandleCall(newRpc, cq); } + + public IServerCallHandler Intercept(Interceptor interceptor) + { + return this; // Do not intercept unimplemented methods. + } } internal static class HandlerUtils diff --git a/src/csharp/Grpc.Core/ServerServiceDefinition.cs b/src/csharp/Grpc.Core/ServerServiceDefinition.cs index 59868c1f40..07c6aa1796 100644 --- a/src/csharp/Grpc.Core/ServerServiceDefinition.cs +++ b/src/csharp/Grpc.Core/ServerServiceDefinition.cs @@ -19,7 +19,10 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; +using Grpc.Core.Interceptors; using Grpc.Core.Internal; +using Grpc.Core.Utils; namespace Grpc.Core { @@ -32,7 +35,7 @@ namespace Grpc.Core { readonly ReadOnlyDictionary<string, IServerCallHandler> callHandlers; - private ServerServiceDefinition(Dictionary<string, IServerCallHandler> callHandlers) + internal ServerServiceDefinition(Dictionary<string, IServerCallHandler> callHandlers) { this.callHandlers = new ReadOnlyDictionary<string, IServerCallHandler>(callHandlers); } diff --git a/src/csharp/tests.json b/src/csharp/tests.json index 469328af1a..60f67ff3c9 100644 --- a/src/csharp/tests.json +++ b/src/csharp/tests.json @@ -1,5 +1,7 @@ { "Grpc.Core.Tests": [ + "Grpc.Core.Interceptors.Tests.ClientInterceptorTest", + "Grpc.Core.Interceptors.Tests.ServerInterceptorTest", "Grpc.Core.Internal.Tests.AsyncCallServerTest", "Grpc.Core.Internal.Tests.AsyncCallTest", "Grpc.Core.Internal.Tests.ChannelArgsSafeHandleTest", @@ -59,4 +61,4 @@ "Grpc.Reflection.Tests.ReflectionClientServerTest", "Grpc.Reflection.Tests.SymbolRegistryTest" ] -}
\ No newline at end of file +} diff --git a/src/objective-c/tests/CoreCronetEnd2EndTests/CoreCronetEnd2EndTests.mm b/src/objective-c/tests/CoreCronetEnd2EndTests/CoreCronetEnd2EndTests.mm index d91b5cf99e..33ccdb5844 100644 --- a/src/objective-c/tests/CoreCronetEnd2EndTests/CoreCronetEnd2EndTests.mm +++ b/src/objective-c/tests/CoreCronetEnd2EndTests/CoreCronetEnd2EndTests.mm @@ -224,8 +224,7 @@ static char *roots_filename; } - (void)testBinaryMetadata { - // NOT SUPPORTED - //[self testIndividualCase:(char *)"binary_metadata"]; + [self testIndividualCase:(char *)"binary_metadata"]; } - (void)testCallCreds { diff --git a/templates/gRPC-C++.podspec.template b/templates/gRPC-C++.podspec.template index 78adb27915..12d5fc17d4 100644 --- a/templates/gRPC-C++.podspec.template +++ b/templates/gRPC-C++.podspec.template @@ -30,6 +30,9 @@ out += lib.get(group, []) return out + def filter_grpcpp(files): + return [file for file in files if not file.startswith("include/grpc++")] + def grpc_private_files(libs): out = grpc_lib_files(libs, ("grpc", "gpr"), ("headers", "src")) return out @@ -59,6 +62,9 @@ # Since some C++ source files directly included private headers in C core, we include all the # C core headers in C++ Implementation subspec as well. out += [file for file in grpc_private_headers(libs) if not file.startswith("third_party/nanopb/")] + + out = filter_grpcpp(out) + return out def grpcpp_private_headers(libs, filegroups): @@ -71,6 +77,8 @@ # Since some C++ source files directly included private headers in C core, we intentionally # keep the C core headers in \a out. But we should exclude nanopb headers. out = [file for file in out if not file.startswith("third_party/nanopb/")] + + out = filter_grpcpp(out) return out def grpcpp_public_headers(libs, filegroups): @@ -81,6 +89,9 @@ excl_files += grpcpp_proto_files(filegroups) out = [file for file in out if file not in excl_files] + + out = filter_grpcpp(out) + return out def grpc_test_util_files(libs): @@ -91,6 +102,8 @@ out = grpc_lib_files(libs, ("grpc_test_util", "gpr_test_util"), ("headers",)) return out + # Tests subspec is currently disabled since the tests currently use `grpc++` include style instead of `grpcpp`. + # TODO (mxyan): enable Tests subspec after the inclusion style is updated in `test/` directory. def grpcpp_test_util_files(libs, filegroups): out = grpc_lib_files(libs, ("grpc++_test_util",), ("src", "headers")) excl_files = grpc_test_util_files(libs) + grpcpp_private_files(libs, filegroups) @@ -118,7 +131,7 @@ s.name = 'gRPC-C++' # TODO (mxyan): use version that match gRPC version when pod is stabilized # version = '${settings.version}' - version = '0.0.1' + version = '0.0.2' s.version = version s.summary = 'gRPC C++ library' s.homepage = 'https://grpc.io' @@ -136,8 +149,14 @@ s.osx.deployment_target = '10.9' s.requires_arc = false - # Add include prefix `grpc++` (i.e. `#include <grpc++/xxx.h>`). - s.header_dir = 'grpc++' + name = 'grpcpp' + # Use `grpcpp` as framework name so that `#include <grpcpp/xxx.h>` works when built as + # framework. + s.module_name = name + + # Add include prefix `grpcpp` so that `#include <grpcpp/xxx.h>` works when built as static + # library. + s.header_dir = name s.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '"$(inherited)" "$(PODS_TARGET_SRCROOT)/include"', @@ -157,8 +176,10 @@ s.default_subspecs = 'Interface', 'Implementation' + s.header_mappings_dir = 'include/grpcpp' + s.subspec 'Interface' do |ss| - ss.header_mappings_dir = 'include/grpc++' + ss.header_mappings_dir = 'include/grpcpp' ss.source_files = ${ruby_multiline_list(grpcpp_public_headers(libs, filegroups), 22)} end @@ -174,16 +195,6 @@ ss.private_header_files = ${ruby_multiline_list(grpcpp_private_headers(libs, filegroups), 30)} end - s.subspec 'Tests' do |ss| - ss.header_mappings_dir = '.' - - ss.dependency "#{s.name}/Interface", version - ss.dependency "#{s.name}/Implementation", version - ss.dependency "gRPC-Core/Tests", grpc_version - - ss.source_files = ${ruby_multiline_list(grpcpp_test_util_files(libs, filegroups), 22)} - end - s.prepare_command = <<-END_OF_COMMAND find src/cpp/ -type f -exec sed -E -i'.back' 's;#include "third_party/nanopb/(.*)";#include <nanopb/\\1>;g' {} \\\; find src/cpp/ -name "*.back" -type f -delete diff --git a/test/core/end2end/dualstack_socket_test.cc b/test/core/end2end/dualstack_socket_test.cc index 411d0f2308..eb1d043fb2 100644 --- a/test/core/end2end/dualstack_socket_test.cc +++ b/test/core/end2end/dualstack_socket_test.cc @@ -166,7 +166,7 @@ void test_connect(const char* server_host, const char* client_host, int port, } else { /* Give up faster when failure is expected. BUG: Setting this to 1000 reveals a memory leak (b/18608927). */ - deadline = grpc_timeout_milliseconds_to_deadline(3000); + deadline = grpc_timeout_milliseconds_to_deadline(8000); } /* Send a trivial request. */ diff --git a/test/core/gpr/BUILD b/test/core/gpr/BUILD index 9bd4c2feff..5308ea0934 100644 --- a/test/core/gpr/BUILD +++ b/test/core/gpr/BUILD @@ -29,6 +29,16 @@ grpc_cc_test( ) grpc_cc_test( + name = "arena_test", + srcs = ["arena_test.cc"], + language = "C++", + deps = [ + "//:gpr", + "//test/core/util:gpr_test_util", + ], +) + +grpc_cc_test( name = "cpu_test", srcs = ["cpu_test.cc"], language = "C++", diff --git a/test/core/iomgr/BUILD b/test/core/iomgr/BUILD index 41e2607646..349a06d578 100644 --- a/test/core/iomgr/BUILD +++ b/test/core/iomgr/BUILD @@ -60,6 +60,19 @@ grpc_cc_test( ) grpc_cc_test( + name = "error_test", + srcs = ["error_test.cc"], + language = "C++", + deps = [ + ":endpoint_tests", + "//:gpr", + "//:grpc", + "//test/core/util:gpr_test_util", + "//test/core/util:grpc_test_util", + ], +) + +grpc_cc_test( name = "ev_epollsig_linux_test", srcs = ["ev_epollsig_linux_test.cc"], deps = [ diff --git a/test/core/security/BUILD b/test/core/security/BUILD index 6eaf0a19ce..9776e6d5fd 100644 --- a/test/core/security/BUILD +++ b/test/core/security/BUILD @@ -67,6 +67,31 @@ grpc_cc_test( ) grpc_cc_test( + name = "json_token_test", + srcs = ["json_token_test.cc"], + language = "C++", + deps = [ + "//:gpr", + "//:grpc", + "//test/core/util:gpr_test_util", + "//test/core/util:grpc_test_util", + ], +) + +grpc_cc_test( + name = "jwt_verifier_test", + srcs = ["jwt_verifier_test.cc"], + language = "C++", + deps = [ + "//:gpr", + "//:grpc", + "//test/core/util:gpr_test_util", + "//test/core/util:grpc_test_util", + ], +) + + +grpc_cc_test( name = "secure_endpoint_test", srcs = ["secure_endpoint_test.cc"], language = "C++", diff --git a/test/core/surface/BUILD b/test/core/surface/BUILD index d27123d1a3..e848dded13 100644 --- a/test/core/surface/BUILD +++ b/test/core/surface/BUILD @@ -55,6 +55,18 @@ grpc_cc_test( ) grpc_cc_test( + name = "completion_queue_threading_test", + srcs = ["completion_queue_threading_test.cc"], + language = "C++", + deps = [ + "//:gpr", + "//:grpc", + "//test/core/util:gpr_test_util", + "//test/core/util:grpc_test_util", + ], +) + +grpc_cc_test( name = "concurrent_connectivity_test", srcs = ["concurrent_connectivity_test.cc"], language = "C++", @@ -104,6 +116,19 @@ grpc_cc_test( ) grpc_cc_test( + name = "num_external_connectivity_watchers_test", + srcs = ["num_external_connectivity_watchers_test.cc"], + language = "C++", + deps = [ + "//:gpr", + "//:grpc", + "//test/core/end2end:ssl_test_data", + "//test/core/util:gpr_test_util", + "//test/core/util:grpc_test_util", + ], +) + +grpc_cc_test( name = "public_headers_must_be_c89", srcs = ["public_headers_must_be_c89.c"], language = "C", diff --git a/test/core/transport/chttp2/bin_decoder_test.cc b/test/core/transport/chttp2/bin_decoder_test.cc index 283eebbacf..751dd90c8c 100644 --- a/test/core/transport/chttp2/bin_decoder_test.cc +++ b/test/core/transport/chttp2/bin_decoder_test.cc @@ -67,6 +67,16 @@ static grpc_slice base64_decode_with_length(const char* s, return out; } +static size_t base64_infer_length(const char* s) { + grpc_slice ss = grpc_slice_from_copied_string(s); + size_t out = grpc_chttp2_base64_infer_length_after_decode(ss); + grpc_slice_unref_internal(ss); + return out; +} + +#define EXPECT_DECODED_LENGTH(s, expected) \ + GPR_ASSERT((expected) == base64_infer_length((s))); + #define EXPECT_SLICE_EQ(expected, slice) \ expect_slice_eq( \ grpc_slice_from_copied_buffer(expected, sizeof(expected) - 1), slice, \ @@ -131,6 +141,26 @@ int main(int argc, char** argv) { // Test illegal charactors in grpc_chttp2_base64_decode_with_length EXPECT_SLICE_EQ("", base64_decode_with_length("Zm:v", 3)); EXPECT_SLICE_EQ("", base64_decode_with_length("Zm=v", 3)); + + EXPECT_DECODED_LENGTH("", 0); + EXPECT_DECODED_LENGTH("ab", 1); + EXPECT_DECODED_LENGTH("abc", 2); + EXPECT_DECODED_LENGTH("abcd", 3); + EXPECT_DECODED_LENGTH("abcdef", 4); + EXPECT_DECODED_LENGTH("abcdefg", 5); + EXPECT_DECODED_LENGTH("abcdefgh", 6); + + EXPECT_DECODED_LENGTH("ab==", 1); + EXPECT_DECODED_LENGTH("abc=", 2); + EXPECT_DECODED_LENGTH("abcd", 3); + EXPECT_DECODED_LENGTH("abcdef==", 4); + EXPECT_DECODED_LENGTH("abcdefg=", 5); + EXPECT_DECODED_LENGTH("abcdefgh", 6); + + EXPECT_DECODED_LENGTH("a", 0); + EXPECT_DECODED_LENGTH("a===", 0); + EXPECT_DECODED_LENGTH("abcde", 0); + EXPECT_DECODED_LENGTH("abcde===", 0); } grpc_shutdown(); return all_ok ? 0 : 1; diff --git a/test/cpp/end2end/BUILD b/test/cpp/end2end/BUILD index afa054ae10..8ab0811ffa 100644 --- a/test/cpp/end2end/BUILD +++ b/test/cpp/end2end/BUILD @@ -201,6 +201,27 @@ grpc_cc_test( ) grpc_cc_test( + name = "health_service_end2end_test", + srcs = ["health_service_end2end_test.cc"], + external_deps = [ + "gtest", + ], + deps = [ + ":test_service_impl", + "//:gpr", + "//:grpc", + "//:grpc++", + "//src/proto/grpc/health/v1:health_proto", + "//src/proto/grpc/testing:echo_messages_proto", + "//src/proto/grpc/testing:echo_proto", + "//src/proto/grpc/testing/duplicate:echo_duplicate_proto", + "//test/core/util:gpr_test_util", + "//test/core/util:grpc_test_util", + "//test/cpp/util:test_util", + ], +) + +grpc_cc_test( name = "hybrid_end2end_test", srcs = ["hybrid_end2end_test.cc"], external_deps = [ diff --git a/test/cpp/grpclb/BUILD b/test/cpp/grpclb/BUILD new file mode 100644 index 0000000000..8319eb5142 --- /dev/null +++ b/test/cpp/grpclb/BUILD @@ -0,0 +1,39 @@ +# Copyright 2017 gRPC authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +licenses(["notice"]) # Apache v2 + +load("//bazel:grpc_build_system.bzl", "grpc_cc_library", "grpc_cc_test", "grpc_package", "grpc_cc_binary") + +grpc_package( + name = "test/cpp/grpclb", + visibility = "public", +) # Allows external users to implement grpclb tests. + +grpc_cc_test( + name = "grpclb_api_test", + srcs = ["grpclb_api_test.cc"], + external_deps = [ + "gtest", + ], + deps = [ + "//:gpr", + "//:grpc", + "//:grpc++", + "//src/proto/grpc/lb/v1:load_balancer_proto", + "//test/core/util:gpr_test_util", + "//test/core/util:grpc_test_util", + "//test/cpp/util:test_util", + ], +) diff --git a/test/cpp/test/BUILD b/test/cpp/test/BUILD new file mode 100644 index 0000000000..c549478919 --- /dev/null +++ b/test/cpp/test/BUILD @@ -0,0 +1,39 @@ +# Copyright 2017 gRPC authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +licenses(["notice"]) # Apache v2 + +load("//bazel:grpc_build_system.bzl", "grpc_cc_library", "grpc_cc_test", "grpc_package", "grpc_cc_binary") + +grpc_package( + name = "test/cpp/test", + visibility = "public", +) + +grpc_cc_test( + name = "server_context_test_spouse_test", + srcs = ["server_context_test_spouse_test.cc"], + external_deps = [ + "gtest", + ], + deps = [ + "//:gpr", + "//:grpc", + "//:grpc++", + "//:grpc++_test", + "//test/core/util:gpr_test_util", + "//test/core/util:grpc_test_util", + "//test/cpp/util:test_util", + ], +) diff --git a/test/cpp/thread_manager/BUILD b/test/cpp/thread_manager/BUILD new file mode 100644 index 0000000000..093e51e3fa --- /dev/null +++ b/test/cpp/thread_manager/BUILD @@ -0,0 +1,40 @@ +# Copyright 2017 gRPC authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +licenses(["notice"]) # Apache v2 + +load("//bazel:grpc_build_system.bzl", "grpc_cc_library", "grpc_cc_test", "grpc_package", "grpc_cc_binary") + +grpc_package( + name = "test/cpp/thread_manager", + visibility = "public", +) + +grpc_cc_test( + name = "thread_manager_test", + srcs = ["thread_manager_test.cc"], + external_deps = [ + "gflags", + "gtest", + ], + deps = [ + "//:gpr", + "//:grpc", + "//:grpc++", + "//test/core/util:gpr_test_util", + "//test/core/util:grpc_test_util", + "//test/cpp/util:test_config", + "//test/cpp/util:test_util", + ], +) diff --git a/tools/run_tests/sanity/check_deprecated_grpc++.py b/tools/run_tests/sanity/check_deprecated_grpc++.py index e0779ba0e5..4ec49fae39 100755 --- a/tools/run_tests/sanity/check_deprecated_grpc++.py +++ b/tools/run_tests/sanity/check_deprecated_grpc++.py @@ -170,4 +170,22 @@ for path_file in expected_files: os.remove(path_file_expected) +check_extensions = [".h", ".cc", ".c", ".m"] + +for root, dirs, files in os.walk('src'): + for filename in files: + path_file = os.path.join(root, filename) + for ext in check_extensions: + if path_file.endswith(ext): + try: + with open(path_file, "r") as fi: + content = fi.read() + if '#include <grpc++/' in content: + print( + 'Failed: invalid include of deprecated headers in include/grpc++ in %s' + % path_file) + errors += 1 + except IOError: + pass + sys.exit(errors) |