mirror of https://github.com/grpc/grpc.git
commit
a6793f2dd6
225 changed files with 9663 additions and 708 deletions
@ -0,0 +1,332 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2015, Google Inc. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are |
||||
* met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following disclaimer |
||||
* in the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* * Neither the name of Google Inc. nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
*/ |
||||
|
||||
#include <cassert> |
||||
#include <cctype> |
||||
#include <map> |
||||
#include <ostream> |
||||
#include <sstream> |
||||
|
||||
#include "src/compiler/python_generator.h" |
||||
#include <google/protobuf/io/printer.h> |
||||
#include <google/protobuf/io/zero_copy_stream_impl_lite.h> |
||||
#include <google/protobuf/descriptor.pb.h> |
||||
#include <google/protobuf/descriptor.h> |
||||
|
||||
using google::protobuf::FileDescriptor; |
||||
using google::protobuf::ServiceDescriptor; |
||||
using google::protobuf::MethodDescriptor; |
||||
using google::protobuf::io::Printer; |
||||
using google::protobuf::io::StringOutputStream; |
||||
using std::initializer_list; |
||||
using std::map; |
||||
using std::string; |
||||
|
||||
namespace grpc_python_generator { |
||||
namespace { |
||||
//////////////////////////////////
|
||||
// BEGIN FORMATTING BOILERPLATE //
|
||||
//////////////////////////////////
|
||||
|
||||
// Converts an initializer list of the form { key0, value0, key1, value1, ... }
|
||||
// into a map of key* to value*. Is merely a readability helper for later code.
|
||||
map<string, string> ListToDict(const initializer_list<string>& values) { |
||||
assert(values.size() % 2 == 0); |
||||
map<string, string> value_map; |
||||
auto value_iter = values.begin(); |
||||
for (unsigned i = 0; i < values.size()/2; ++i) { |
||||
string key = *value_iter; |
||||
++value_iter; |
||||
string value = *value_iter; |
||||
value_map[key] = value; |
||||
++value_iter; |
||||
} |
||||
return value_map; |
||||
} |
||||
|
||||
// Provides RAII indentation handling. Use as:
|
||||
// {
|
||||
// IndentScope raii_my_indent_var_name_here(my_py_printer);
|
||||
// // constructor indented my_py_printer
|
||||
// ...
|
||||
// // destructor called at end of scope, un-indenting my_py_printer
|
||||
// }
|
||||
class IndentScope { |
||||
public: |
||||
explicit IndentScope(Printer* printer) : printer_(printer) { |
||||
printer_->Indent(); |
||||
} |
||||
|
||||
~IndentScope() { |
||||
printer_->Outdent(); |
||||
} |
||||
|
||||
private: |
||||
Printer* printer_; |
||||
}; |
||||
|
||||
////////////////////////////////
|
||||
// END FORMATTING BOILERPLATE //
|
||||
////////////////////////////////
|
||||
|
||||
void PrintService(const ServiceDescriptor* service, |
||||
Printer* out) { |
||||
string doc = "<fill me in later!>"; |
||||
map<string, string> dict = ListToDict({ |
||||
"Service", service->name(), |
||||
"Documentation", doc, |
||||
}); |
||||
out->Print(dict, "class $Service$Service(object):\n"); |
||||
{ |
||||
IndentScope raii_class_indent(out); |
||||
out->Print(dict, "\"\"\"$Documentation$\"\"\"\n"); |
||||
out->Print("def __init__(self):\n"); |
||||
{ |
||||
IndentScope raii_method_indent(out); |
||||
out->Print("pass\n"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void PrintServicer(const ServiceDescriptor* service, |
||||
Printer* out) { |
||||
string doc = "<fill me in later!>"; |
||||
map<string, string> dict = ListToDict({ |
||||
"Service", service->name(), |
||||
"Documentation", doc, |
||||
}); |
||||
out->Print(dict, "class $Service$Servicer(object):\n"); |
||||
{ |
||||
IndentScope raii_class_indent(out); |
||||
out->Print(dict, "\"\"\"$Documentation$\"\"\"\n"); |
||||
for (int i = 0; i < service->method_count(); ++i) { |
||||
auto meth = service->method(i); |
||||
out->Print("def $Method$(self, arg):\n", "Method", meth->name()); |
||||
{ |
||||
IndentScope raii_method_indent(out); |
||||
out->Print("raise NotImplementedError()\n"); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
void PrintStub(const ServiceDescriptor* service, |
||||
Printer* out) { |
||||
string doc = "<fill me in later!>"; |
||||
map<string, string> dict = ListToDict({ |
||||
"Service", service->name(), |
||||
"Documentation", doc, |
||||
}); |
||||
out->Print(dict, "class $Service$Stub(object):\n"); |
||||
{ |
||||
IndentScope raii_class_indent(out); |
||||
out->Print(dict, "\"\"\"$Documentation$\"\"\"\n"); |
||||
for (int i = 0; i < service->method_count(); ++i) { |
||||
const MethodDescriptor* meth = service->method(i); |
||||
auto methdict = ListToDict({"Method", meth->name()}); |
||||
out->Print(methdict, "def $Method$(self, arg):\n"); |
||||
{ |
||||
IndentScope raii_method_indent(out); |
||||
out->Print("raise NotImplementedError()\n"); |
||||
} |
||||
out->Print(methdict, "$Method$.async = None\n"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void PrintStubImpl(const ServiceDescriptor* service, |
||||
Printer* out) { |
||||
map<string, string> dict = ListToDict({ |
||||
"Service", service->name(), |
||||
}); |
||||
out->Print(dict, "class _$Service$Stub($Service$Stub):\n"); |
||||
{ |
||||
IndentScope raii_class_indent(out); |
||||
out->Print("def __init__(self, face_stub, default_timeout):\n"); |
||||
{ |
||||
IndentScope raii_method_indent(out); |
||||
out->Print("self._face_stub = face_stub\n" |
||||
"self._default_timeout = default_timeout\n" |
||||
"stub_self = self\n"); |
||||
|
||||
for (int i = 0; i < service->method_count(); ++i) { |
||||
const MethodDescriptor* meth = service->method(i); |
||||
bool server_streaming = meth->server_streaming(); |
||||
bool client_streaming = meth->client_streaming(); |
||||
std::string blocking_call, future_call; |
||||
if (server_streaming) { |
||||
if (client_streaming) { |
||||
blocking_call = "stub_self._face_stub.inline_stream_in_stream_out"; |
||||
future_call = blocking_call; |
||||
} else { |
||||
blocking_call = "stub_self._face_stub.inline_value_in_stream_out"; |
||||
future_call = blocking_call; |
||||
} |
||||
} else { |
||||
if (client_streaming) { |
||||
blocking_call = "stub_self._face_stub.blocking_stream_in_value_out"; |
||||
future_call = "stub_self._face_stub.future_stream_in_value_out"; |
||||
} else { |
||||
blocking_call = "stub_self._face_stub.blocking_value_in_value_out"; |
||||
future_call = "stub_self._face_stub.future_value_in_value_out"; |
||||
} |
||||
} |
||||
// TODO(atash): use the solution described at
|
||||
// http://stackoverflow.com/a/2982 to bind 'async' attribute
|
||||
// functions to def'd functions instead of using callable attributes.
|
||||
auto methdict = ListToDict({ |
||||
"Method", meth->name(), |
||||
"BlockingCall", blocking_call, |
||||
"FutureCall", future_call |
||||
}); |
||||
out->Print(methdict, "class $Method$(object):\n"); |
||||
{ |
||||
IndentScope raii_callable_indent(out); |
||||
out->Print("def __call__(self, arg):\n"); |
||||
{ |
||||
IndentScope raii_callable_call_indent(out); |
||||
out->Print(methdict, |
||||
"return $BlockingCall$(\"$Method$\", arg, " |
||||
"stub_self._default_timeout)\n"); |
||||
} |
||||
out->Print("def async(self, arg):\n"); |
||||
{ |
||||
IndentScope raii_callable_async_indent(out); |
||||
out->Print(methdict, |
||||
"return $FutureCall$(\"$Method$\", arg, " |
||||
"stub_self._default_timeout)\n"); |
||||
} |
||||
} |
||||
out->Print(methdict, "self.$Method$ = $Method$()\n"); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
void PrintStubGenerators(const ServiceDescriptor* service, Printer* out) { |
||||
map<string, string> dict = ListToDict({ |
||||
"Service", service->name(), |
||||
}); |
||||
// Write out a generator of linked pairs of Server/Stub
|
||||
out->Print(dict, "def mock_$Service$(servicer, default_timeout):\n"); |
||||
{ |
||||
IndentScope raii_mock_indent(out); |
||||
out->Print("value_in_value_out = {}\n" |
||||
"value_in_stream_out = {}\n" |
||||
"stream_in_value_out = {}\n" |
||||
"stream_in_stream_out = {}\n"); |
||||
for (int i = 0; i < service->method_count(); ++i) { |
||||
const MethodDescriptor* meth = service->method(i); |
||||
std::string super_interface, meth_dict; |
||||
bool server_streaming = meth->server_streaming(); |
||||
bool client_streaming = meth->client_streaming(); |
||||
if (server_streaming) { |
||||
if (client_streaming) { |
||||
super_interface = "InlineStreamInStreamOutMethod"; |
||||
meth_dict = "stream_in_stream_out"; |
||||
} else { |
||||
super_interface = "InlineValueInStreamOutMethod"; |
||||
meth_dict = "value_in_stream_out"; |
||||
} |
||||
} else { |
||||
if (client_streaming) { |
||||
super_interface = "InlineStreamInValueOutMethod"; |
||||
meth_dict = "stream_in_value_out"; |
||||
} else { |
||||
super_interface = "InlineValueInValueOutMethod"; |
||||
meth_dict = "value_in_value_out"; |
||||
} |
||||
} |
||||
map<string, string> methdict = ListToDict({ |
||||
"Method", meth->name(), |
||||
"SuperInterface", super_interface, |
||||
"MethodDict", meth_dict |
||||
}); |
||||
out->Print( |
||||
methdict, "class $Method$(_face_interfaces.$SuperInterface$):\n"); |
||||
{ |
||||
IndentScope raii_inline_class_indent(out); |
||||
out->Print("def service(self, request, context):\n"); |
||||
{ |
||||
IndentScope raii_inline_class_fn_indent(out); |
||||
out->Print(methdict, "return servicer.$Method$(request)\n"); |
||||
} |
||||
} |
||||
out->Print(methdict, "$MethodDict$['$Method$'] = $Method$()\n"); |
||||
} |
||||
out->Print( |
||||
"face_linked_pair = _face_testing.server_and_stub(default_timeout," |
||||
"inline_value_in_value_out_methods=value_in_value_out," |
||||
"inline_value_in_stream_out_methods=value_in_stream_out," |
||||
"inline_stream_in_value_out_methods=stream_in_value_out," |
||||
"inline_stream_in_stream_out_methods=stream_in_stream_out)\n"); |
||||
out->Print("class LinkedPair(object):\n"); |
||||
{ |
||||
IndentScope raii_linked_pair(out); |
||||
out->Print("def __init__(self, server, stub):\n"); |
||||
{ |
||||
IndentScope raii_linked_pair_init(out); |
||||
out->Print("self.server = server\n" |
||||
"self.stub = stub\n"); |
||||
} |
||||
} |
||||
out->Print( |
||||
dict, |
||||
"stub = _$Service$Stub(face_linked_pair.stub, default_timeout)\n"); |
||||
out->Print("return LinkedPair(None, stub)\n"); |
||||
} |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
string GetServices(const FileDescriptor* file) { |
||||
string output; |
||||
StringOutputStream output_stream(&output); |
||||
Printer out(&output_stream, '$'); |
||||
out.Print("import abc\n"); |
||||
out.Print("import google3\n"); |
||||
out.Print("from grpc.framework.face import demonstration as _face_testing\n"); |
||||
out.Print("from grpc.framework.face import interfaces as _face_interfaces\n"); |
||||
|
||||
for (int i = 0; i < file->service_count(); ++i) { |
||||
auto service = file->service(i); |
||||
PrintService(service, &out); |
||||
PrintServicer(service, &out); |
||||
PrintStub(service, &out); |
||||
PrintStubImpl(service, &out); |
||||
PrintStubGenerators(service, &out); |
||||
} |
||||
return output; |
||||
} |
||||
|
||||
} // namespace grpc_python_generator
|
@ -0,0 +1,51 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2015, Google Inc. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are |
||||
* met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following disclaimer |
||||
* in the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* * Neither the name of Google Inc. nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
*/ |
||||
|
||||
#ifndef __GRPC_COMPILER_PYTHON_GENERATOR_H__ |
||||
#define __GRPC_COMPILER_PYTHON_GENERATOR_H__ |
||||
|
||||
#include <string> |
||||
|
||||
namespace google { |
||||
namespace protobuf { |
||||
class FileDescriptor; |
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
|
||||
namespace grpc_python_generator { |
||||
|
||||
std::string GetServices(const google::protobuf::FileDescriptor* file); |
||||
|
||||
} // namespace grpc_python_generator
|
||||
|
||||
#endif // __GRPC_COMPILER_PYTHON_GENERATOR_H__
|
@ -0,0 +1,87 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2015, Google Inc. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are |
||||
* met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following disclaimer |
||||
* in the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* * Neither the name of Google Inc. nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
*/ |
||||
|
||||
// Generates a Python gRPC service interface out of Protobuf IDL.
|
||||
|
||||
#include <memory> |
||||
#include <string> |
||||
|
||||
#include "src/compiler/python_generator.h" |
||||
#include <google/protobuf/compiler/code_generator.h> |
||||
#include <google/protobuf/compiler/plugin.h> |
||||
#include <google/protobuf/io/coded_stream.h> |
||||
#include <google/protobuf/io/zero_copy_stream.h> |
||||
#include <google/protobuf/descriptor.h> |
||||
|
||||
using google::protobuf::FileDescriptor; |
||||
using google::protobuf::compiler::CodeGenerator; |
||||
using google::protobuf::compiler::GeneratorContext; |
||||
using google::protobuf::compiler::PluginMain; |
||||
using google::protobuf::io::CodedOutputStream; |
||||
using google::protobuf::io::ZeroCopyOutputStream; |
||||
using std::string; |
||||
|
||||
class PythonGrpcGenerator : public CodeGenerator { |
||||
public: |
||||
PythonGrpcGenerator() {} |
||||
~PythonGrpcGenerator() override {} |
||||
|
||||
bool Generate(const FileDescriptor* file, |
||||
const string& parameter, |
||||
GeneratorContext* context, |
||||
string* error) const override { |
||||
// Get output file name.
|
||||
string file_name; |
||||
static const int proto_suffix_length = 6; // length of ".proto"
|
||||
if (file->name().size() > proto_suffix_length && |
||||
file->name().find_last_of(".proto") == file->name().size() - 1) { |
||||
file_name = file->name().substr( |
||||
0, file->name().size() - proto_suffix_length) + "_pb2.py"; |
||||
} else { |
||||
*error = "Invalid proto file name. Proto file must end with .proto"; |
||||
return false; |
||||
} |
||||
|
||||
std::unique_ptr<ZeroCopyOutputStream> output( |
||||
context->OpenForInsert(file_name, "module_scope")); |
||||
CodedOutputStream coded_out(output.get()); |
||||
string code = grpc_python_generator::GetServices(file); |
||||
coded_out.WriteRaw(code.data(), code.size()); |
||||
return true; |
||||
} |
||||
}; |
||||
|
||||
int main(int argc, char* argv[]) { |
||||
PythonGrpcGenerator generator; |
||||
return PluginMain(argc, argv, &generator); |
||||
} |
@ -0,0 +1,9 @@ |
||||
#Overview |
||||
|
||||
This directory contains source code for shared C library. Libraries in other languages in this repository (C++, Ruby, |
||||
Python, PHP, NodeJS, Objective-C) are layered on top of this library. |
||||
|
||||
#Status |
||||
|
||||
Alpha : Ready for early adopters |
||||
|
@ -0,0 +1,9 @@ |
||||
|
||||
#Overview |
||||
|
||||
This directory contains source code for C++ implementation of gRPC. |
||||
|
||||
#Status |
||||
|
||||
Alpha : Ready for early adopters |
||||
|
@ -0,0 +1,28 @@ |
||||
{ |
||||
"bitwise": true, |
||||
"curly": true, |
||||
"eqeqeq": true, |
||||
"esnext": true, |
||||
"freeze": true, |
||||
"immed": true, |
||||
"indent": 2, |
||||
"latedef": "nofunc", |
||||
"maxlen": 80, |
||||
"newcap": true, |
||||
"node": true, |
||||
"noarg": true, |
||||
"quotmark": "single", |
||||
"strict": true, |
||||
"trailing": true, |
||||
"undef": true, |
||||
"unused": "vars", |
||||
"globals": { |
||||
/* Mocha-provided globals */ |
||||
"describe": false, |
||||
"it": false, |
||||
"before": false, |
||||
"beforeEach": false, |
||||
"after": false, |
||||
"afterEach": false |
||||
} |
||||
} |
@ -0,0 +1,18 @@ |
||||
# Xcode |
||||
# |
||||
build/ |
||||
*.pbxuser |
||||
!default.pbxuser |
||||
*.mode1v3 |
||||
!default.mode1v3 |
||||
*.mode2v3 |
||||
!default.mode2v3 |
||||
*.perspectivev3 |
||||
!default.perspectivev3 |
||||
xcuserdata |
||||
*.xccheckout |
||||
*.moved-aside |
||||
DerivedData |
||||
*.hmap |
||||
*.ipa |
||||
*.xcuserstate |
@ -0,0 +1,90 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2014, Google Inc. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are |
||||
* met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following disclaimer |
||||
* in the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* * Neither the name of Google Inc. nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
*/ |
||||
|
||||
#import <Foundation/Foundation.h> |
||||
#import <RxLibrary/GRXWriter.h> |
||||
|
||||
@class GRPCMethodName; |
||||
|
||||
@class GRPCCall; |
||||
|
||||
// The gRPC protocol is an RPC protocol on top of HTTP2.
|
||||
//
|
||||
// While the most common type of RPC receives only one request message and
|
||||
// returns only one response message, the protocol also supports RPCs that
|
||||
// return multiple individual messages in a streaming fashion, RPCs that
|
||||
// accept a stream of request messages, or RPCs with both streaming requests
|
||||
// and responses.
|
||||
//
|
||||
// Conceptually, each gRPC call consists of a bidirectional stream of binary
|
||||
// messages, with RPCs of the "non-streaming type" sending only one message in
|
||||
// the corresponding direction (the protocol doesn't make any distinction).
|
||||
//
|
||||
// Each RPC uses a different HTTP2 stream, and thus multiple simultaneous RPCs
|
||||
// can be multiplexed transparently on the same TCP connection.
|
||||
@interface GRPCCall : NSObject<GRXWriter> |
||||
|
||||
// These HTTP2 headers will be passed to the server as part of this call. Each
|
||||
// HTTP2 header is a name-value pair with string names and either string or binary values.
|
||||
// The passed dictionary has to use NSString keys, corresponding to the header names. The
|
||||
// value associated to each can be a NSString object or a NSData object. E.g.:
|
||||
//
|
||||
// call.requestMetadata = @{
|
||||
// @"Authorization": @"Bearer ...",
|
||||
// @"SomeBinaryHeader": someData
|
||||
// };
|
||||
//
|
||||
// After the call is started, modifying this won't have any effect.
|
||||
@property(nonatomic, readwrite) NSMutableDictionary *requestMetadata; |
||||
|
||||
// This isn't populated until the first event is delivered to the handler.
|
||||
@property(atomic, readonly) NSDictionary *responseMetadata; |
||||
|
||||
// The request writer has to write NSData objects into the provided Writeable. The server will
|
||||
// receive each of those separately and in order.
|
||||
// A gRPC call might not complete until the request writer finishes. On the other hand, the
|
||||
// request finishing doesn't necessarily make the call to finish, as the server might continue
|
||||
// sending messages to the response side of the call indefinitely (depending on the semantics of
|
||||
// the specific remote method called).
|
||||
// To finish a call right away, invoke cancel.
|
||||
- (instancetype)initWithHost:(NSString *)host |
||||
method:(GRPCMethodName *)method |
||||
requestsWriter:(id<GRXWriter>)requestsWriter NS_DESIGNATED_INITIALIZER; |
||||
|
||||
// Finishes the request side of this call, notifies the server that the RPC
|
||||
// should be cancelled, and finishes the response side of the call with an error
|
||||
// of code CANCELED.
|
||||
- (void)cancel; |
||||
|
||||
// TODO(jcanizales): Let specify a deadline. As a category of GRXWriter?
|
||||
@end |
@ -0,0 +1,406 @@ |
||||
/* |
||||
* |
||||
* Copyright 2014, Google Inc. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are |
||||
* met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following disclaimer |
||||
* in the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* * Neither the name of Google Inc. nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
*/ |
||||
|
||||
#import "GRPCCall.h" |
||||
|
||||
#include <grpc.h> |
||||
#include <support/time.h> |
||||
|
||||
#import "GRPCMethodName.h" |
||||
#import "private/GRPCChannel.h" |
||||
#import "private/GRPCCompletionQueue.h" |
||||
#import "private/GRPCDelegateWrapper.h" |
||||
#import "private/GRPCMethodName+HTTP2Encoding.h" |
||||
#import "private/NSData+GRPC.h" |
||||
#import "private/NSDictionary+GRPC.h" |
||||
#import "private/NSError+GRPC.h" |
||||
|
||||
// A grpc_call_error represents a precondition failure when invoking the |
||||
// grpc_call_* functions. If one ever happens, it's a bug in this library. |
||||
// |
||||
// TODO(jcanizales): Can an application shut down gracefully when a thread other |
||||
// than the main one throws an exception? |
||||
static void AssertNoErrorInCall(grpc_call_error error) { |
||||
if (error != GRPC_CALL_OK) { |
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException |
||||
reason:@"Precondition of grpc_call_* not met." |
||||
userInfo:nil]; |
||||
} |
||||
} |
||||
|
||||
@interface GRPCCall () <GRXWriteable> |
||||
// Makes it readwrite. |
||||
@property(atomic, strong) NSDictionary *responseMetadata; |
||||
@end |
||||
|
||||
// The following methods of a C gRPC call object aren't reentrant, and thus |
||||
// calls to them must be serialized: |
||||
// - add_metadata |
||||
// - invoke |
||||
// - start_write |
||||
// - writes_done |
||||
// - start_read |
||||
// - destroy |
||||
// The first four are called as part of responding to client commands, but |
||||
// start_read we want to call as soon as we're notified that the RPC was |
||||
// successfully established (which happens concurrently in the network queue). |
||||
// Serialization is achieved by using a private serial queue to operate the |
||||
// call object. |
||||
// Because add_metadata and invoke are called and return successfully before |
||||
// any of the other methods is called, they don't need to use the queue. |
||||
// |
||||
// Furthermore, start_write and writes_done can only be called after the |
||||
// WRITE_ACCEPTED event for any previous write is received. This is achieved by |
||||
// pausing the requests writer immediately every time it writes a value, and |
||||
// resuming it again when WRITE_ACCEPTED is received. |
||||
// |
||||
// Similarly, start_read can only be called after the READ event for any |
||||
// previous read is received. This is easier to enforce, as we're writing the |
||||
// received messages into the writeable: start_read is enqueued once upon receiving |
||||
// the CLIENT_METADATA_READ event, and then once after receiving each READ |
||||
// event. |
||||
@implementation GRPCCall { |
||||
dispatch_queue_t _callQueue; |
||||
|
||||
grpc_call *_gRPCCall; |
||||
dispatch_once_t _callAlreadyInvoked; |
||||
|
||||
GRPCChannel *_channel; |
||||
GRPCCompletionQueue *_completionQueue; |
||||
|
||||
// The C gRPC library has less guarantees on the ordering of events than we |
||||
// do. Particularly, in the face of errors, there's no ordering guarantee at |
||||
// all. This wrapper over our actual writeable ensures thread-safety and |
||||
// correct ordering. |
||||
GRPCDelegateWrapper *_responseWriteable; |
||||
id<GRXWriter> _requestWriter; |
||||
} |
||||
|
||||
@synthesize state = _state; |
||||
|
||||
- (instancetype)init { |
||||
return [self initWithHost:nil method:nil requestsWriter:nil]; |
||||
} |
||||
|
||||
// Designated initializer |
||||
- (instancetype)initWithHost:(NSString *)host |
||||
method:(GRPCMethodName *)method |
||||
requestsWriter:(id<GRXWriter>)requestWriter { |
||||
if (!host || !method) { |
||||
[NSException raise:NSInvalidArgumentException format:@"Neither host nor method can be nil."]; |
||||
} |
||||
// TODO(jcanizales): Throw if the requestWriter was already started. |
||||
if ((self = [super init])) { |
||||
static dispatch_once_t initialization; |
||||
dispatch_once(&initialization, ^{ |
||||
grpc_init(); |
||||
}); |
||||
|
||||
_completionQueue = [GRPCCompletionQueue completionQueue]; |
||||
|
||||
_channel = [GRPCChannel channelToHost:host]; |
||||
_gRPCCall = grpc_channel_create_call_old(_channel.unmanagedChannel, |
||||
method.HTTP2Path.UTF8String, |
||||
host.UTF8String, |
||||
gpr_inf_future); |
||||
|
||||
// Serial queue to invoke the non-reentrant methods of the grpc_call object. |
||||
_callQueue = dispatch_queue_create("org.grpc.call", NULL); |
||||
|
||||
_requestWriter = requestWriter; |
||||
} |
||||
return self; |
||||
} |
||||
|
||||
#pragma mark Finish |
||||
|
||||
- (void)finishWithError:(NSError *)errorOrNil { |
||||
_requestWriter.state = GRXWriterStateFinished; |
||||
_requestWriter = nil; |
||||
if (errorOrNil) { |
||||
[_responseWriteable cancelWithError:errorOrNil]; |
||||
} else { |
||||
[_responseWriteable enqueueSuccessfulCompletion]; |
||||
} |
||||
} |
||||
|
||||
- (void)cancelCall { |
||||
// Can be called from any thread, any number of times. |
||||
AssertNoErrorInCall(grpc_call_cancel(_gRPCCall)); |
||||
} |
||||
|
||||
- (void)cancel { |
||||
[self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
||||
code:GRPCErrorCodeCancelled |
||||
userInfo:nil]]; |
||||
[self cancelCall]; |
||||
} |
||||
|
||||
- (void)dealloc { |
||||
grpc_call *gRPCCall = _gRPCCall; |
||||
dispatch_async(_callQueue, ^{ |
||||
grpc_call_destroy(gRPCCall); |
||||
}); |
||||
} |
||||
|
||||
#pragma mark Read messages |
||||
|
||||
// Only called from the call queue. |
||||
// The handler will be called from the network queue. |
||||
- (void)startReadWithHandler:(GRPCEventHandler)handler { |
||||
AssertNoErrorInCall(grpc_call_start_read_old(_gRPCCall, (__bridge_retained void *)handler)); |
||||
} |
||||
|
||||
// Called initially from the network queue once response headers are received, |
||||
// then "recursively" from the responseWriteable queue after each response from the |
||||
// server has been written. |
||||
// If the call is currently paused, this is a noop. Restarting the call will invoke this |
||||
// method. |
||||
// TODO(jcanizales): Rename to readResponseIfNotPaused. |
||||
- (void)startNextRead { |
||||
if (self.state == GRXWriterStatePaused) { |
||||
return; |
||||
} |
||||
__weak GRPCCall *weakSelf = self; |
||||
__weak GRPCDelegateWrapper *weakWriteable = _responseWriteable; |
||||
|
||||
dispatch_async(_callQueue, ^{ |
||||
[weakSelf startReadWithHandler:^(grpc_event *event) { |
||||
if (!event->data.read) { |
||||
// No more responses from the server. |
||||
return; |
||||
} |
||||
NSData *data = [NSData grpc_dataWithByteBuffer:event->data.read]; |
||||
if (!data) { |
||||
// The app doesn't have enough memory to hold the server response. We |
||||
// don't want to throw, because the app shouldn't crash for a behavior |
||||
// that's on the hands of any server to have. Instead we finish and ask |
||||
// the server to cancel. |
||||
// |
||||
// TODO(jcanizales): No canonical code is appropriate for this situation |
||||
// (because it's just a client problem). Use another domain and an |
||||
// appropriately-documented code. |
||||
[weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
||||
code:GRPCErrorCodeInternal |
||||
userInfo:nil]]; |
||||
[weakSelf cancelCall]; |
||||
return; |
||||
} |
||||
[weakWriteable enqueueMessage:data completionHandler:^{ |
||||
[weakSelf startNextRead]; |
||||
}]; |
||||
}]; |
||||
}); |
||||
} |
||||
|
||||
#pragma mark Send headers |
||||
|
||||
- (void)addHeaderWithName:(NSString *)name binaryValue:(NSData *)value { |
||||
grpc_metadata metadata; |
||||
// Safe to discard const qualifiers; we're not going to modify the contents. |
||||
metadata.key = (char *)name.UTF8String; |
||||
metadata.value = (char *)value.bytes; |
||||
metadata.value_length = value.length; |
||||
grpc_call_add_metadata_old(_gRPCCall, &metadata, 0); |
||||
} |
||||
|
||||
- (void)addHeaderWithName:(NSString *)name ASCIIValue:(NSString *)value { |
||||
grpc_metadata metadata; |
||||
// Safe to discard const qualifiers; we're not going to modify the contents. |
||||
metadata.key = (char *)name.UTF8String; |
||||
metadata.value = (char *)value.UTF8String; |
||||
// The trailing \0 isn't encoded in HTTP2. |
||||
metadata.value_length = value.length; |
||||
grpc_call_add_metadata_old(_gRPCCall, &metadata, 0); |
||||
} |
||||
|
||||
// TODO(jcanizales): Rename to commitHeaders. |
||||
- (void)sendHeaders:(NSDictionary *)metadata { |
||||
for (NSString *name in metadata) { |
||||
id value = metadata[name]; |
||||
if ([value isKindOfClass:[NSData class]]) { |
||||
[self addHeaderWithName:name binaryValue:value]; |
||||
} else if ([value isKindOfClass:[NSString class]]) { |
||||
[self addHeaderWithName:name ASCIIValue:value]; |
||||
} |
||||
} |
||||
} |
||||
|
||||
#pragma mark GRXWriteable implementation |
||||
|
||||
// Only called from the call queue. The error handler will be called from the |
||||
// network queue if the write didn't succeed. |
||||
- (void)writeMessage:(NSData *)message withErrorHandler:(void (^)())errorHandler { |
||||
|
||||
__weak GRPCCall *weakSelf = self; |
||||
GRPCEventHandler resumingHandler = ^(grpc_event *event) { |
||||
if (event->data.write_accepted != GRPC_OP_OK) { |
||||
errorHandler(); |
||||
} |
||||
// Resume the request writer (even in the case of error). |
||||
// TODO(jcanizales): No need to do it in the case of errors anymore? |
||||
GRPCCall *strongSelf = weakSelf; |
||||
if (strongSelf) { |
||||
strongSelf->_requestWriter.state = GRXWriterStateStarted; |
||||
} |
||||
}; |
||||
|
||||
grpc_byte_buffer *buffer = message.grpc_byteBuffer; |
||||
AssertNoErrorInCall(grpc_call_start_write_old(_gRPCCall, |
||||
buffer, |
||||
(__bridge_retained void *)resumingHandler, |
||||
0)); |
||||
grpc_byte_buffer_destroy(buffer); |
||||
} |
||||
|
||||
- (void)didReceiveValue:(id)value { |
||||
// TODO(jcanizales): Throw/assert if value isn't NSData. |
||||
|
||||
// Pause the input and only resume it when the C layer notifies us that writes |
||||
// can proceed. |
||||
_requestWriter.state = GRXWriterStatePaused; |
||||
|
||||
__weak GRPCCall *weakSelf = self; |
||||
dispatch_async(_callQueue, ^{ |
||||
[weakSelf writeMessage:value withErrorHandler:^{ |
||||
[weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
||||
code:GRPCErrorCodeInternal |
||||
userInfo:nil]]; |
||||
}]; |
||||
}); |
||||
} |
||||
|
||||
// Only called from the call queue. The error handler will be called from the |
||||
// network queue if the requests stream couldn't be closed successfully. |
||||
- (void)finishRequestWithErrorHandler:(void (^)())errorHandler { |
||||
GRPCEventHandler handler = ^(grpc_event *event) { |
||||
if (event->data.finish_accepted != GRPC_OP_OK) { |
||||
errorHandler(); |
||||
} |
||||
}; |
||||
AssertNoErrorInCall(grpc_call_writes_done_old(_gRPCCall, (__bridge_retained void *)handler)); |
||||
} |
||||
|
||||
- (void)didFinishWithError:(NSError *)errorOrNil { |
||||
if (errorOrNil) { |
||||
[self cancel]; |
||||
} else { |
||||
__weak GRPCCall *weakSelf = self; |
||||
dispatch_async(_callQueue, ^{ |
||||
[weakSelf finishRequestWithErrorHandler:^{ |
||||
[weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
||||
code:GRPCErrorCodeInternal |
||||
userInfo:nil]]; |
||||
}]; |
||||
}); |
||||
} |
||||
} |
||||
|
||||
#pragma mark Invoke |
||||
|
||||
// Both handlers will eventually be called, from the network queue. Writes can start immediately |
||||
// after this. |
||||
// The first one (metadataHandler), when the response headers are received. |
||||
// The second one (completionHandler), whenever the RPC finishes for any reason. |
||||
- (void)invokeCallWithMetadataHandler:(GRPCEventHandler)metadataHandler |
||||
completionHandler:(GRPCEventHandler)completionHandler { |
||||
AssertNoErrorInCall(grpc_call_invoke_old(_gRPCCall, |
||||
_completionQueue.unmanagedQueue, |
||||
(__bridge_retained void *)metadataHandler, |
||||
(__bridge_retained void *)completionHandler, |
||||
0)); |
||||
} |
||||
|
||||
- (void)invokeCall { |
||||
__weak GRPCCall *weakSelf = self; |
||||
[self invokeCallWithMetadataHandler:^(grpc_event *event) { |
||||
// Response metadata received. |
||||
// TODO(jcanizales): Name the type of event->data.client_metadata_read |
||||
// in the C library so one can actually pass the object to a method. |
||||
grpc_metadata *entries = event->data.client_metadata_read.elements; |
||||
size_t count = event->data.client_metadata_read.count; |
||||
GRPCCall *strongSelf = weakSelf; |
||||
if (strongSelf) { |
||||
strongSelf.responseMetadata = [NSDictionary grpc_dictionaryFromMetadata:entries |
||||
count:count]; |
||||
[strongSelf startNextRead]; |
||||
} |
||||
} completionHandler:^(grpc_event *event) { |
||||
// TODO(jcanizales): Merge HTTP2 trailers into response metadata. |
||||
[weakSelf finishWithError:[NSError grpc_errorFromStatus:&event->data.finished]]; |
||||
}]; |
||||
// Now that the RPC has been initiated, request writes can start. |
||||
[_requestWriter startWithWriteable:self]; |
||||
} |
||||
|
||||
#pragma mark GRXWriter implementation |
||||
|
||||
- (void)startWithWriteable:(id<GRXWriteable>)writeable { |
||||
// The following produces a retain cycle self:_responseWriteable:self, which is only |
||||
// broken when didFinishWithError: is sent to the wrapped writeable. |
||||
// Care is taken not to retain self strongly in any of the blocks used in |
||||
// the implementation of GRPCCall, so that the life of the instance is |
||||
// determined by this retain cycle. |
||||
_responseWriteable = [[GRPCDelegateWrapper alloc] initWithWriteable:writeable writer:self]; |
||||
[self sendHeaders:_requestMetadata]; |
||||
[self invokeCall]; |
||||
} |
||||
|
||||
- (void)setState:(GRXWriterState)newState { |
||||
// Manual transitions are only allowed from the started or paused states. |
||||
if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) { |
||||
return; |
||||
} |
||||
|
||||
switch (newState) { |
||||
case GRXWriterStateFinished: |
||||
_state = newState; |
||||
// Per GRXWriter's contract, setting the state to Finished manually |
||||
// means one doesn't wish the writeable to be messaged anymore. |
||||
[_responseWriteable cancelSilently]; |
||||
_responseWriteable = nil; |
||||
return; |
||||
case GRXWriterStatePaused: |
||||
_state = newState; |
||||
return; |
||||
case GRXWriterStateStarted: |
||||
if (_state == GRXWriterStatePaused) { |
||||
_state = newState; |
||||
[self startNextRead]; |
||||
} |
||||
return; |
||||
case GRXWriterStateNotStarted: |
||||
return; |
||||
} |
||||
} |
||||
@end |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue