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