├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Dockerfile ├── Grpc.xs ├── LICENSE ├── MANIFEST ├── Makefile.PL ├── README.md ├── Vagrantfile ├── examples ├── README.md ├── greeter │ ├── Makefile │ ├── README.md │ ├── helloworld.proto │ ├── helloworld.proto3 │ └── t │ │ └── 01-greeter.t └── route_guide │ ├── Makefile │ ├── README.md │ ├── route_guide.proto │ └── t │ ├── 01-get_feature.t │ ├── 02-list_features.t │ ├── 03-record_route.t │ ├── 04-route_chat.t │ └── route.txt ├── ext ├── call.h ├── call.xs ├── call_credentials.h ├── call_credentials.xs ├── channel.h ├── channel.xs ├── channel_credentials.h ├── channel_credentials.xs ├── constants.h ├── constants.xs ├── server.h ├── server.xs ├── server_credentials.h ├── server_credentials.xs ├── timeval.h └── timeval.xs ├── lib └── Grpc │ ├── Client │ ├── AbstractCall.pm │ ├── BaseStub.pm │ ├── BidiStreamingCall.pm │ ├── ClientStreamingCall.pm │ ├── ServerStreamingCall.pm │ └── UnaryCall.pm │ ├── Constants.pm │ ├── XS.pm │ └── XS │ ├── Call.pm │ ├── CallCredentials.pm │ ├── Channel.pm │ ├── ChannelCredentials.pm │ ├── Constants.pm │ ├── Server.pm │ ├── ServerCredentials.pm │ └── Timeval.pm ├── ppport.h ├── t ├── 00-compile.t ├── 01-call_credentials.t ├── 02-call.t ├── 03-channel_credentials.t ├── 04-channel.t ├── 05-constants.t ├── 06-server_credentials.t ├── 07-server.t ├── 08-timeval.t ├── 09-abstract_call.t ├── 10-base_stub.t ├── 11-bidi_streaming_call.t ├── 12-client_streaming_call.t ├── 13-server_streaming_call.t ├── 14-unary_call.t ├── 15-xs_end_to_end.t ├── 16-xs_secure_end_to_end.t ├── 17-fork_friendliness.t └── data │ ├── README │ ├── ca.pem │ ├── server1.key │ └── server1.pem ├── typemap ├── util.c └── util.h /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | perl-job: 7 | runs-on: ubuntu-latest 8 | container: 9 | image: perldocker/perl-tester:${{ matrix.perl-version }} # https://hub.docker.com/r/perldocker/perl-tester 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | perl-version: 14 | - '5.30' 15 | # - '5.32' 16 | # - 'latest' 17 | name: Perl ${{ matrix.perl-version }} 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Regular tests 21 | run: | 22 | apt-get install -y libgrpc-dev 23 | cpanm Devel::CheckLib 24 | perl Makefile.PL 25 | make 26 | make test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .project 3 | .*~ 4 | .*.swp 5 | /*.o 6 | /*.obj 7 | /blib 8 | /pm_to_blib 9 | /XS.bs 10 | /Grpc.c 11 | /MYMETA.* 12 | /Makefile 13 | /Makefile.old 14 | .vagrant 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/grpc/cxx:1.12.0 2 | 3 | ADD . /opt/grpc-perl 4 | 5 | WORKDIR /opt/grpc-perl 6 | 7 | RUN perl -MCPAN -e 'install Devel::CheckLib' \ 8 | && perl -MCPAN -e 'install Google::ProtocolBuffers::Dynamic' \ 9 | && perl Makefile.PL \ 10 | && make test \ 11 | && make install 12 | -------------------------------------------------------------------------------- /Grpc.xs: -------------------------------------------------------------------------------- 1 | #define PERL_NO_GET_CONTEXT 2 | #include "EXTERN.h" 3 | #include "perl.h" 4 | #include "XSUB.h" 5 | #include "ppport.h" 6 | 7 | #include "util.h" 8 | #include "ext/call.h" 9 | #include "ext/call_credentials.h" 10 | #include "ext/channel.h" 11 | #include "ext/channel_credentials.h" 12 | #include "ext/constants.h" 13 | #include "ext/server.h" 14 | #include "ext/server_credentials.h" 15 | #include "ext/timeval.h" 16 | 17 | MODULE = Grpc::XS PACKAGE = Grpc::XS 18 | 19 | BOOT: 20 | grpc_perl_init(); 21 | 22 | void 23 | init() 24 | CODE: 25 | grpc_perl_init(); 26 | 27 | void 28 | destroy() 29 | CODE: 30 | grpc_perl_destroy(); 31 | 32 | MODULE = Grpc::XS PACKAGE = Grpc::XS::Call 33 | INCLUDE: ext/call.xs 34 | 35 | MODULE = Grpc::XS PACKAGE = Grpc::XS::CallCredentials 36 | INCLUDE: ext/call_credentials.xs 37 | 38 | MODULE = Grpc::XS PACKAGE = Grpc::XS::Channel 39 | INCLUDE: ext/channel.xs 40 | 41 | MODULE = Grpc::XS PACKAGE = Grpc::XS::ChannelCredentials 42 | INCLUDE: ext/channel_credentials.xs 43 | 44 | MODULE = Grpc::XS PACKAGE = Grpc::XS::Constants 45 | INCLUDE: ext/constants.xs 46 | 47 | MODULE = Grpc::XS PACKAGE = Grpc::XS::Server 48 | INCLUDE: ext/server.xs 49 | 50 | MODULE = Grpc::XS PACKAGE = Grpc::XS::ServerCredentials 51 | INCLUDE: ext/server_credentials.xs 52 | 53 | MODULE = Grpc::XS PACKAGE = Grpc::XS::Timeval 54 | INCLUDE: ext/timeval.xs 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | Grpc.xs 2 | LICENSE 3 | MANIFEST 4 | Makefile.PL 5 | ppport.h 6 | README.md 7 | typemap 8 | util.c 9 | util.h 10 | examples/greeter/helloworld.proto 11 | examples/greeter/helloworld.proto3 12 | examples/greeter/Makefile 13 | examples/greeter/README.md 14 | examples/greeter/t/01-greeter.t 15 | examples/README.md 16 | examples/route_guide/Makefile 17 | examples/route_guide/README.md 18 | examples/route_guide/route_guide.proto 19 | examples/route_guide/t/01-get_feature.t 20 | examples/route_guide/t/02-list_features.t 21 | examples/route_guide/t/03-record_route.t 22 | examples/route_guide/t/04-route_chat.t 23 | examples/route_guide/t/route.txt 24 | ext/call_credentials.h 25 | ext/call_credentials.xs 26 | ext/call.h 27 | ext/call.xs 28 | ext/channel_credentials.h 29 | ext/channel_credentials.xs 30 | ext/channel.h 31 | ext/channel.xs 32 | ext/constants.h 33 | ext/constants.xs 34 | ext/server_credentials.h 35 | ext/server_credentials.xs 36 | ext/server.h 37 | ext/server.xs 38 | ext/timeval.h 39 | ext/timeval.xs 40 | lib/Grpc/Client/AbstractCall.pm 41 | lib/Grpc/Client/BaseStub.pm 42 | lib/Grpc/Client/BidiStreamingCall.pm 43 | lib/Grpc/Client/ClientStreamingCall.pm 44 | lib/Grpc/Client/ServerStreamingCall.pm 45 | lib/Grpc/Client/UnaryCall.pm 46 | lib/Grpc/Constants.pm 47 | lib/Grpc/XS/CallCredentials.pm 48 | lib/Grpc/XS/Call.pm 49 | lib/Grpc/XS/ChannelCredentials.pm 50 | lib/Grpc/XS/Channel.pm 51 | lib/Grpc/XS/Constants.pm 52 | lib/Grpc/XS.pm 53 | lib/Grpc/XS/ServerCredentials.pm 54 | lib/Grpc/XS/Server.pm 55 | lib/Grpc/XS/Timeval.pm 56 | t/00-compile.t 57 | t/01-call_credentials.t 58 | t/02-call.t 59 | t/03-channel_credentials.t 60 | t/04-channel.t 61 | t/05-constants.t 62 | t/06-server_credentials.t 63 | t/07-server.t 64 | t/08-timeval.t 65 | t/09-abstract_call.t 66 | t/10-base_stub.t 67 | t/11-bidi_streaming_call.t 68 | t/12-client_streaming_call.t 69 | t/13-server_streaming_call.t 70 | t/14-unary_call.t 71 | t/15-xs_end_to_end.t 72 | t/16-xs_secure_end_to_end.t 73 | t/17-fork_friendliness.t 74 | t/data/ca.pem 75 | t/data/README 76 | t/data/server1.key 77 | t/data/server1.pem 78 | -------------------------------------------------------------------------------- /Makefile.PL: -------------------------------------------------------------------------------- 1 | use 5.008005; 2 | use ExtUtils::MakeMaker; 3 | 4 | use Getopt::Long; 5 | use Devel::CheckLib; 6 | 7 | # perl Makefile.PL -d to enable -g flag for gdb. 8 | Getopt::Long::Configure('pass_through'); 9 | GetOptions( 10 | 'd' => \my $DEBUG, 11 | 'grpc-prefix=s' => \my $GRPC_PREFIX, 12 | ) 13 | or die "Error in command line arguments\n"; 14 | 15 | my ($EXTRA_INCFLAGS, $EXTRA_DEFINES, $EXTRA_LDFLAGS, %CHECKLIB_ARGS); 16 | if ($GRPC_PREFIX) { 17 | $EXTRA_INCFLAGS = "-I$GRPC_PREFIX/include"; 18 | $EXTRA_LDFLAGS = "-L$GRPC_PREFIX/lib"; 19 | $EXTRA_DEFINES = ''; 20 | %CHECKLIB_ARGS = ( 21 | header => 'grpc/grpc.h', 22 | INC => $EXTRA_INCFLAGS, 23 | LIBS => "$EXTRA_LDFLAGS -lgrpc", 24 | ); 25 | } else { 26 | $EXTRA_INCFLAGS = $EXTRA_DEFINES = $EXTRA_LDFLAGS = ''; 27 | %CHECKLIB_ARGS = ( 28 | LIBS => '-lgrpc', 29 | header => 'grpc/grpc.h', 30 | ); 31 | } 32 | 33 | # sanity check 34 | check_lib_or_exit( 35 | %CHECKLIB_ARGS, 36 | function => 'grpc_version_string();', 37 | ); 38 | 39 | check_lib( 40 | %CHECKLIB_ARGS, 41 | function => <<'EOT', 42 | grpc_op op; op.data.send_message.send_message = (grpc_byte_buffer *) NULL; 43 | EOT 44 | ) and ($EXTRA_DEFINES .= " -DGRPC_VERSION_1_1"); 45 | 46 | check_lib( 47 | %CHECKLIB_ARGS, 48 | function => <<'EOT', 49 | grpc_call_details details; 50 | grpc_call_details_init(&details); 51 | grpc_slice_to_c_string(details.method); 52 | EOT 53 | ) and ($EXTRA_DEFINES .= " -DGRPC_VERSION_1_2"); 54 | 55 | check_lib( 56 | %CHECKLIB_ARGS, 57 | function => <<'EOT', 58 | void (*call_unref)(grpc_call *call) = &grpc_call_unref; 59 | EOT 60 | ) and ($EXTRA_DEFINES .= " -DGRPC_VERSION_1_4"); 61 | 62 | check_lib( 63 | %CHECKLIB_ARGS, 64 | header => 'grpc/grpc_security.h', 65 | function => <<'EOT', 66 | return !GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX; 67 | EOT 68 | ) and ($EXTRA_DEFINES .= " -DGRPC_VERSION_1_7"); 69 | 70 | check_lib( 71 | %CHECKLIB_ARGS, 72 | function => <<'EOT', 73 | return 0; 74 | grpc_op op; 75 | op.data.recv_status_on_client.error_string = 0; 76 | EOT 77 | ) and ($EXTRA_DEFINES .= " -DGRPC_RECV_STATUS_ON_CLIENT_HAS_ERROR_STRING"); 78 | 79 | check_lib( 80 | %CHECKLIB_ARGS, 81 | header => [$CHECKLIB_ARGS{header}, "grpc/grpc_security.h"], 82 | function => <<'EOT', 83 | return 0; 84 | grpc_ssl_credentials_create(0, 0, 0, 0); 85 | EOT 86 | ) and ($EXTRA_DEFINES .= " -DGRPC_SSL_CREDENTIALS_HAS_4_ARGS"); 87 | 88 | check_lib( 89 | %CHECKLIB_ARGS, 90 | header => [$CHECKLIB_ARGS{header}, "grpc/grpc_security.h"], 91 | function => <<'EOT', 92 | grpc_metadata_credentials_plugin plugin; 93 | return 0; 94 | grpc_metadata_credentials_create_from_plugin(plugin, 0, NULL); 95 | EOT 96 | ) and ($EXTRA_DEFINES .= " -DGRPC_METADATA_CREDENTIALS_CRATE_FROM_PLUGIN_HAS_3_ARGS"); 97 | 98 | check_lib( 99 | %CHECKLIB_ARGS, 100 | header => [$CHECKLIB_ARGS{header}, "grpc/grpc_security.h"], 101 | function => <<'EOT', 102 | return 0; 103 | grpc_google_default_credentials_create(NULL); 104 | EOT 105 | ) and ($EXTRA_DEFINES .= " -DGRPC_GOOGLE_DEFAULT_CREDENTIALS_CREATE_HAS_1_ARG"); 106 | 107 | check_lib( 108 | %CHECKLIB_ARGS, 109 | function => <<'EOT', 110 | grpc_channel_credentials * cred = NULL; 111 | return 0; 112 | EOT 113 | ) and ($EXTRA_DEFINES .= " -DGRPC_NO_INSECURE_BUILD"); 114 | 115 | # grpc_shutdown() is async in recent versions of the library, leading 116 | # to a race at shutdown which can cause fork to fail. See 117 | # https://github.com/joyrex2001/grpc-perl/issues/22. 118 | # If the library has grpc_shutdown_blocking(), use that. 119 | check_lib( 120 | %CHECKLIB_ARGS, 121 | function => <<'EOT', 122 | void *p = grpc_shutdown_blocking; 123 | return 0; 124 | EOT 125 | ) and ($EXTRA_DEFINES .= " -DGRPC_HAS_SHUTDOWN_BLOCKING"); 126 | 127 | 128 | WriteMakefile( 129 | NAME => 'Grpc::XS', 130 | VERSION_FROM => 'lib/Grpc/XS.pm', 131 | AUTHOR => 'Vincent van Dam', 132 | LIBS => ["$EXTRA_LDFLAGS -lgrpc"], 133 | DEFINE => $EXTRA_DEFINES, 134 | INC => "$EXTRA_INCFLAGS -I.", 135 | C => [ "Grpc.c", "util.c" ], 136 | OBJECT => '$(O_FILES)', 137 | OPTIMIZE => $DEBUG ? '-g' : '-O2', 138 | CONFIGURE_REQUIRES => { 139 | 'Devel::CheckLib' => 0, 140 | }, 141 | META_MERGE => { 142 | resources => { 143 | repository => 'https://github.com/joyrex2001/grpc-perl', 144 | bugtracker => 'https://github.com/joyrex2001/grpc-perl/issues', 145 | homepage => 'https://github.com/joyrex2001/grpc-perl', 146 | }, 147 | }, 148 | ); 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grpc::XS / grpc-perl 2 | 3 | ## Overview 4 | 5 | This repository contains source code for a perl 5 implementation of gRPC 6 | transport layer. It binds to the official shared C library. The implementation 7 | is heavily based on the php implementation of the [gRPC library](https://grpc.io). 8 | 9 | ## Usage 10 | 11 | This implementation only implements the grpc client, not the server. This 12 | library also only implements the transport layer and is not intended to be used 13 | directly. Instead it should be used in combination with a protocol buffer 14 | implementation that support service rpc definitions. Currently the excellent 15 | [Google::ProtocolBuffers::Dynamic](http://search.cpan.org/dist/Google-ProtocolBuffers-Dynamic/) 16 | module is the best option for this. 17 | 18 | #### `fork()` compatibility 19 | 20 | It's possible to fork processes which use `Grpc::XS`, but only if this 21 | library is used exclusively inside child processes. This requires an 22 | explicit (de)initialization, otherwise things will hang forever on 23 | very first attemp to use anything grpc-related. Here is how it can be 24 | done: 25 | 26 | ```perl 27 | Gprc::XS::destroy(); 28 | if (fork() == 0) { # in child 29 | Grpc::XS::init(); 30 | # Grpc::XS can be used in this child without any problems 31 | } 32 | ``` 33 | 34 | ## Installation 35 | 36 | To install this package on ubuntu, install `libgrpc-dev` package first: 37 | 38 | ```bash 39 | sudo apt-get install libgrpc-dev 40 | ``` 41 | 42 | ## See also 43 | 44 | * https://metacpan.org/pod/Grpc::XS 45 | * https://hub.docker.com/r/joyrex2001/grpc-perl 46 | 47 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure(2) do |config| 5 | 6 | config.vm.box = "ubuntu/trusty64" 7 | 8 | config.vm.provider "virtualbox" do |vb| 9 | vb.name = "grpc-perl" 10 | vb.memory = "4096" 11 | end 12 | 13 | config.vm.synced_folder "./", "/vagrant", type: "nfs" 14 | config.vm.boot_timeout = 300 15 | config.vm.network "private_network", ip: "192.168.33.15" 16 | 17 | config.vm.provider 'virtualbox' do |vb| 18 | vb.customize [ "guestproperty", "set", :id, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-threshold", 1000 ] 19 | end 20 | 21 | config.vm.provision "shell", inline: <<-SHELL 22 | apt-get -y update 23 | apt-get -y install build-essential autoconf libtool git 24 | apt-get -y install perl-doc 25 | 26 | ## install grpc 27 | cd /opt 28 | git clone -b $(curl -L http://grpc.io/release) https://github.com/grpc/grpc 29 | cd grpc 30 | git submodule update --init 31 | make 32 | make install 33 | 34 | ## install perl makefile.pl dependency 35 | export PERL_MM_USE_DEFAULT=1 36 | perl -MCPAN -e 'install Devel::CheckLib' 37 | 38 | ## add some handy aliases and enable debug info 39 | locale-gen UTF-8 40 | echo "alias gobase='cd /vagrant'" >> ~vagrant/.bash_profile 41 | echo "unset LC_CTYPE" >> ~vagrant/.bash_profile 42 | echo "cd /vagrant" >> ~vagrant/.bash_profile 43 | echo "export GRPC_TRACE=api" >> ~vagrant/.bash_profile 44 | echo "export GRPC_VERBOSITY=DEBUG" >> ~vagrant/.bash_profile 45 | echo 'alias git="echo not tonight, you know how it is..."' >> ~vagrant/.bash_profile 46 | echo "export PERL5LIB=/vagrant/blib/arch:/vagrant/blib/lib" >> ~vagrant/.bash_profile 47 | #echo "/vagrant/grpc/libs/opt" >> /etc/ld.so.conf.d/grpc-debug.conf 48 | SHELL 49 | end 50 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This folder contains perl implementation of the example clients as included by 4 | the original grpc distribution. Note that these examples are using the 5 | deprecated experimental [protoxs-perl](https://github.com/joyrex2001/protobuf-perlxs) 6 | to generate the client based on a proto2 file. 7 | -------------------------------------------------------------------------------- /examples/greeter/Makefile: -------------------------------------------------------------------------------- 1 | helloworld: 2 | mkdir helloworld 3 | cp -r t helloworld 4 | protoxs --cpp_out=helloworld --out=helloworld helloworld.proto 5 | cd helloworld ; perl Makefile.PL ; make ; make test 6 | 7 | clean: 8 | rm -rf helloworld 9 | -------------------------------------------------------------------------------- /examples/greeter/README.md: -------------------------------------------------------------------------------- 1 | # Greeter example 2 | 3 | This is the perl version of the standard 'greeter' (Hello World) example. 4 | 5 | As the perl implementation does not support a server, it requires a running server implemented in another language (e.g. the go greeter server). 6 | 7 | The example requires an updated version of the protoxs-perl compiler to be installed (https://github.com/joyrex2001/protobuf-perlxs). 8 | 9 | Note that the protoxs-perl protocol buffer implementation only supports version 2 of the protocolbuffers, therefor the helloworld.proto has been adjusted accordingly. 10 | 11 | To run the example, simply type 'make'. This will compile the proto file, and runs the test as specified in the t/ folder. 12 | -------------------------------------------------------------------------------- /examples/greeter/helloworld.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | syntax = "proto2"; 31 | 32 | package helloworld; 33 | 34 | // The greeting service definition. 35 | service Greeter { 36 | // Sends a greeting 37 | rpc SayHello (HelloRequest) returns (HelloReply) {} 38 | } 39 | 40 | // The request message containing the user's name. 41 | message HelloRequest { 42 | optional string name = 1; 43 | } 44 | 45 | // The response message containing the greetings 46 | message HelloReply { 47 | optional string message = 1; 48 | } 49 | -------------------------------------------------------------------------------- /examples/greeter/helloworld.proto3: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | syntax = "proto3"; 31 | 32 | option java_multiple_files = true; 33 | option java_package = "io.grpc.examples.helloworld"; 34 | option java_outer_classname = "HelloWorldProto"; 35 | option objc_class_prefix = "HLW"; 36 | 37 | package helloworld; 38 | 39 | // The greeting service definition. 40 | service Greeter { 41 | // Sends a greeting 42 | rpc SayHello (HelloRequest) returns (HelloReply) {} 43 | } 44 | 45 | // The request message containing the user's name. 46 | message HelloRequest { 47 | string name = 1; 48 | } 49 | 50 | // The response message containing the greetings 51 | message HelloReply { 52 | string message = 1; 53 | } 54 | -------------------------------------------------------------------------------- /examples/greeter/t/01-greeter.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | 5 | use Test::More; 6 | 7 | use ProtobufXS::helloworld; 8 | use ProtobufXS::helloworld::Service::Greeter; 9 | 10 | use Grpc::XS::ChannelCredentials; 11 | 12 | plan tests => 1; 13 | 14 | my $credentials = Grpc::XS::ChannelCredentials::createInsecure(); 15 | my $greeter = new ProtobufXS::helloworld::Service::Greeter('localhost:50051', 16 | credentials => $credentials ); 17 | 18 | my $request = new ProtobufXS::helloworld::HelloRequest(); 19 | $request->set_name("grpc-perl"); 20 | my $call = $greeter->SayHello( argument => $request ); 21 | my $response = $call->wait(); 22 | print STDERR Dumper($response); 23 | if ($response) { 24 | print STDERR Dumper($response->to_hashref()); 25 | } 26 | ok($response,"didn't receive a response from server"); 27 | -------------------------------------------------------------------------------- /examples/route_guide/Makefile: -------------------------------------------------------------------------------- 1 | route_guide: 2 | mkdir route_guide 3 | cp -r t route_guide 4 | protoxs --cpp_out=route_guide --out=route_guide route_guide.proto 5 | cd route_guide; perl Makefile.PL ; make ; make test 6 | 7 | clean: 8 | rm -rf route_guide 9 | -------------------------------------------------------------------------------- /examples/route_guide/README.md: -------------------------------------------------------------------------------- 1 | # Route guide example 2 | 3 | This is the perl version of the standard 'route_guide' example. 4 | 5 | As the perl implementation does not support a server, it requires a running server implemented in another language, running on port 10000 (e.g. the go route_guide server). 6 | 7 | The example requires an updated version of the protoxs-perl compiler to be installed (https://github.com/joyrex2001/protobuf-perlxs). 8 | 9 | Note that the protoxs-perl protocol buffer implementation only supports version 2 of the protocolbuffers, therefor the route_guide.proto has been adjusted accordingly. 10 | 11 | To run the example, simply type 'make'. This will compile the proto file, and runs the test as specified in the t/ folder. 12 | -------------------------------------------------------------------------------- /examples/route_guide/route_guide.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | syntax = "proto2"; 31 | 32 | option java_package = "io.grpc.examples"; 33 | 34 | package routeguide; 35 | 36 | // Interface exported by the server. 37 | service RouteGuide { 38 | // A simple RPC. 39 | // 40 | // Obtains the feature at a given position. 41 | rpc GetFeature(Point) returns (Feature) {} 42 | 43 | // A server-to-client streaming RPC. 44 | // 45 | // Obtains the Features available within the given Rectangle. Results are 46 | // streamed rather than returned at once (e.g. in a response message with a 47 | // repeated field), as the rectangle may cover a large area and contain a 48 | // huge number of features. 49 | rpc ListFeatures(Rectangle) returns (stream Feature) {} 50 | 51 | // A client-to-server streaming RPC. 52 | // 53 | // Accepts a stream of Points on a route being traversed, returning a 54 | // RouteSummary when traversal is completed. 55 | rpc RecordRoute(stream Point) returns (RouteSummary) {} 56 | 57 | // A Bidirectional streaming RPC. 58 | // 59 | // Accepts a stream of RouteNotes sent while a route is being traversed, 60 | // while receiving other RouteNotes (e.g. from other users). 61 | rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} 62 | } 63 | 64 | // Points are represented as latitude-longitude pairs in the E7 representation 65 | // (degrees multiplied by 10**7 and rounded to the nearest integer). 66 | // Latitudes should be in the range +/- 90 degrees and longitude should be in 67 | // the range +/- 180 degrees (inclusive). 68 | message Point { 69 | optional int32 latitude = 1 [default = 0]; 70 | optional int32 longitude = 2 [default = 0]; 71 | } 72 | 73 | // A latitude-longitude rectangle, represented as two diagonally opposite 74 | // points "lo" and "hi". 75 | message Rectangle { 76 | // One corner of the rectangle. 77 | optional Point lo = 1; 78 | 79 | // The other corner of the rectangle. 80 | optional Point hi = 2; 81 | } 82 | 83 | // A feature names something at a given point. 84 | // 85 | // If a feature could not be named, the name is empty. 86 | message Feature { 87 | // The name of the feature. 88 | optional string name = 1; 89 | 90 | // The point where the feature is detected. 91 | optional Point location = 2; 92 | } 93 | 94 | // A RouteNote is a message sent while at a given point. 95 | message RouteNote { 96 | // The location from which the message is sent. 97 | optional Point location = 1; 98 | 99 | // The message to be sent. 100 | optional string message = 2; 101 | } 102 | 103 | // A RouteSummary is received in response to a RecordRoute rpc. 104 | // 105 | // It contains the number of individual points received, the number of 106 | // detected features, and the total distance covered as the cumulative sum of 107 | // the distance between each point. 108 | message RouteSummary { 109 | // The number of points received. 110 | optional int32 point_count = 1 [default = 0]; 111 | 112 | // The number of known features passed while traversing the route. 113 | optional int32 feature_count = 2 [default = 0]; 114 | 115 | // The distance covered in metres. 116 | optional int32 distance = 3 [default = 0]; 117 | 118 | // The duration of the traversal in seconds. 119 | optional int32 elapsed_time = 4 [default = 0]; 120 | } 121 | -------------------------------------------------------------------------------- /examples/route_guide/t/01-get_feature.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | 5 | use Test::More; 6 | 7 | use ProtobufXS::routeguide; 8 | use ProtobufXS::routeguide::Service::RouteGuide; 9 | 10 | plan tests => 2; 11 | 12 | 13 | sub printFeature 14 | { 15 | my $feature = shift; 16 | 17 | my $name = $feature->{name}; 18 | if (!$name) { 19 | $name = "no feature"; 20 | } else { 21 | $name = "feature called $name"; 22 | } 23 | print sprintf("Found %s \n at %f, %f\n", $name, 24 | ($feature->{location}->{latitude}||0) / 10000000, 25 | ($feature->{location}->{longitude}||0) / 10000000); 26 | } 27 | 28 | ################ 29 | ## getFeature ## 30 | ################ 31 | 32 | # Run the getFeature demo. Calls getFeature with a point known to have a 33 | # feature and a point known not to have a feature. 34 | 35 | my $client = new ProtobufXS::routeguide::Service::RouteGuide('localhost:10000', 36 | credentials => undef ); 37 | 38 | my @points = ( 39 | { 40 | latitude => 409146138, 41 | longitude => -746188906, 42 | }, 43 | { 44 | latitude => 0, 45 | longitude => 0, 46 | }, 47 | ); 48 | 49 | foreach my $p (@points) { 50 | my $point = new ProtobufXS::routeguide::Point($p); 51 | my $call = $client->GetFeature( argument => $point ); 52 | my $response = $call->wait(); 53 | print STDERR Dumper($response); 54 | if ($response) { 55 | print STDERR Dumper($response->to_hashref()); 56 | } 57 | ok($response,"didn't receive a response from server"); 58 | #printFeature($response->to_hashref()); 59 | } 60 | -------------------------------------------------------------------------------- /examples/route_guide/t/02-list_features.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | 5 | use Test::More; 6 | 7 | use ProtobufXS::routeguide; 8 | use ProtobufXS::routeguide::Service::RouteGuide; 9 | 10 | plan tests => 1; 11 | 12 | 13 | sub printFeature 14 | { 15 | my $feature = shift; 16 | 17 | my $name = $feature->{name}; 18 | if (!$name) { 19 | $name = "no feature"; 20 | } else { 21 | $name = "feature called $name"; 22 | } 23 | print sprintf("Found %s \n at %f, %f\n", $name, 24 | ($feature->{location}->{latitude}||0) / 10000000, 25 | ($feature->{location}->{longitude}||0) / 10000000); 26 | } 27 | 28 | ################## 29 | ## listFeatures ## 30 | ################## 31 | 32 | # Run the listFeatures demo. Calls listFeatures with a rectangle 33 | # containing all of the features in the pre-generated 34 | # database. Prints each response as it comes in. 35 | 36 | my $client = new ProtobufXS::routeguide::Service::RouteGuide('localhost:10000', 37 | credentials => undef ); 38 | 39 | my $lo_point = new ProtobufXS::routeguide::Point({ 40 | latitude => 400000000, 41 | longitude => -750000000, 42 | }); 43 | my $hi_point = new ProtobufXS::routeguide::Point({ 44 | latitude => 420000000, 45 | longitude => -730000000, 46 | }); 47 | 48 | my $rectangle = new ProtobufXS::routeguide::Rectangle({ 49 | lo => $lo_point, 50 | hi => $hi_point, 51 | }); 52 | 53 | my $call = $client->ListFeatures(argument => $rectangle); 54 | ## an iterator over the server streaming responses 55 | my @features = $call->responses(); 56 | foreach my $feature (@features) { 57 | print STDERR Dumper($feature); 58 | #printFeature($feature); 59 | } 60 | ok(@features,"no features returned by server"); 61 | -------------------------------------------------------------------------------- /examples/route_guide/t/03-record_route.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | 5 | use Test::More; 6 | use File::Basename; 7 | use File::Spec; 8 | 9 | use ProtobufXS::routeguide; 10 | use ProtobufXS::routeguide::Service::RouteGuide; 11 | 12 | plan tests => 2; 13 | 14 | my $path = File::Basename::dirname( File::Spec->rel2abs(__FILE__) ); 15 | 16 | sub readRouteDbFile 17 | { 18 | my @db; 19 | # route.txt => longitude,latitude,name 20 | open(F,"<".$path."/route.txt"); 21 | while(my $l =) { 22 | $l =~ s/\s$//g; 23 | my ($long,$lat,$name) = split(/\t/,$l); 24 | push @db, { 25 | longitude => $long, 26 | latitude => $lat, 27 | name => $name, 28 | }; 29 | } 30 | close(F); 31 | return @db; 32 | } 33 | 34 | sub printFeature 35 | { 36 | my $feature = shift; 37 | 38 | my $name = $feature->{name}; 39 | if (!$name) { 40 | $name = "no feature"; 41 | } else { 42 | $name = "feature called $name"; 43 | } 44 | print sprintf("Found %s \n at %f, %f\n", $name, 45 | ($feature->{location}->{latitude}||0) / 10000000, 46 | ($feature->{location}->{longitude}||0) / 10000000); 47 | } 48 | 49 | ################# 50 | ## recordRoute ## 51 | ################# 52 | 53 | # Run the recordRoute demo. Sends several randomly chosen points from the 54 | # pre-generated feature database with a variable delay in between. Prints 55 | # the statistics when they are sent from the server. 56 | 57 | my $client = new ProtobufXS::routeguide::Service::RouteGuide('localhost:10000', 58 | credentials => undef ); 59 | 60 | ## start the client streaming call 61 | my $call = $client->RecordRoute(); 62 | my @db = readRouteDbFile(); 63 | my $num_points_in_db = scalar(@db); 64 | my $num_points = 10; 65 | for (my $i = 0; $i < $num_points; $i++) { 66 | my $point = new ProtobufXS::routeguide::Point(); 67 | my $index = rand($num_points_in_db); 68 | my $lat = $db[$index]->{latitude}; 69 | my $long = $db[$index]->{longitude}; 70 | $point->set_latitude($lat); 71 | $point->set_longitude($long); 72 | my $feature_name = $db[$index]->{name}; 73 | #print sprintf("Visiting point %f, %f,\n with feature name: %s\n", 74 | # $lat / 10000000, $long / 10000000, 75 | # $feature_name ? $feature_name : ''); 76 | sleep(rand(2)); 77 | $call->write($point); 78 | } 79 | 80 | my $route_summary = $call->wait(); 81 | print STDERR Dumper($route_summary->to_hashref()); 82 | 83 | ok($route_summary,"no valid route summary returned"); 84 | ok($route_summary && $route_summary->to_hashref()->{point_count} == $num_points, 85 | "did not record all points sent to server"); 86 | -------------------------------------------------------------------------------- /examples/route_guide/t/04-route_chat.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | 5 | use Test::More; 6 | 7 | use ProtobufXS::routeguide; 8 | use ProtobufXS::routeguide::Service::RouteGuide; 9 | 10 | plan tests => 10; 11 | 12 | sub printFeature 13 | { 14 | my $feature = shift; 15 | 16 | my $name = $feature->{name}; 17 | if (!$name) { 18 | $name = "no feature"; 19 | } else { 20 | $name = "feature called $name"; 21 | } 22 | print sprintf("Found %s \n at %f, %f\n", $name, 23 | ($feature->{location}->{latitude}||0) / 10000000, 24 | ($feature->{location}->{longitude}||0) / 10000000); 25 | } 26 | 27 | ############### 28 | ## routeChat ## 29 | ############### 30 | 31 | # Run the routeChat demo. Send some chat messages, and print any chat 32 | # messages that are sent from the server. 33 | 34 | my $client = new ProtobufXS::routeguide::Service::RouteGuide('localhost:10000', 35 | credentials => undef ); 36 | 37 | my $call = $client->RouteChat(); 38 | my @notes = ( 39 | { 40 | longitude => 1, 41 | latitude => 1, 42 | message => 'first message' 43 | }, 44 | { 45 | longitude => 1, 46 | latitude => 2, 47 | message => 'second message' 48 | }, 49 | { 50 | longitude => 1, 51 | latitude => 1, 52 | message => 'third message' 53 | }, 54 | { 55 | longitude => 1, 56 | latitude => 1, 57 | message => 'fourth message' 58 | }, 59 | ); 60 | 61 | foreach my $n (@notes) { 62 | my $point = new ProtobufXS::routeguide::Point(); 63 | $point->set_latitude($n->{latitude}); 64 | $point->set_longitude($n->{longitude}); 65 | 66 | my $route_note = new ProtobufXS::routeguide::RouteNote(); 67 | $route_note->set_location($point); 68 | $route_note->set_message($n->{message}); 69 | # send a bunch of messages to the server 70 | $call->write($route_note); 71 | } 72 | $call->writesDone(); 73 | 74 | ## read from the server until there's no more (with a maximum of 10) 75 | my $count = 10; 76 | while (my $route_note_reply = $call->read()) { 77 | ok($route_note_reply,"did not receive reply"); 78 | print STDERR Dumper($route_note_reply->to_hashref()); 79 | $count--; last if (!$count); 80 | } 81 | -------------------------------------------------------------------------------- /examples/route_guide/t/route.txt: -------------------------------------------------------------------------------- 1 | -746143763 407838351 Patriots Path, Mendham, NJ 07945, USA 2 | -743999179 408122808 101 New Jersey 10, Whippany, NJ 07981, USA 3 | -749015468 413628156 U.S. 6, Shohola, PA 18458, USA 4 | -740371136 419999544 5 Conners Road, Kingston, NY 12401, USA 5 | -743951297 414008389 Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA 6 | -746524769 419611318 287 Flugertown Road, Livingston Manor, NY 12758, USA 7 | -742186778 406109563 4001 Tremley Point Road, Linden, NJ 07036, USA 8 | -742370183 416802456 352 South Mountain Road, Wallkill, NY 12589, USA 9 | -741077389 412950425 Bailey Turn Road, Harriman, NY 10926, USA 10 | -743949739 412144655 193-199 Wawayanda Road, Hewitt, NJ 07421, USA 11 | -742847522 415736605 406-496 Ward Avenue, Pine Bush, NY 12566, USA 12 | -740501726 413843930 162 Merrill Road, Highland Mills, NY 10930, USA 13 | -744459023 410873075 Clinton Road, West Milford, NJ 07480, USA 14 | -744026814 412346009 16 Old Brook Lane, Warwick, NY 10990, USA 15 | -747903913 402948455 3 Drake Lane, Pennington, NJ 08534, USA 16 | -740122226 406337092 6324 8th Avenue, Brooklyn, NY 11220, USA 17 | -747727624 406421967 1 Merck Access Road, Whitehouse Station, NJ 08889, USA 18 | -749677716 416318082 78-98 Schalck Road, Narrowsburg, NY 12764, USA 19 | -748416257 415301720 282 Lakeview Drive Road, Highland Lake, NY 12743, USA 20 | -747071791 402647019 330 Evelyn Avenue, Hamilton Township, NJ 08619, USA 21 | -741058078 412567807 New York State Reference Route 987E, Southfields, NY 10975, USA 22 | -744420597 416855156 103-271 Tempaloni Road, Ellenville, NY 12428, USA 23 | -744820157 404663628 1300 Airport Road, North Brunswick Township, NJ 08902, USA 24 | -749746483 407113723 25 | -743613249 402133926 26 | -741220915 400273442 27 | -744070769 411236786 28 | -746784970 411633782 211-225 Plains Road, Augusta, NJ 07822, USA 29 | -742952812 415830701 30 | -748712898 413447164 165 Pedersen Ridge Road, Milford, PA 18337, USA 31 | -749800722 405047245 100-122 Locktown Road, Frenchtown, NJ 08825, USA 32 | -746156790 418858923 33 | -748484944 417951888 650-652 Willi Hill Road, Swan Lake, NY 12783, USA 34 | -743977337 407033786 26 East 3rd Street, New Providence, NJ 07974, USA 35 | -740075041 417548014 36 | -744972325 410395868 37 | -745129803 404615353 38 | -743560121 406589790 611 Lawrence Avenue, Westfield, NJ 07090, USA 39 | -740477477 414653148 18 Lannis Avenue, New Windsor, NY 12553, USA 40 | -743255336 405957808 82-104 Amherst Avenue, Colonia, NJ 07067, USA 41 | -741648093 411733589 170 Seven Lakes Drive, Sloatsburg, NY 10974, USA 42 | -742606606 412676291 1270 Lakes Road, Monroe, NY 10950, USA 43 | -748286738 409224445 509-535 Alphano Road, Great Meadows, NJ 07838, USA 44 | -742135517 406523420 652 Garden Street, Elizabeth, NJ 07202, USA 45 | -740294537 401827388 349 Sea Spray Court, Neptune City, NJ 07753, USA 46 | -743685054 410564152 13-17 Stanley Street, West Milford, NJ 07480, USA 47 | -740726046 408472324 47 Industrial Avenue, Teterboro, NJ 07608, USA 48 | -740214052 412452168 5 White Oak Lane, Stony Point, NY 10980, USA 49 | -746188906 409146138 Berkshire Valley Management Area Trail, Jefferson, NJ, USA 50 | -744781745 404701380 1007 Jersey Avenue, New Brunswick, NJ 08901, USA 51 | -746017679 409642566 6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA 52 | -748645385 408031728 1358-1474 New Jersey 57, Port Murray, NJ 07865, USA 53 | -742135189 413700272 367 Prospect Road, Chester, NY 10918, USA 54 | -740282632 404310607 10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA 55 | -746201391 409319800 11 Ward Street, Mount Arlington, NJ 07856, USA 56 | -742108603 406685311 300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA 57 | -749142781 419018117 43 Dreher Road, Roscoe, NY 12776, USA 58 | -745148837 412856162 Swan Street, Pine Island, NY 10969, USA 59 | -746721964 416560744 66 Pleasantview Avenue, Monticello, NY 12701, USA 60 | -749836354 405314270 61 | -743327440 414219548 62 | -742900616 415534177 565 Winding Hills Road, Montgomery, NY 12549, USA 63 | -749127080 406898530 231 Rocky Run Road, Glen Gardner, NJ 08826, USA 64 | -741670168 407586880 100 Mount Pleasant Avenue, Newark, NJ 07104, USA 65 | -742870190 400106455 517-521 Huntington Drive, Manchester Township, NJ 08759, USA 66 | -746793294 400066188 67 | -744102673 418803880 40 Mountain Road, Napanoch, NY 12458, USA 68 | -747895140 414204288 69 | -740615601 414777405 70 | -747175374 415464475 48 North Road, Forestburgh, NY 12777, USA 71 | -746376177 404062378 72 | -749285130 405688272 73 | -748788996 400342070 74 | -744157964 401809022 75 | -740517141 404226644 9 Thompson Avenue, Leonardo, NJ 07737, USA 76 | -747871659 410322033 77 | -747742727 407100674 78 | -741718005 418811433 213 Bush Road, Stone Ridge, NY 12484, USA 79 | -743850945 415034302 80 | -743694161 411349992 81 | -744759616 404839914 1-17 Bergen Court, New Brunswick, NJ 08901, USA 82 | -745957854 414638017 35 Oakland Valley Road, Cuddebackville, NY 12729, USA 83 | -740173578 412127800 84 | -747964303 401263460 85 | -749086026 412843391 86 | -743067823 418512773 87 | -740835638 404318328 42-102 Main Street, Belford, NJ 07718, USA 88 | -741172328 419020746 89 | -746119569 404080723 90 | -744035134 401012643 91 | -741079661 404306372 92 | -748519297 403966326 93 | -748407866 405002031 94 | -742200683 409532885 95 | -742674555 416851321 96 | -741722051 406411633 3387 Richmond Terrace, Staten Island, NY 10303, USA 97 | -744597778 413069058 261 Van Sickle Road, Goshen, NY 10924, USA 98 | -746859398 418465462 99 | -744228360 411733222 100 | -747127767 410248224 3 Hasta Way, Newton, NJ 07860, USA 101 | -------------------------------------------------------------------------------- /ext/call.h: -------------------------------------------------------------------------------- 1 | #ifndef GRPC_PERL_CALL_H 2 | #define GRPC_PERL_CALL_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | grpc_call *wrapped; 9 | } CallCTX; 10 | 11 | typedef CallCTX* Grpc__XS__Call; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /ext/call.xs: -------------------------------------------------------------------------------- 1 | Grpc::XS::Call 2 | new(const char *class, Grpc::XS::Channel channel, \ 3 | const char* method, Grpc::XS::Timeval deadline, ... ) 4 | PREINIT: 5 | CallCTX* ctx = (CallCTX *)malloc( sizeof(CallCTX) ); 6 | ctx->wrapped = NULL; 7 | CODE: 8 | 9 | // Params: 10 | // * channel - channel object 11 | // * method - string 12 | // * deadline - timeval object 13 | // * host_override - string (optional) 14 | 15 | if ( items > 5 ) { 16 | croak("Too many variables for constructor Grpc::XS::Call"); 17 | } 18 | #if defined(GRPC_VERSION_1_2) 19 | grpc_slice host_override; 20 | grpc_slice* host_override_ptr = NULL; 21 | 22 | if ( items == 5) { 23 | host_override = grpc_slice_from_sv(ST(4)); 24 | host_override_ptr = &host_override; 25 | } 26 | 27 | grpc_slice method_slice = grpc_slice_from_static_string(method); 28 | ctx->wrapped = grpc_channel_create_call( 29 | channel->wrapped, NULL, GRPC_PROPAGATE_DEFAULTS, completion_queue, 30 | method_slice, host_override_ptr, deadline->wrapped, NULL); 31 | 32 | if (host_override_ptr) { 33 | grpc_slice_unref(host_override); 34 | } 35 | grpc_slice_unref(method_slice); 36 | #else 37 | const char* host_override = NULL; 38 | if ( items == 5) { 39 | host_override = SvPV_nolen(ST(4)); 40 | } 41 | 42 | ctx->wrapped = grpc_channel_create_call( 43 | channel->wrapped, NULL, GRPC_PROPAGATE_DEFAULTS, completion_queue, 44 | method, host_override, deadline->wrapped, NULL); 45 | #endif 46 | 47 | RETVAL = ctx; 48 | OUTPUT: RETVAL 49 | 50 | SV* 51 | startBatch(Grpc::XS::Call self, ...) 52 | CODE: 53 | if ( items > 1 && ( items - 1 ) % 2 ) { 54 | croak("Expecting a hash as input to constructor"); 55 | } 56 | 57 | /** 58 | * Start a batch of RPC actions. 59 | * @param array batch Array of actions to take 60 | * @return object Object with results of all actions 61 | */ 62 | 63 | HV *result = newHV(); 64 | #if defined(GRPC_VERSION_1_2) 65 | grpc_slice send_status_details = grpc_empty_slice(); 66 | grpc_slice recv_status_details = grpc_empty_slice(); 67 | #else 68 | char *status_details = NULL; 69 | size_t status_details_capacity = 0; 70 | #endif 71 | grpc_op ops[8]; 72 | 73 | size_t op_num = 0; 74 | 75 | grpc_byte_buffer *message; 76 | grpc_status_code status; 77 | grpc_call_error error; 78 | int cancelled; 79 | 80 | char *message_str; 81 | size_t message_len; 82 | 83 | grpc_metadata_array metadata; 84 | grpc_metadata_array trailing_metadata; 85 | grpc_metadata_array recv_metadata; 86 | grpc_metadata_array recv_trailing_metadata; 87 | 88 | grpc_metadata_array_init(&metadata); 89 | grpc_metadata_array_init(&trailing_metadata); 90 | grpc_metadata_array_init(&recv_metadata); 91 | grpc_metadata_array_init(&recv_trailing_metadata); 92 | 93 | if (items<2) goto cleanup; 94 | 95 | int i; 96 | for (i = 1; i < items; i += 2 ) { 97 | SV *key = ST(i); 98 | SV *value = ST(i+1); 99 | 100 | if (!looks_like_number(key)) { 101 | croak("Expected an int for message flags"); 102 | goto cleanup; 103 | } 104 | 105 | switch(atoi(SvPV_nolen(key))) { 106 | case GRPC_OP_SEND_INITIAL_METADATA: 107 | value = SvRV(value); 108 | if (SvTYPE(value)!=SVt_PVHV) { 109 | croak("Expected a hash for GRPC_OP_SEND_INITIAL_METADATA"); 110 | goto cleanup; 111 | } 112 | if (!create_metadata_array((HV*)value, &metadata)) { 113 | croak("Bad metadata value given"); 114 | goto cleanup; 115 | } 116 | ops[op_num].data.send_initial_metadata.maybe_compression_level.is_set=0; 117 | ops[op_num].data.send_initial_metadata.count = 118 | metadata.count; 119 | ops[op_num].data.send_initial_metadata.metadata = 120 | metadata.metadata; 121 | break; 122 | case GRPC_OP_SEND_MESSAGE: 123 | value = SvRV(value); 124 | if (SvTYPE(value)!=SVt_PVHV) { 125 | croak("Expected a hash for send message"); 126 | goto cleanup; 127 | } 128 | // ops[op_num].flags = hash->{flags} & GRPC_WRITE_USED_MASK;// int 129 | SV **flags = hv_fetchs((HV*)value, "flags", 0); 130 | if (flags) { 131 | if (!looks_like_number(*flags) || !SvIOK(*flags)) { 132 | croak("Expected an int for message flags"); 133 | goto cleanup; 134 | } 135 | ops[op_num].flags = SvIV(*flags) & GRPC_WRITE_USED_MASK; 136 | } 137 | // ops[op_num].data.send_message = hash->{message}; // string 138 | SV **message_sv = hv_fetchs((HV*)value,"message",0); 139 | if (!message_sv) { 140 | croak("Missing send message"); 141 | goto cleanup; 142 | } 143 | if (!SvOK(*message_sv)) { 144 | croak("Expected an string for send message"); 145 | goto cleanup; 146 | } 147 | message_str = SvPV(*message_sv,message_len); 148 | #if !defined(GRPC_VERSION_1_1) 149 | ops[op_num].data.send_message = 150 | #else 151 | ops[op_num].data.send_message.send_message = 152 | #endif 153 | string_to_byte_buffer(message_str,message_len); 154 | break; 155 | case GRPC_OP_SEND_CLOSE_FROM_CLIENT: 156 | break; 157 | case GRPC_OP_SEND_STATUS_FROM_SERVER: 158 | if (SvROK(value)) value = SvRV(value); 159 | if (SvTYPE(value)!=SVt_PVHV) { 160 | croak("Expected a hash for send message"); 161 | goto cleanup; 162 | } 163 | 164 | // hash->{metadata} 165 | if (hv_exists((HV*)value, "metadata", strlen("metadata"))) { 166 | SV** inner_value; 167 | inner_value = hv_fetchs((HV*)value, "metadata", 0); 168 | if (!create_metadata_array((HV*)SvRV(*inner_value), &trailing_metadata)) { 169 | croak("Bad trailing metadata value given"); 170 | goto cleanup; 171 | } 172 | ops[op_num].data.send_status_from_server.trailing_metadata = 173 | trailing_metadata.metadata; 174 | ops[op_num].data.send_status_from_server.trailing_metadata_count = 175 | trailing_metadata.count; 176 | } 177 | // hash->{code} 178 | if (hv_exists((HV*)value, "code", strlen("code"))) { 179 | SV** inner_value; 180 | inner_value = hv_fetchs((HV*)value, "code", 0); 181 | if (!SvIOK(*inner_value)) { 182 | croak("Status code must be an integer"); 183 | goto cleanup; 184 | } 185 | ops[op_num].data.send_status_from_server.status = 186 | SvIV(*inner_value); 187 | } else { 188 | croak("Integer status code is required"); 189 | goto cleanup; 190 | } 191 | // hash->{details} 192 | if (hv_exists((HV*)value, "details", strlen("details"))) { 193 | SV** inner_value; 194 | inner_value = hv_fetchs((HV*)value, "details", 0); 195 | if (!SvOK(*inner_value)) { 196 | croak("Status details must be a string"); 197 | goto cleanup; 198 | } 199 | #if defined(GRPC_VERSION_1_2) 200 | send_status_details = grpc_slice_from_sv(*inner_value); 201 | ops[op_num].data.send_status_from_server.status_details = 202 | &send_status_details; 203 | #else 204 | ops[op_num].data.send_status_from_server.status_details = 205 | SvPV_nolen(*inner_value); 206 | #endif 207 | } else { 208 | croak("String status details is required"); 209 | goto cleanup; 210 | } 211 | break; 212 | case GRPC_OP_RECV_INITIAL_METADATA: 213 | #if !defined(GRPC_VERSION_1_1) 214 | ops[op_num].data.recv_initial_metadata = 215 | #else 216 | ops[op_num].data.recv_initial_metadata.recv_initial_metadata = 217 | #endif 218 | &recv_metadata; 219 | break; 220 | case GRPC_OP_RECV_MESSAGE: 221 | #if !defined(GRPC_VERSION_1_1) 222 | ops[op_num].data.recv_message = 223 | #else 224 | ops[op_num].data.recv_message.recv_message = 225 | #endif 226 | &message; 227 | break; 228 | case GRPC_OP_RECV_STATUS_ON_CLIENT: 229 | #if defined GRPC_RECV_STATUS_ON_CLIENT_HAS_ERROR_STRING 230 | ops[op_num].data.recv_status_on_client.error_string = NULL; 231 | #endif 232 | ops[op_num].data.recv_status_on_client.trailing_metadata = 233 | &recv_trailing_metadata; 234 | ops[op_num].data.recv_status_on_client.status = &status; 235 | #if defined(GRPC_VERSION_1_2) 236 | ops[op_num].data.recv_status_on_client.status_details = 237 | &recv_status_details; 238 | #else 239 | ops[op_num].data.recv_status_on_client.status_details = 240 | &status_details; 241 | ops[op_num].data.recv_status_on_client.status_details_capacity = 242 | &status_details_capacity; 243 | #endif 244 | break; 245 | case GRPC_OP_RECV_CLOSE_ON_SERVER: 246 | ops[op_num].data.recv_close_on_server.cancelled = &cancelled; 247 | break; 248 | default: 249 | croak("Unrecognized key in batch"); 250 | goto cleanup; 251 | } 252 | ops[op_num].op = (grpc_op_type)SvIV(key); 253 | ops[op_num].flags = 0; 254 | ops[op_num].reserved = NULL; 255 | op_num++; 256 | } 257 | 258 | error = grpc_call_start_batch(self->wrapped, ops, op_num, self->wrapped, 259 | NULL); 260 | 261 | if (error != GRPC_CALL_OK) { 262 | croak("start_batch was called incorrectly, error = %d",error); 263 | goto cleanup; 264 | } 265 | 266 | grpc_completion_queue_pluck(completion_queue, self->wrapped, 267 | gpr_inf_future(GPR_CLOCK_REALTIME), NULL); 268 | 269 | for (i = 0; i < op_num; i++) { 270 | switch(ops[i].op) { 271 | case GRPC_OP_SEND_INITIAL_METADATA: 272 | hv_stores(result,"send_metadata",newSViv(TRUE)); 273 | break; 274 | case GRPC_OP_SEND_MESSAGE: 275 | hv_stores(result,"send_message",newSViv(TRUE)); 276 | break; 277 | case GRPC_OP_SEND_CLOSE_FROM_CLIENT: 278 | hv_stores(result,"send_close",newSViv(TRUE)); 279 | break; 280 | case GRPC_OP_SEND_STATUS_FROM_SERVER: 281 | hv_stores(result,"send_status",newSViv(TRUE)); 282 | break; 283 | case GRPC_OP_RECV_INITIAL_METADATA: 284 | hv_stores(result,"metadata", 285 | newRV_noinc((SV *)grpc_parse_metadata_array(&recv_metadata))); 286 | break; 287 | case GRPC_OP_RECV_MESSAGE: 288 | byte_buffer_to_string(message, &message_str, &message_len); 289 | if (message_str == NULL) { 290 | hv_stores(result,"message",newSV(0));//undef 291 | } else { 292 | hv_stores(result,"message",newSVpv(message_str,message_len)); 293 | } 294 | break; 295 | case GRPC_OP_RECV_STATUS_ON_CLIENT: ; 296 | HV* recv_status = newHV(); 297 | hv_stores(recv_status,"metadata", 298 | newRV_noinc((SV *)grpc_parse_metadata_array(&recv_trailing_metadata))); 299 | hv_stores(recv_status,"code",newSViv(status)); 300 | #if defined(GRPC_VERSION_1_2) 301 | hv_stores(recv_status, "details", 302 | grpc_slice_to_sv(recv_status_details)); 303 | #else 304 | hv_stores(recv_status, "details", 305 | newSVpv(status_details, 0)); 306 | #endif 307 | hv_stores(result,"status",newRV_noinc((SV *)recv_status)); 308 | break; 309 | case GRPC_OP_RECV_CLOSE_ON_SERVER: 310 | hv_stores(result,"cancelled",newSViv(cancelled)); 311 | break; 312 | default: 313 | break; 314 | } 315 | } 316 | 317 | cleanup: 318 | grpc_metadata_array_destroy(&metadata); 319 | grpc_metadata_array_destroy(&trailing_metadata); 320 | grpc_metadata_array_destroy(&recv_metadata); 321 | grpc_metadata_array_destroy(&recv_trailing_metadata); 322 | #if defined(GRPC_VERSION_1_2) 323 | grpc_slice_unref(recv_status_details); 324 | grpc_slice_unref(send_status_details); 325 | #else 326 | if (status_details != NULL) { 327 | gpr_free(status_details); 328 | } 329 | #endif 330 | 331 | for (i = 0; i < op_num; i++) { 332 | if (ops[i].op == GRPC_OP_SEND_MESSAGE) { 333 | #if !defined(GRPC_VERSION_1_1) 334 | grpc_byte_buffer_destroy(ops[i].data.send_message); 335 | #else 336 | grpc_byte_buffer_destroy(ops[i].data.send_message.send_message); 337 | #endif 338 | } 339 | if (ops[i].op == GRPC_OP_RECV_MESSAGE) { 340 | grpc_byte_buffer_destroy(message); 341 | } 342 | } 343 | RETVAL = (SV*)newRV_noinc((SV *)result); 344 | OUTPUT: RETVAL 345 | 346 | const char* 347 | getPeer(Grpc::XS::Call self) 348 | CODE: 349 | RETVAL = grpc_call_get_peer(self->wrapped); 350 | OUTPUT: RETVAL 351 | 352 | void 353 | cancel(Grpc::XS::Call self) 354 | CODE: 355 | grpc_call_cancel(self->wrapped, NULL); 356 | OUTPUT: 357 | 358 | int 359 | setCredentials(Grpc::XS::Call self, Grpc::XS::CallCredentials creds) 360 | CODE: 361 | int error = GRPC_CALL_ERROR; 362 | error = grpc_call_set_credentials(self->wrapped, creds->wrapped); 363 | RETVAL = error; 364 | OUTPUT: RETVAL 365 | 366 | void 367 | DESTROY(Grpc::XS::Call self) 368 | CODE: 369 | if (self->wrapped != NULL) { 370 | #if defined(GRPC_VERSION_1_4) 371 | grpc_call_unref(self->wrapped); 372 | #else 373 | grpc_call_destroy(self->wrapped); 374 | #endif 375 | } 376 | Safefree(self); 377 | -------------------------------------------------------------------------------- /ext/call_credentials.h: -------------------------------------------------------------------------------- 1 | #ifndef GRPC_PERL_CALL_CREDENTIALS_H 2 | #define GRPC_PERL_CALL_CREDENTIALS_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | grpc_call_credentials *wrapped; 9 | } CallCredentialsCTX; 10 | 11 | typedef CallCredentialsCTX* Grpc__XS__CallCredentials; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /ext/call_credentials.xs: -------------------------------------------------------------------------------- 1 | Grpc::XS::CallCredentials 2 | createComposite(Grpc::XS::CallCredentials cred1, Grpc::XS::CallCredentials cred2) 3 | PREINIT: 4 | CallCredentialsCTX* ctx = (CallCredentialsCTX *)malloc( sizeof(CallCredentialsCTX) ); 5 | ctx->wrapped = NULL; 6 | CODE: 7 | ctx->wrapped = grpc_composite_call_credentials_create( 8 | cred1->wrapped, cred2->wrapped, NULL); 9 | RETVAL = ctx; 10 | OUTPUT: RETVAL 11 | 12 | Grpc::XS::CallCredentials 13 | createFromPlugin(SV* callback) 14 | PREINIT: 15 | CallCredentialsCTX* ctx = (CallCredentialsCTX *)malloc( sizeof(CallCredentialsCTX) ); 16 | ctx->wrapped = NULL; 17 | CODE: 18 | grpc_metadata_credentials_plugin plugin; 19 | plugin.get_metadata = plugin_get_metadata; 20 | plugin.destroy = plugin_destroy_state; 21 | plugin.state = (void *)SvRV(callback); 22 | plugin.type = ""; 23 | ctx->wrapped = grpc_metadata_credentials_create_from_plugin(plugin, 24 | #if GRPC_METADATA_CREDENTIALS_CRATE_FROM_PLUGIN_HAS_3_ARGS 25 | GRPC_PRIVACY_AND_INTEGRITY, 26 | #endif 27 | NULL); 28 | SvREFCNT_inc(callback); 29 | RETVAL = ctx; 30 | OUTPUT: RETVAL 31 | 32 | void 33 | DESTROY(Grpc::XS::CallCredentials self) 34 | CODE: 35 | if (self->wrapped != NULL) { 36 | grpc_call_credentials_release(self->wrapped); 37 | } 38 | Safefree(self); 39 | -------------------------------------------------------------------------------- /ext/channel.h: -------------------------------------------------------------------------------- 1 | #ifndef GRPC_PERL_CHANNEL_H 2 | #define GRPC_PERL_CHANNEL_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | grpc_channel *wrapped; 9 | } ChannelCTX; 10 | 11 | typedef ChannelCTX* Grpc__XS__Channel; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /ext/channel.xs: -------------------------------------------------------------------------------- 1 | Grpc::XS::Channel 2 | new(const char *class, const char* target, ... ) 3 | PREINIT: 4 | ChannelCTX* ctx = (ChannelCTX *)malloc( sizeof(ChannelCTX) ); 5 | ctx->wrapped = NULL; 6 | CODE: 7 | if ( items > 2 && ( items - 2 ) % 2 ) { 8 | croak("Expecting a hash as input to constructor"); 9 | } 10 | 11 | Grpc__XS__ChannelCredentials creds = NULL; 12 | 13 | // channel, args_hash 14 | // hash->{credentials} - credentials object (optional) 15 | 16 | int i; 17 | HV *hash = newHV(); 18 | if (items>2) { 19 | for (i = 2; i < items; i += 2 ) { 20 | SV *key = ST(i); 21 | if (!strcmp(SvPV_nolen(key), "credentials")) { 22 | if (!sv_isobject(ST(i+1)) || 23 | !sv_derived_from(ST(i+1),"Grpc::XS::ChannelCredentials")) { 24 | croak("credentials is not a credentials object"); 25 | } else { 26 | IV tmp = SvIV((SV*)SvRV(ST(i+1))); 27 | creds = INT2PTR(Grpc__XS__ChannelCredentials,tmp); 28 | } 29 | } else { 30 | SV *value = newSVsv(ST(i+1)); 31 | hv_store_ent(hash,key,value,0); 32 | } 33 | } 34 | } 35 | 36 | grpc_channel_args args; 37 | perl_grpc_read_args_array(hash, &args); 38 | 39 | if (creds == NULL) { 40 | #ifdef GRPC_NO_INSECURE_BUILD 41 | grpc_channel_credentials * insecure_cred = grpc_insecure_credentials_create(); 42 | ctx->wrapped = grpc_channel_create(target, insecure_cred, &args); 43 | grpc_channel_credentials_release(insecure_cred); 44 | #else 45 | ctx->wrapped = grpc_insecure_channel_create(target, &args, NULL); 46 | #endif 47 | } else { 48 | gpr_log(GPR_DEBUG, "Initialized secure channel"); 49 | #ifdef GRPC_NO_INSECURE_BUILD 50 | ctx->wrapped = grpc_channel_create(target, creds->wrapped, &args); 51 | #else 52 | ctx->wrapped = 53 | grpc_secure_channel_create(creds->wrapped, target, &args, NULL); 54 | #endif 55 | } 56 | free(args.args); 57 | 58 | RETVAL = ctx; 59 | OUTPUT: RETVAL 60 | 61 | const char* 62 | getTarget(Grpc::XS::Channel self) 63 | CODE: 64 | RETVAL = grpc_channel_get_target(self->wrapped); 65 | OUTPUT: RETVAL 66 | 67 | long 68 | getConnectivityState(Grpc::XS::Channel self, ... ) 69 | CODE: 70 | int try_to_connect = 0; 71 | if ( items > 1 ) { 72 | if (items > 2 || !SvIOK(ST(1))) { 73 | croak("Invalid param getConnectivityState"); 74 | } 75 | try_to_connect = SvUV(ST(1)); 76 | } 77 | RETVAL = grpc_channel_check_connectivity_state(self->wrapped, try_to_connect); 78 | OUTPUT: RETVAL 79 | 80 | int 81 | watchConnectivityState(Grpc::XS::Channel self, long last_state, Grpc::XS::Timeval deadline) 82 | CODE: 83 | grpc_channel_watch_connectivity_state( 84 | self->wrapped, (grpc_connectivity_state)last_state, 85 | deadline->wrapped, completion_queue, NULL); 86 | grpc_event event = grpc_completion_queue_pluck( 87 | completion_queue, NULL, 88 | gpr_inf_future(GPR_CLOCK_REALTIME), NULL); 89 | RETVAL = event.success; 90 | OUTPUT: RETVAL 91 | 92 | void 93 | close(Grpc::XS::Channel self) 94 | CODE: 95 | if (self->wrapped != NULL) { 96 | grpc_channel_destroy(self->wrapped); 97 | self->wrapped = NULL; 98 | } 99 | 100 | void 101 | DESTROY(Grpc::XS::Channel self) 102 | CODE: 103 | if (self->wrapped != NULL) { 104 | grpc_channel_destroy(self->wrapped); 105 | } 106 | Safefree(self); 107 | -------------------------------------------------------------------------------- /ext/channel_credentials.h: -------------------------------------------------------------------------------- 1 | #ifndef GRPC_PERL_CHANNEL_CREDENTIALS_H 2 | #define GRPC_PERL_CHANNEL_CREDENTIALS_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | grpc_channel_credentials *wrapped; 9 | } ChannelCredentialsCTX; 10 | 11 | typedef ChannelCredentialsCTX* Grpc__XS__ChannelCredentials; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /ext/channel_credentials.xs: -------------------------------------------------------------------------------- 1 | Grpc::XS::ChannelCredentials 2 | createDefault() 3 | PREINIT: 4 | ChannelCredentialsCTX* ctx = (ChannelCredentialsCTX *)malloc( sizeof(ChannelCredentialsCTX) ); 5 | CODE: 6 | ctx->wrapped = grpc_google_default_credentials_create( 7 | #if GRPC_GOOGLE_DEFAULT_CREDENTIALS_CREATE_HAS_1_ARG 8 | NULL 9 | #endif 10 | ); 11 | RETVAL = ctx; 12 | OUTPUT: RETVAL 13 | 14 | Grpc::XS::ChannelCredentials 15 | createSsl(...) 16 | PREINIT: 17 | ChannelCredentialsCTX* ctx = (ChannelCredentialsCTX *)malloc( sizeof(ChannelCredentialsCTX) ); 18 | CODE: 19 | if ( items % 2 ) { 20 | croak("Expecting a hash as input to channel credentials constructor"); 21 | } 22 | 23 | // @param string pem_root_certs PEM encoding of the server root certificates 24 | // @param string pem_private_key PEM encoding of the client's private key 25 | // (optional) 26 | // @param string pem_cert_chain PEM encoding of the client's certificate chain 27 | // (optional) 28 | // @return ChannelCredentials The new SSL credentials object 29 | 30 | const char* pem_root_certs = NULL; 31 | 32 | grpc_ssl_pem_key_cert_pair pem_key_cert_pair; 33 | pem_key_cert_pair.private_key = pem_key_cert_pair.cert_chain = NULL; 34 | 35 | int i; 36 | for (i = 0; i < items; i += 2 ) { 37 | const char *key = SvPV_nolen(ST(i)); 38 | if (!strcmp( key, "pem_root_certs")) { 39 | if (SvOK(ST(i+1))) 40 | pem_root_certs = SvPV_nolen(ST(i+1)); 41 | } else if (!strcmp( key, "pem_private_key")) { 42 | if (SvOK(ST(i+1))) 43 | pem_key_cert_pair.private_key = SvPV_nolen(ST(i+1)); 44 | } else if (!strcmp( key, "pem_cert_chain")) { 45 | if (SvOK(ST(i+1))) 46 | pem_key_cert_pair.cert_chain = SvPV_nolen(ST(i+1)); 47 | } 48 | } 49 | 50 | ctx->wrapped = grpc_ssl_credentials_create( 51 | pem_root_certs, 52 | pem_key_cert_pair.private_key == NULL ? NULL : &pem_key_cert_pair, NULL 53 | #ifdef GRPC_SSL_CREDENTIALS_HAS_4_ARGS 54 | , NULL 55 | #endif 56 | ); 57 | 58 | RETVAL = ctx; 59 | OUTPUT: RETVAL 60 | 61 | Grpc::XS::ChannelCredentials 62 | createComposite(Grpc::XS::ChannelCredentials cred1, Grpc::XS::CallCredentials cred2) 63 | PREINIT: 64 | ChannelCredentialsCTX* ctx = (ChannelCredentialsCTX *)malloc( sizeof(ChannelCredentialsCTX) ); 65 | CODE: 66 | ctx->wrapped = grpc_composite_channel_credentials_create( 67 | cred1->wrapped, cred2->wrapped, NULL); 68 | RETVAL = ctx; 69 | OUTPUT: RETVAL 70 | 71 | Grpc::XS::ChannelCredentials 72 | createInsecure() 73 | CODE: 74 | XSRETURN_UNDEF; 75 | OUTPUT: 76 | 77 | void 78 | DESTROY(Grpc::XS::ChannelCredentials self) 79 | CODE: 80 | grpc_channel_credentials_release(self->wrapped); 81 | Safefree(self); 82 | -------------------------------------------------------------------------------- /ext/constants.h: -------------------------------------------------------------------------------- 1 | #ifndef GRPC_PERL_CONSTANTS_H 2 | #define GRPC_PERL_CONSTANTS_H 3 | 4 | #include 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /ext/constants.xs: -------------------------------------------------------------------------------- 1 | int 2 | CALL_OK() 3 | CODE: 4 | RETVAL = GRPC_CALL_OK; 5 | OUTPUT: RETVAL 6 | 7 | int 8 | CALL_ERROR() 9 | CODE: 10 | RETVAL = GRPC_CALL_ERROR; 11 | OUTPUT: RETVAL 12 | 13 | int 14 | CALL_ERROR_NOT_ON_SERVER() 15 | CODE: 16 | RETVAL = GRPC_CALL_ERROR_NOT_ON_SERVER; 17 | OUTPUT: RETVAL 18 | 19 | int 20 | CALL_ERROR_NOT_ON_CLIENT() 21 | CODE: 22 | RETVAL = GRPC_CALL_ERROR_NOT_ON_CLIENT; 23 | OUTPUT: RETVAL 24 | 25 | int 26 | CALL_ERROR_ALREADY_INVOKED() 27 | CODE: 28 | RETVAL = GRPC_CALL_ERROR_ALREADY_INVOKED; 29 | OUTPUT: RETVAL 30 | 31 | int 32 | CALL_ERROR_NOT_INVOKED() 33 | CODE: 34 | RETVAL = GRPC_CALL_ERROR_NOT_INVOKED; 35 | OUTPUT: RETVAL 36 | 37 | int 38 | CALL_ERROR_ALREADY_FINISHED() 39 | CODE: 40 | RETVAL = GRPC_CALL_ERROR_ALREADY_FINISHED; 41 | OUTPUT: RETVAL 42 | 43 | int 44 | CALL_ERROR_TOO_MANY_OPERATIONS() 45 | CODE: 46 | RETVAL = GRPC_CALL_ERROR_TOO_MANY_OPERATIONS; 47 | OUTPUT: RETVAL 48 | 49 | int 50 | CALL_ERROR_INVALID_FLAGS() 51 | CODE: 52 | RETVAL = GRPC_CALL_ERROR_INVALID_FLAGS; 53 | OUTPUT: RETVAL 54 | 55 | int 56 | WRITE_BUFFER_HINT() 57 | CODE: 58 | RETVAL = GRPC_WRITE_BUFFER_HINT; 59 | OUTPUT: RETVAL 60 | 61 | int 62 | WRITE_NO_COMPRESS() 63 | CODE: 64 | RETVAL = GRPC_WRITE_NO_COMPRESS; 65 | OUTPUT: RETVAL 66 | 67 | int 68 | STATUS_OK() 69 | CODE: 70 | RETVAL = GRPC_STATUS_OK; 71 | OUTPUT: RETVAL 72 | 73 | int 74 | STATUS_CANCELLED() 75 | CODE: 76 | RETVAL = GRPC_STATUS_CANCELLED; 77 | OUTPUT: RETVAL 78 | 79 | int 80 | STATUS_UNKNOWN() 81 | CODE: 82 | RETVAL = GRPC_STATUS_UNKNOWN; 83 | OUTPUT: RETVAL 84 | 85 | int 86 | STATUS_INVALID_ARGUMENT() 87 | CODE: 88 | RETVAL = GRPC_STATUS_INVALID_ARGUMENT; 89 | OUTPUT: RETVAL 90 | 91 | int 92 | STATUS_DEADLINE_EXCEEDED() 93 | CODE: 94 | RETVAL = GRPC_STATUS_DEADLINE_EXCEEDED; 95 | OUTPUT: RETVAL 96 | 97 | int 98 | STATUS_NOT_FOUND() 99 | CODE: 100 | RETVAL = GRPC_STATUS_NOT_FOUND; 101 | OUTPUT: RETVAL 102 | 103 | int 104 | STATUS_ALREADY_EXISTS() 105 | CODE: 106 | RETVAL = GRPC_STATUS_ALREADY_EXISTS; 107 | OUTPUT: RETVAL 108 | 109 | int 110 | STATUS_PERMISSION_DENIED() 111 | CODE: 112 | RETVAL = GRPC_STATUS_PERMISSION_DENIED; 113 | OUTPUT: RETVAL 114 | 115 | int 116 | STATUS_UNAUTHENTICATED() 117 | CODE: 118 | RETVAL = GRPC_STATUS_UNAUTHENTICATED; 119 | OUTPUT: RETVAL 120 | 121 | int 122 | STATUS_RESOURCE_EXHAUSTED() 123 | CODE: 124 | RETVAL = GRPC_STATUS_RESOURCE_EXHAUSTED; 125 | OUTPUT: RETVAL 126 | 127 | int 128 | STATUS_FAILED_PRECONDITION() 129 | CODE: 130 | RETVAL = GRPC_STATUS_FAILED_PRECONDITION; 131 | OUTPUT: RETVAL 132 | 133 | int 134 | STATUS_ABORTED() 135 | CODE: 136 | RETVAL = GRPC_STATUS_ABORTED; 137 | OUTPUT: RETVAL 138 | 139 | int 140 | STATUS_OUT_OF_RANGE() 141 | CODE: 142 | RETVAL = GRPC_STATUS_OUT_OF_RANGE; 143 | OUTPUT: RETVAL 144 | 145 | int 146 | STATUS_UNIMPLEMENTED() 147 | CODE: 148 | RETVAL = GRPC_STATUS_UNIMPLEMENTED; 149 | OUTPUT: RETVAL 150 | 151 | int 152 | STATUS_INTERNAL() 153 | CODE: 154 | RETVAL = GRPC_STATUS_INTERNAL; 155 | OUTPUT: RETVAL 156 | 157 | int 158 | STATUS_UNAVAILABLE() 159 | CODE: 160 | RETVAL = GRPC_STATUS_UNAVAILABLE; 161 | OUTPUT: RETVAL 162 | 163 | int 164 | STATUS_DATA_LOSS() 165 | CODE: 166 | RETVAL = GRPC_STATUS_DATA_LOSS; 167 | OUTPUT: RETVAL 168 | 169 | int 170 | OP_SEND_INITIAL_METADATA() 171 | CODE: 172 | RETVAL = GRPC_OP_SEND_INITIAL_METADATA; 173 | OUTPUT: RETVAL 174 | 175 | int 176 | OP_SEND_MESSAGE() 177 | CODE: 178 | RETVAL = GRPC_OP_SEND_MESSAGE; 179 | OUTPUT: RETVAL 180 | 181 | int 182 | OP_SEND_CLOSE_FROM_CLIENT() 183 | CODE: 184 | RETVAL = GRPC_OP_SEND_CLOSE_FROM_CLIENT; 185 | OUTPUT: RETVAL 186 | 187 | int 188 | OP_SEND_STATUS_FROM_SERVER() 189 | CODE: 190 | RETVAL = GRPC_OP_SEND_STATUS_FROM_SERVER; 191 | OUTPUT: RETVAL 192 | 193 | int 194 | OP_RECV_INITIAL_METADATA() 195 | CODE: 196 | RETVAL = GRPC_OP_RECV_INITIAL_METADATA; 197 | OUTPUT: RETVAL 198 | 199 | int 200 | OP_RECV_MESSAGE() 201 | CODE: 202 | RETVAL = GRPC_OP_RECV_MESSAGE; 203 | OUTPUT: RETVAL 204 | 205 | int 206 | OP_RECV_STATUS_ON_CLIENT() 207 | CODE: 208 | RETVAL = GRPC_OP_RECV_STATUS_ON_CLIENT; 209 | OUTPUT: RETVAL 210 | 211 | int 212 | OP_RECV_CLOSE_ON_SERVER() 213 | CODE: 214 | RETVAL = GRPC_OP_RECV_CLOSE_ON_SERVER; 215 | OUTPUT: RETVAL 216 | 217 | int 218 | CHANNEL_IDLE() 219 | CODE: 220 | RETVAL = GRPC_CHANNEL_IDLE; 221 | OUTPUT: RETVAL 222 | 223 | int 224 | CHANNEL_CONNECTING() 225 | CODE: 226 | RETVAL = GRPC_CHANNEL_CONNECTING; 227 | OUTPUT: RETVAL 228 | 229 | int 230 | CHANNEL_READY() 231 | CODE: 232 | RETVAL = GRPC_CHANNEL_READY; 233 | OUTPUT: RETVAL 234 | 235 | int 236 | CHANNEL_TRANSIENT_FAILURE() 237 | CODE: 238 | RETVAL = GRPC_CHANNEL_TRANSIENT_FAILURE; 239 | OUTPUT: RETVAL 240 | 241 | int 242 | CHANNEL_FATAL_FAILURE() 243 | CODE: 244 | RETVAL = GRPC_CHANNEL_SHUTDOWN; 245 | OUTPUT: RETVAL 246 | 247 | int 248 | XS_GPR_CLOCK_MONOTONIC() 249 | CODE: 250 | RETVAL = GPR_CLOCK_MONOTONIC; 251 | OUTPUT: RETVAL 252 | 253 | int 254 | XS_GPR_CLOCK_REALTIME() 255 | CODE: 256 | RETVAL = GPR_CLOCK_REALTIME; 257 | OUTPUT: RETVAL 258 | 259 | int 260 | XS_GPR_CLOCK_PRECISE() 261 | CODE: 262 | RETVAL = GPR_CLOCK_PRECISE; 263 | OUTPUT: RETVAL 264 | 265 | int 266 | XS_GPR_TIMESPAN() 267 | CODE: 268 | RETVAL = GPR_TIMESPAN; 269 | OUTPUT: RETVAL 270 | -------------------------------------------------------------------------------- /ext/server.h: -------------------------------------------------------------------------------- 1 | #ifndef GRPC_PERL_SERVER_H 2 | #define GRPC_PERL_SERVER_H 3 | 4 | #include 5 | 6 | typedef struct { 7 | grpc_server *wrapped; 8 | } ServerCTX; 9 | 10 | typedef ServerCTX* Grpc__XS__Server; 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /ext/server.xs: -------------------------------------------------------------------------------- 1 | Grpc::XS::Server 2 | new(const char *class, ... ) 3 | PREINIT: 4 | ServerCTX* ctx = (ServerCTX *)malloc( sizeof(ServerCTX) ); 5 | ctx->wrapped = NULL; 6 | CODE: 7 | if ( items > 1 && ( items - 1 ) % 2 ) { 8 | croak("Expecting a hash as input to constructor"); 9 | } 10 | 11 | int i; 12 | HV *hash = newHV(); 13 | if (items>1) { 14 | for (i = 1; i < items; i += 2 ) { 15 | SV *key = ST(i); 16 | SV *value = newSVsv(ST(i+1)); 17 | hv_store_ent(hash,key,value,0); 18 | } 19 | grpc_channel_args args; 20 | perl_grpc_read_args_array(hash, &args); 21 | ctx->wrapped = grpc_server_create(&args, NULL); 22 | free(args.args); 23 | } else { 24 | ctx->wrapped = grpc_server_create(NULL, NULL); 25 | } 26 | 27 | grpc_server_register_completion_queue(ctx->wrapped,completion_queue,NULL); 28 | 29 | RETVAL = ctx; 30 | OUTPUT: RETVAL 31 | 32 | SV* 33 | requestCall(Grpc::XS::Server self) 34 | CODE: 35 | grpc_call_error error_code; 36 | grpc_call *call; 37 | grpc_call_details details; 38 | grpc_metadata_array metadata; 39 | grpc_event event; 40 | 41 | grpc_call_details_init(&details); 42 | grpc_metadata_array_init(&metadata); 43 | 44 | error_code = 45 | grpc_server_request_call(self->wrapped, &call, &details, &metadata, 46 | completion_queue, completion_queue, NULL); 47 | 48 | if (error_code != GRPC_CALL_OK) { 49 | warn("request_call failed, error = %d",error_code); 50 | goto cleanup; 51 | } 52 | 53 | event = grpc_completion_queue_pluck(completion_queue, NULL, 54 | gpr_inf_future(GPR_CLOCK_REALTIME), NULL); 55 | 56 | if (!event.success) { 57 | warn("Failed to request a call for some reason"); 58 | goto cleanup; 59 | } 60 | 61 | HV* result = newHV(); 62 | 63 | // add call object instance to hash 64 | CallCTX* call_ctx = (CallCTX *)malloc( sizeof(CallCTX) ); 65 | call_ctx->wrapped = call; 66 | SV* call_sv = sv_setref_pv(newSV (0), "Grpc::XS::Call", (void*)call_ctx) ; 67 | hv_stores(result,"call", call_sv); 68 | 69 | // add time object instance to hash 70 | TimevalCTX* timeval_ctx = (TimevalCTX *)malloc( sizeof(TimevalCTX) ); 71 | timeval_ctx->wrapped = details.deadline; 72 | SV* timeval_sv = sv_setref_pv(newSV (0), "Grpc::XS::Timeval", (void*)timeval_ctx); 73 | hv_stores(result,"absolute_deadline", timeval_sv); 74 | 75 | hv_stores(result,"method",grpc_slice_or_string_to_sv(details.method)); 76 | hv_stores(result,"host",grpc_slice_or_string_to_sv(details.host)); 77 | 78 | hv_stores(result,"metadata", 79 | newRV((SV*)grpc_parse_metadata_array(&metadata))); 80 | 81 | cleanup: 82 | grpc_call_details_destroy(&details); 83 | grpc_metadata_array_destroy(&metadata); 84 | RETVAL = (SV*)newRV_noinc((SV *)result); 85 | OUTPUT: RETVAL 86 | 87 | long 88 | addHttp2Port(Grpc::XS::Server self, SV* addr) 89 | CODE: 90 | #ifdef GRPC_NO_INSECURE_BUILD 91 | { 92 | grpc_server_credentials * insecure_cred = grpc_insecure_server_credentials_create(); 93 | RETVAL = grpc_server_add_http2_port(self->wrapped, SvPV_nolen(addr), insecure_cred); 94 | grpc_server_credentials_release(insecure_cred); 95 | } 96 | #else 97 | RETVAL = grpc_server_add_insecure_http2_port(self->wrapped, SvPV_nolen(addr)); 98 | #endif 99 | OUTPUT: RETVAL 100 | 101 | long 102 | addSecureHttp2Port(Grpc::XS::Server self, SV* addr, Grpc::XS::ServerCredentials creds) 103 | CODE: 104 | RETVAL = 105 | #ifdef GRPC_NO_INSECURE_BUILD 106 | grpc_server_add_http2_port( 107 | #else 108 | grpc_server_add_secure_http2_port( 109 | #endif 110 | self->wrapped, SvPV_nolen(addr), creds->wrapped); 111 | OUTPUT: RETVAL 112 | 113 | void 114 | start(Grpc::XS::Server self) 115 | CODE: 116 | grpc_server_start(self->wrapped); 117 | OUTPUT: 118 | 119 | void 120 | DESTROY(Grpc::XS::Server self) 121 | CODE: 122 | if (self->wrapped != NULL) { 123 | grpc_server_shutdown_and_notify(self->wrapped, completion_queue, NULL); 124 | grpc_server_cancel_all_calls(self->wrapped); 125 | grpc_completion_queue_pluck(completion_queue, NULL, 126 | gpr_inf_future(GPR_CLOCK_REALTIME), NULL); 127 | grpc_server_destroy(self->wrapped); 128 | } 129 | Safefree(self); 130 | -------------------------------------------------------------------------------- /ext/server_credentials.h: -------------------------------------------------------------------------------- 1 | #ifndef GRPC_PERL_SERVER_CREDENTIALS_H 2 | #define GRPC_PERL_SERVER_CREDENTIALS_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | grpc_server_credentials *wrapped; 9 | } ServerCredentialsCTX; 10 | 11 | typedef ServerCredentialsCTX* Grpc__XS__ServerCredentials; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /ext/server_credentials.xs: -------------------------------------------------------------------------------- 1 | Grpc::XS::ServerCredentials 2 | createSsl(const char *class, ...) 3 | PREINIT: 4 | ServerCredentialsCTX* ctx = (ServerCredentialsCTX *)malloc( sizeof(ServerCredentialsCTX) ); 5 | ctx->wrapped = NULL; 6 | CODE: 7 | if (items % 2) { 8 | croak("Expecting a hash as input to server credentials constructor"); 9 | } 10 | 11 | // @param string pem_root_certs PEM encoding of the server root certificates (optional) 12 | // @param string pem_private_key PEM encoding of the client's private key 13 | // @param string pem_cert_chain PEM encoding of the client's certificate chain 14 | // @return Credentials The new SSL credentials object 15 | 16 | const char* pem_root_certs = NULL; 17 | 18 | grpc_ssl_pem_key_cert_pair pem_key_cert_pair; 19 | pem_key_cert_pair.private_key = pem_key_cert_pair.cert_chain = NULL; 20 | 21 | int i; 22 | for (i = 0; i < items; i += 2 ) { 23 | const char *key = SvPV_nolen(ST(i)); 24 | if (!strcmp( key, "pem_root_certs")) { 25 | if (SvOK(ST(i+1))) 26 | pem_root_certs = SvPV_nolen(ST(i+1)); 27 | } else if (!strcmp( key, "pem_private_key")) { 28 | if (SvOK(ST(i+1))) 29 | pem_key_cert_pair.private_key = SvPV_nolen(ST(i+1)); 30 | } else if (!strcmp( key, "pem_cert_chain")) { 31 | if (SvOK(ST(i+1))) 32 | pem_key_cert_pair.cert_chain = SvPV_nolen(ST(i+1)); 33 | } 34 | } 35 | 36 | ctx->wrapped = grpc_ssl_server_credentials_create( 37 | pem_root_certs, 38 | pem_key_cert_pair.private_key == NULL ? NULL : &pem_key_cert_pair, 39 | 1, 0, NULL); 40 | 41 | RETVAL = ctx; 42 | OUTPUT: RETVAL 43 | 44 | void 45 | DESTROY(Grpc::XS::ServerCredentials self) 46 | CODE: 47 | if (self->wrapped != NULL) { 48 | grpc_server_credentials_release(self->wrapped); 49 | } 50 | Safefree(self); 51 | -------------------------------------------------------------------------------- /ext/timeval.h: -------------------------------------------------------------------------------- 1 | #ifndef GRPC_PERL_TIMEVAL_H 2 | #define GRPC_PERL_TIMEVAL_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | gpr_timespec wrapped; 9 | } TimevalCTX; 10 | 11 | typedef TimevalCTX* Grpc__XS__Timeval; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /ext/timeval.xs: -------------------------------------------------------------------------------- 1 | ## constructor, optionally takes long to create timespan timeval object 2 | 3 | Grpc::XS::Timeval 4 | new(const char *class, ... ) 5 | PREINIT: 6 | TimevalCTX* ctx = (TimevalCTX *)malloc( sizeof(TimevalCTX) ); 7 | CODE: 8 | if (items>1) { 9 | ctx->wrapped = 10 | gpr_time_from_micros(SvUV(ST(1)),GPR_TIMESPAN); 11 | } else { 12 | ctx->wrapped = gpr_time_0(GPR_CLOCK_REALTIME); 13 | } 14 | RETVAL = ctx; 15 | OUTPUT: RETVAL 16 | 17 | long 18 | similar( Grpc::XS::Timeval t1, Grpc::XS::Timeval t2, Grpc::XS::Timeval thres ) 19 | CODE: 20 | RETVAL = gpr_time_similar(t1->wrapped, t2->wrapped, thres->wrapped); 21 | OUTPUT: RETVAL 22 | 23 | long 24 | compare( Grpc::XS::Timeval t1, Grpc::XS::Timeval t2 ) 25 | CODE: 26 | RETVAL = gpr_time_cmp(t1->wrapped,t2->wrapped); 27 | OUTPUT: RETVAL 28 | 29 | Grpc::XS::Timeval 30 | substract( Grpc::XS::Timeval t1, Grpc::XS::Timeval t2 ) 31 | PREINIT: 32 | TimevalCTX* ctx = (TimevalCTX *)malloc( sizeof(TimevalCTX) ); 33 | CODE: 34 | ctx->wrapped = gpr_time_sub(t1->wrapped,t2->wrapped); 35 | RETVAL = ctx; 36 | OUTPUT: RETVAL 37 | 38 | Grpc::XS::Timeval 39 | add( Grpc::XS::Timeval t1, Grpc::XS::Timeval t2 ) 40 | PREINIT: 41 | TimevalCTX* ctx = (TimevalCTX *)malloc( sizeof(TimevalCTX) ); 42 | CODE: 43 | ctx->wrapped = gpr_time_add(t1->wrapped,t2->wrapped); 44 | RETVAL = ctx; 45 | OUTPUT: RETVAL 46 | 47 | ## sleep until this, or given timeval. 48 | 49 | void 50 | sleepUntil(Grpc::XS::Timeval timeval) 51 | CODE: 52 | gpr_sleep_until(timeval->wrapped); 53 | OUTPUT: 54 | 55 | ## static methods to create specific timeval values 56 | 57 | Grpc::XS::Timeval 58 | now() 59 | PREINIT: 60 | TimevalCTX* ctx = (TimevalCTX *)malloc( sizeof(TimevalCTX) ); 61 | CODE: 62 | ctx->wrapped = gpr_now(GPR_CLOCK_REALTIME); 63 | RETVAL = ctx; 64 | OUTPUT: RETVAL 65 | 66 | Grpc::XS::Timeval 67 | zero() 68 | PREINIT: 69 | TimevalCTX* ctx = (TimevalCTX *)malloc( sizeof(TimevalCTX) ); 70 | CODE: 71 | ctx->wrapped = gpr_time_0(GPR_CLOCK_REALTIME); 72 | RETVAL = ctx; 73 | OUTPUT: RETVAL 74 | 75 | Grpc::XS::Timeval 76 | infFuture() 77 | PREINIT: 78 | TimevalCTX* ctx = (TimevalCTX *)malloc( sizeof(TimevalCTX) ); 79 | CODE: 80 | ctx->wrapped = gpr_inf_future(GPR_CLOCK_REALTIME); 81 | RETVAL = ctx; 82 | OUTPUT: RETVAL 83 | 84 | Grpc::XS::Timeval 85 | infPast() 86 | PREINIT: 87 | TimevalCTX* ctx = (TimevalCTX *)malloc( sizeof(TimevalCTX) ); 88 | CODE: 89 | ctx->wrapped = gpr_inf_past(GPR_CLOCK_REALTIME); 90 | RETVAL = ctx; 91 | OUTPUT: RETVAL 92 | 93 | ## helper methods to access timespec struct values 94 | 95 | unsigned long 96 | getTvNsec(Grpc::XS::Timeval self) 97 | CODE: 98 | RETVAL = self->wrapped.tv_nsec; 99 | OUTPUT: RETVAL 100 | 101 | unsigned long 102 | getTvSec(Grpc::XS::Timeval self) 103 | CODE: 104 | RETVAL = self->wrapped.tv_sec; 105 | OUTPUT: RETVAL 106 | 107 | unsigned long 108 | getClockType(Grpc::XS::Timeval self) 109 | CODE: 110 | RETVAL = self->wrapped.clock_type; 111 | OUTPUT: RETVAL 112 | 113 | ## cleanup 114 | 115 | void 116 | DESTROY(Grpc::XS::Timeval self) 117 | CODE: 118 | Safefree(self); 119 | -------------------------------------------------------------------------------- /lib/Grpc/Client/AbstractCall.pm: -------------------------------------------------------------------------------- 1 | package Grpc::Client::AbstractCall; 2 | use strict; 3 | use warnings; 4 | 5 | use Grpc::XS::Call; 6 | use Grpc::XS::CallCredentials; 7 | use Grpc::XS::Timeval; 8 | 9 | ## Create a new Call wrapper object. 10 | ## 11 | ## @param Channel $channel The channel object to communicate on 12 | ## @param string $method The method to call on the 13 | ## remote server 14 | ## @param callback $serialize A callback function to serialize 15 | ## the request 16 | ## @param callback $deserialize A callback function to deserialize 17 | ## the response 18 | ## @param array $options Call options (optional) 19 | 20 | sub new { 21 | my $proto = shift; 22 | my $channel = shift; 23 | my $method = shift; 24 | my $serialize = shift; 25 | my $deserialize = shift; 26 | my $options = shift || {}; 27 | my $timeout = $options->{timeout}; 28 | my $call_credentials_callback = $options->{call_credentials_callback}; 29 | 30 | my $deadline; 31 | if (defined($timeout) && $timeout =~ /^\d+$/) { 32 | my $now = Grpc::XS::Timeval::now(); 33 | my $delta = new Grpc::XS::Timeval($timeout); 34 | $deadline = Grpc::XS::Timeval::add($now,$delta); 35 | } else { 36 | $deadline = Grpc::XS::Timeval::infFuture(); 37 | } 38 | 39 | my $call = new Grpc::XS::Call($channel, $method, $deadline); 40 | 41 | my $call_credentials; 42 | if (defined($call_credentials_callback)) { 43 | $call_credentials = Grpc::XS::CallCredentials::createFromPlugin( 44 | $call_credentials_callback); 45 | $call->setCredentials($call_credentials); 46 | } 47 | 48 | my $self = { 49 | '_call' => $call, 50 | '_channel' => $channel, ## keep in scope together with call 51 | '_serialize' => $serialize, 52 | '_deserialize' => $deserialize, 53 | '_metadata' => undef, 54 | }; 55 | bless $self,$proto; 56 | 57 | return $self; 58 | } 59 | 60 | ## @return The metadata sent by the server. 61 | 62 | sub getMetadata { 63 | my $self = shift; 64 | return $self->{_metadata}; 65 | } 66 | 67 | ## @return string The URI of the endpoint. 68 | 69 | sub getPeer { 70 | my $self = shift; 71 | return $self->{_call}->getPeer(); 72 | } 73 | 74 | ## Cancels the call. 75 | 76 | sub cancel { 77 | my $self = shift; 78 | return $self->{_call}->cancel(); 79 | } 80 | 81 | ## Serialize a response value to a string. 82 | ## 83 | ## @param data $value The value to serialize 84 | ## 85 | ## @return The deserialized value 86 | 87 | sub serializeRequest { 88 | my $self = shift; 89 | my $value = shift; 90 | 91 | return undef if (!defined($value)); 92 | return $value if (!$self->{_serialize}); 93 | return $self->{_serialize}($value); 94 | } 95 | 96 | ## Deserialize a response value to an object. 97 | ## 98 | ## @param string $value The binary value to deserialize 99 | ## 100 | ## @return The deserialized value 101 | 102 | sub deserializeResponse { 103 | my $self = shift; 104 | my $value = shift; 105 | 106 | return undef if (!defined($value)); 107 | return $value if (!$self->{_deserialize}); 108 | return $self->{_deserialize}($value); 109 | } 110 | 111 | ## Set the CallCredentials for the underlying Call. 112 | ## 113 | ## @param CallCredentials $call_credentials The CallCredentials 114 | ## object 115 | 116 | sub setCallCredentials { 117 | my $self = shift; 118 | my $call_credentials = shift; 119 | $self->{_call}->setCredentials($call_credentials); 120 | } 121 | 122 | 1; 123 | -------------------------------------------------------------------------------- /lib/Grpc/Client/BaseStub.pm: -------------------------------------------------------------------------------- 1 | package Grpc::Client::BaseStub; 2 | use strict; 3 | use warnings; 4 | 5 | ## Base class for generated client stubs. Stub methods are expected to call 6 | ## _simpleRequest or _streamRequest and return the result. 7 | 8 | use Grpc::XS; 9 | use Grpc::XS::Channel; 10 | use Grpc::XS::Timeval; 11 | 12 | use Grpc::Constants; 13 | 14 | use Grpc::Client::UnaryCall; 15 | use Grpc::Client::ClientStreamingCall; 16 | use Grpc::Client::ServerStreamingCall; 17 | use Grpc::Client::BidiStreamingCall; 18 | 19 | use constant true => 1; 20 | use constant false => 0; 21 | 22 | ## params: 23 | ## - 'hostname': string 24 | ## - 'update_metadata': (optional) a callback function which takes in a 25 | ## metadata array, and returns an updated metadata array 26 | ## - 'grpc.primary_user_agent': (optional) a user-agent string 27 | 28 | sub new { 29 | my $proto = shift; 30 | my $hostname = shift; 31 | my %param = @_; 32 | my $update_metadata = $param{"update_metadata"}; 33 | my $primary_user_agent = $param{"grpc.primary_user_agent"}; 34 | my $credentials = $param{"credentials"}; 35 | my $timeout = $param{"timeout"}; 36 | 37 | unless (defined($primary_user_agent)) { 38 | $primary_user_agent = "grpc-perl/".($Grpc::XS::VERSION); 39 | } 40 | 41 | if (!exists($param{"credentials"})) { 42 | die("The 'credentials' key is now required. Please see one of the ". 43 | "Grpc::XS::ChannelCredentials::create methods"); 44 | } elsif (!defined($param{"credentials"})) { 45 | delete $param{"credentials"}; 46 | } 47 | 48 | my $channel = new Grpc::XS::Channel($hostname,%param); 49 | 50 | my $self = { 51 | '_hostname' => $hostname, 52 | '_channel' => undef, 53 | '_update_metadata' => $update_metadata, ## a callback function 54 | '_primary_user_agent' => $primary_user_agent, 55 | '_credentials' => $credentials, 56 | '_channel' => $channel, 57 | '_timeout' => $timeout, 58 | }; 59 | bless $self,$proto; 60 | 61 | return $self; 62 | } 63 | 64 | ## return string The URI of the endpoint. 65 | 66 | sub getTarget { 67 | my $self = shift; 68 | return $self->{_channel}->getTarget(); 69 | } 70 | 71 | ## $try_to_connect bool 72 | ## return int The grpc connectivity state 73 | 74 | sub getConnectivityState { 75 | my $self = shift; 76 | my $try_to_connect = shift||false; 77 | return $self->{_channel}->getConnectivityState($try_to_connect); 78 | } 79 | 80 | ## $timeout in microseconds 81 | ## return bool true if channel is ready 82 | ## dies if channel is in FATAL_ERROR state 83 | 84 | sub waitForReady { 85 | my $self = shift; 86 | my $timeout = shift; 87 | 88 | my $new_state = $self->getConnectivityState(true); 89 | if ($self->_checkConnectivityState($new_state)) { 90 | return true; 91 | } 92 | 93 | my $now = Grpc::XS::Timeval::now(); 94 | my $delta = new Grpc::XS::Timeval($timeout); 95 | my $deadline = Grpc::XS::Timeval::add($now,$delta); 96 | 97 | while ($self->{_channel}->watchConnectivityState($new_state, $deadline)) { 98 | ## state has changed before deadline 99 | $new_state = $self->getConnectivityState(); 100 | if ($self->_checkConnectivityState($new_state)) { 101 | return true; 102 | } 103 | } 104 | 105 | ## deadline has passed 106 | $new_state = $self->getConnectivityState(); 107 | 108 | return $self->_checkConnectivityState($new_state); 109 | } 110 | 111 | sub _checkConnectivityState { 112 | my $self = shift; 113 | my $new_state = shift; 114 | 115 | if ($new_state == Grpc::Constants::GRPC_CHANNEL_READY()){ 116 | return true; 117 | } 118 | if ($new_state == Grpc::Constants::GRPC_CHANNEL_SHUTDOWN()){ 119 | die('Failed to connect to server'); 120 | } 121 | 122 | return false; 123 | } 124 | 125 | ## Close the communication channel associated with this stub. 126 | 127 | sub close { 128 | my $self = shift; 129 | $self->{_channel}->close(); 130 | } 131 | 132 | ## constructs the auth uri for the jwt. 133 | 134 | sub _get_jwt_aud_uri { 135 | my $self = shift; 136 | my $method = shift; 137 | 138 | my $service_name; 139 | if ($method =~ m|^(.*)/[^/]+$|) { 140 | $service_name = $1; 141 | } else { 142 | die("InvalidArgumentException: service name must have a slash"); 143 | } 144 | 145 | return 'https://'.$self->{_hostname}.$service_name; 146 | } 147 | 148 | sub _validate_and_normalize_metadata { 149 | my $self = shift; 150 | my $metadata = shift || {}; 151 | 152 | my $metadata_copy = {}; 153 | foreach my $key (keys %{$metadata}) { 154 | if ($key !~ /^[A-Za-z\d_-]+$/) { 155 | die("InvalidArgumentException: ". 156 | "Metadata keys must be nonempty strings containing only ". 157 | "alphanumeric characters, hyphens and underscores"); 158 | } 159 | $metadata_copy->{lc($key)} = $metadata->{$key}; 160 | } 161 | 162 | return $metadata_copy; 163 | } 164 | 165 | ## This class is intended to be subclassed by generated code, so 166 | ## all functions begin with "_" to avoid name collisions. */ 167 | 168 | ## Call a remote method that takes a single argument and has a 169 | ## single output. 170 | ## 171 | ## @param string $method The name of the method to call 172 | ## @param $argument The argument to the method 173 | ## @param callable $serialize A function that serializes the request 174 | ## @param callable $deserialize A function that deserializes the response 175 | ## @param array $metadata A metadata map to send to the server 176 | ## 177 | ## @return SimpleSurfaceActiveCall The active call object 178 | 179 | sub _simpleRequest { 180 | my $self = shift; 181 | my %param = @_; 182 | my $method = $param{method}; 183 | my $argument = $param{argument}; 184 | my $serialize = $param{serialize}; 185 | my $deserialize = $param{deserialize}; 186 | my $metadata = $param{metadata} || {}; 187 | my $options = $param{options} || {}; 188 | $options->{timeout} = $options->{timeout} || $self->{_timeout}; 189 | 190 | my $call = new Grpc::Client::UnaryCall( 191 | $self->{_channel}, 192 | $method, 193 | $serialize, 194 | $deserialize, 195 | $options ); 196 | my $jwt_aud_uri = $self->_get_jwt_aud_uri($method); 197 | 198 | if (defined($self->{_update_metadata})) { 199 | $metadata = $self->{_update_metadata}($metadata,$jwt_aud_uri); ## TODO: PORT 200 | } 201 | $metadata = $self->_validate_and_normalize_metadata($metadata); 202 | $call->start($argument, $metadata, $options); 203 | 204 | return $call; 205 | } 206 | 207 | ## Call a remote method that takes a stream of arguments and has a single 208 | ## output. 209 | ## 210 | ## @param string $method The name of the method to call 211 | ## @param $arguments An array or Traversable of arguments to stream to the 212 | ## server 213 | ## @param callable $serialize A function that serializes the request 214 | ## @param callable $deserialize A function that deserializes the response 215 | ## @param array $metadata A metadata map to send to the server 216 | ## 217 | ## @return ClientStreamingSurfaceActiveCall The active call object 218 | 219 | sub _clientStreamRequest { 220 | my $self = shift; 221 | my %param = @_; 222 | my $method = $param{method}; 223 | my $serialize = $param{serialize}; 224 | my $deserialize = $param{deserialize}; 225 | my $metadata = $param{metadata} || {}; 226 | my $options = $param{options} || {}; 227 | $options->{timeout} = $options->{timeout} || $self->{_timeout}; 228 | 229 | my $call = new Grpc::Client::ClientStreamingCall( 230 | $self->{_channel}, 231 | $method, 232 | $serialize, 233 | $deserialize, 234 | $options ); 235 | my $jwt_aud_uri = $self->_get_jwt_aud_uri($method); 236 | 237 | if (defined($self->{_update_metadata})) { 238 | $metadata = $self->{_update_metadata}($metadata,$jwt_aud_uri); ## TODO: PORT 239 | } 240 | $metadata = $self->_validate_and_normalize_metadata($metadata); 241 | $call->start($metadata, $options); 242 | 243 | return $call; 244 | } 245 | 246 | ## Call a remote method that takes a single argument and returns a stream of 247 | ## responses. 248 | ## 249 | ## @param string $method The name of the method to call 250 | ## @param $argument The argument to the method 251 | ## @param callable $serialize A function that serializes the request 252 | ## @param callable $deserialize A function that deserializes the responses 253 | ## @param array $metadata A metadata map to send to the server 254 | ## 255 | ## @return ServerStreamingSurfaceActiveCall The active call object 256 | 257 | sub _serverStreamRequest { 258 | my $self = shift; 259 | my %param = @_; 260 | my $method = $param{method}; 261 | my $argument = $param{argument}; 262 | my $serialize = $param{serialize}; 263 | my $deserialize = $param{deserialize}; 264 | my $metadata = $param{metadata} || {}; 265 | my $options = $param{options} || {}; 266 | $options->{timeout} = $options->{timeout} || $self->{_timeout}; 267 | 268 | my $call = new Grpc::Client::ServerStreamingCall( 269 | $self->{_channel}, 270 | $method, 271 | $serialize, 272 | $deserialize, 273 | $options ); 274 | my $jwt_aud_uri = $self->_get_jwt_aud_uri($method); 275 | 276 | if (defined($self->{_update_metadata})) { 277 | $metadata = $self->{_update_metadata}($metadata,$jwt_aud_uri); ## TODO: PORT 278 | } 279 | $metadata = $self->_validate_and_normalize_metadata($metadata); 280 | $call->start($argument, $metadata, $options); 281 | 282 | return $call; 283 | } 284 | 285 | ## Call a remote method with messages streaming in both directions. 286 | ## 287 | ## @param string $method The name of the method to call 288 | ## @param callable $serialize A function that serializes the request 289 | ## @param callable $deserialize A function that deserializes the responses 290 | ## @param array $metadata A metadata map to send to the server 291 | ## 292 | ## @return BidiStreamingSurfaceActiveCall The active call object 293 | 294 | sub _bidiRequest { 295 | my $self = shift; 296 | my %param = @_; 297 | my $method = $param{method}; 298 | my $serialize = $param{serialize}; 299 | my $deserialize = $param{deserialize}; 300 | my $metadata = $param{metadata} || {}; 301 | my $options = $param{options} || {}; 302 | $options->{timeout} = $options->{timeout} || $self->{_timeout}; 303 | 304 | my $call = new Grpc::Client::BidiStreamingCall( 305 | $self->{_channel}, 306 | $method, 307 | $serialize, 308 | $deserialize, 309 | $options ); 310 | my $jwt_aud_uri = $self->_get_jwt_aud_uri($method); 311 | 312 | if (defined($self->{_update_metadata})) { 313 | $metadata = $self->{_update_metadata}($metadata,$jwt_aud_uri); ## TODO: PORT 314 | } 315 | $metadata = $self->_validate_and_normalize_metadata($metadata); 316 | $call->start($metadata, $options); 317 | 318 | return $call; 319 | } 320 | 321 | 1; 322 | -------------------------------------------------------------------------------- /lib/Grpc/Client/BidiStreamingCall.pm: -------------------------------------------------------------------------------- 1 | package Grpc::Client::BidiStreamingCall; 2 | use strict; 3 | use warnings; 4 | 5 | use base qw(Grpc::Client::AbstractCall); 6 | 7 | use Grpc::Constants; 8 | 9 | use constant true => 1; 10 | use constant false => 0; 11 | 12 | ## @param array $metadata Metadata to send with the call, if applicable 13 | 14 | sub start { 15 | my $self = shift; 16 | my $metadata = shift || {}; 17 | 18 | $self->{_call}->startBatch( 19 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => $metadata, 20 | ); 21 | } 22 | 23 | ## Reads the next value from the server. 24 | ## 25 | ## @return The next value from the server, or null if there is none 26 | 27 | sub read { 28 | my $self = shift; 29 | 30 | my %batch = ( Grpc::Constants::GRPC_OP_RECV_MESSAGE() => true ); 31 | if (!defined($self->{_metadata})) { 32 | $batch{Grpc::Constants::GRPC_OP_RECV_INITIAL_METADATA()} = true; 33 | } 34 | 35 | my $read_event = $self->{_call}->startBatch(%batch); 36 | if (!defined($self->{_metadata})) { 37 | $self->{_metadata} = $read_event->{metadata}; 38 | } 39 | 40 | return $self->deserializeResponse($read_event->{message}); 41 | } 42 | 43 | ## Write a single message to the server. This cannot be called after 44 | ## writesDone is called. 45 | ## 46 | ## @param ByteBuffer $data The data to write 47 | ## @param array $options an array of options, possible keys: 48 | ## 'flags' => a number 49 | 50 | sub write { 51 | my $self = shift; 52 | my $data = shift; 53 | my $options = shift||{}; 54 | 55 | my $message = { 'message' => $self->serializeRequest($data) }; 56 | if (defined($options->{'flags'})) { 57 | $message->{'flags'} = $options->{'flags'}; 58 | } 59 | $self->{_call}->startBatch( 60 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => $message, 61 | ); 62 | } 63 | 64 | ## Indicate that no more writes will be sent. 65 | 66 | sub writesDone { 67 | my $self = shift; 68 | $self->{_call}->startBatch( 69 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => true, 70 | ); 71 | } 72 | 73 | ## Wait for the server to send the status, and return it. 74 | ## 75 | ## @return object The status object, with integer $code, string $details, 76 | ## and array $metadata members 77 | 78 | sub getStatus { 79 | my $self = shift; 80 | my $status_event = $self->{_call}->startBatch( 81 | Grpc::Constants::GRPC_OP_RECV_STATUS_ON_CLIENT() => true, 82 | ); 83 | 84 | return $status_event->{status}; 85 | } 86 | 87 | 1; 88 | -------------------------------------------------------------------------------- /lib/Grpc/Client/ClientStreamingCall.pm: -------------------------------------------------------------------------------- 1 | package Grpc::Client::ClientStreamingCall; 2 | use strict; 3 | use warnings; 4 | 5 | use base qw(Grpc::Client::AbstractCall); 6 | 7 | use Grpc::Constants; 8 | 9 | use constant true => 1; 10 | use constant false => 0; 11 | 12 | ## @param array $metadata Metadata to send with the call, if applicable 13 | 14 | sub start { 15 | my $self = shift; 16 | my $metadata = shift || {}; 17 | 18 | $self->{_call}->startBatch( 19 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => $metadata, 20 | ); 21 | } 22 | 23 | ## Write a single message to the server. This cannot be called after 24 | ## writesDone is called. 25 | ## 26 | ## @param ByteBuffer $data The data to write 27 | ## @param array $options an array of options, possible keys: 28 | ## 'flags' => a number 29 | 30 | sub write { 31 | my $self = shift; 32 | my $data = shift; 33 | my $options = shift||{}; 34 | 35 | my $message = { 'message' => $self->serializeRequest($data) }; 36 | if (defined($options->{'flags'})) { 37 | $message->{'flags'} = $options->{'flags'}; 38 | } 39 | $self->{_call}->startBatch( 40 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => $message, 41 | ); 42 | } 43 | 44 | ## Wait for the server to respond with data and a status. 45 | ## 46 | ## @return (response data, status) 47 | 48 | sub wait { 49 | my $self = shift; 50 | 51 | my $event = $self->{_call}->startBatch( 52 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => true, 53 | Grpc::Constants::GRPC_OP_RECV_INITIAL_METADATA() => true, 54 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => true, 55 | Grpc::Constants::GRPC_OP_RECV_STATUS_ON_CLIENT() => true, 56 | ); 57 | 58 | if (!defined($self->{_metadata})) { 59 | $self->{_metadata} = $event->{metadata}; 60 | } 61 | 62 | return wantarray 63 | ? ($self->deserializeResponse($event->{message}), $event->{status}) 64 | : $self->deserializeResponse($event->{message}); 65 | } 66 | 67 | 1; 68 | -------------------------------------------------------------------------------- /lib/Grpc/Client/ServerStreamingCall.pm: -------------------------------------------------------------------------------- 1 | package Grpc::Client::ServerStreamingCall; 2 | use strict; 3 | use warnings; 4 | 5 | use base qw(Grpc::Client::AbstractCall); 6 | 7 | use Grpc::Constants; 8 | 9 | use constant true => 1; 10 | use constant false => 0; 11 | 12 | ## Start the call. 13 | ## 14 | ## @param $data The data to send 15 | ## @param array $metadata Metadata to send with the call, if applicable 16 | ## @param array $options an array of options, possible keys: 17 | ## 'flags' => a number 18 | 19 | sub start { 20 | my $self = shift; 21 | my $data = shift; 22 | my $metadata= shift || {}; 23 | my $options = shift; 24 | 25 | my $message = { 'message' => $self->serializeRequest($data) }; 26 | if (defined($options->{'flags'})) { 27 | $message->{'flags'} = $options->{'flags'}; 28 | } 29 | my $event = $self->{_call}->startBatch( 30 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => $metadata, 31 | Grpc::Constants::GRPC_OP_RECV_INITIAL_METADATA() => true, 32 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => $message, 33 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => true, 34 | ); 35 | 36 | $self->{_metadata} = $event->{metadata}; 37 | } 38 | 39 | ## Reads the next value from the server. 40 | ## 41 | ## @return The next value from the server, or undef if there is none 42 | 43 | sub read { 44 | my $self = shift; 45 | 46 | my $response = $self->{_call}->startBatch( 47 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => true, 48 | )->{message}; 49 | 50 | return $self->deserializeResponse($response); 51 | } 52 | 53 | ## @return An array of response values 54 | 55 | sub responses { 56 | my $self = shift; 57 | 58 | my @responses; 59 | my $response = $self->{_call}->startBatch( 60 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => true, 61 | )->{message}; 62 | while (defined($response)) { 63 | push @responses,$self->deserializeResponse($response); 64 | $response = $self->{_call}->startBatch( 65 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => true, 66 | )->{message}; 67 | } 68 | return @responses; 69 | } 70 | 71 | ## Wait for the server to send the status, and return it. 72 | ## 73 | ## @return object The status object, with integer $code, string $details, 74 | ## and array $metadata members 75 | 76 | sub getStatus { 77 | my $self = shift; 78 | my $status_event = $self->{_call}->startBatch( 79 | Grpc::Constants::GRPC_OP_RECV_STATUS_ON_CLIENT() => true, 80 | ); 81 | 82 | return $status_event->{status}; 83 | } 84 | 1; 85 | -------------------------------------------------------------------------------- /lib/Grpc/Client/UnaryCall.pm: -------------------------------------------------------------------------------- 1 | package Grpc::Client::UnaryCall; 2 | use strict; 3 | use warnings; 4 | 5 | use base qw(Grpc::Client::AbstractCall); 6 | 7 | use Grpc::Constants; 8 | 9 | use constant true => 1; 10 | use constant false => 0; 11 | 12 | ## Start the call. 13 | ## 14 | ## @param $data The data to send 15 | ## @param array $metadata Metadata to send with the call, if applicable 16 | ## @param array $options an array of options, possible keys: 17 | ## 'flags' => a number 18 | 19 | sub start { 20 | my $self = shift; 21 | my $data = shift; 22 | my $metadata= shift || {}; 23 | my $options = shift; 24 | 25 | my $message = { 'message' => $self->serializeRequest($data) }; 26 | if (defined($options->{'flags'})) { 27 | $message->{'flags'} = $options->{'flags'}; 28 | } 29 | 30 | my $event = $self->{_call}->startBatch( 31 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => $metadata, 32 | Grpc::Constants::GRPC_OP_RECV_INITIAL_METADATA() => true, 33 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => $message, 34 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => true, 35 | ); 36 | 37 | $self->{_metadata} = $event->{metadata}; 38 | } 39 | 40 | ## Wait for the server to respond with data and a status. 41 | ## 42 | ## @return (response data, status) 43 | 44 | sub wait { 45 | my $self = shift; 46 | 47 | my $event = $self->{_call}->startBatch( 48 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => true, 49 | Grpc::Constants::GRPC_OP_RECV_STATUS_ON_CLIENT() => true, 50 | ); 51 | 52 | return wantarray 53 | ? ($self->deserializeResponse($event->{message}), $event->{status}) 54 | : $self->deserializeResponse($event->{message}); 55 | } 56 | 57 | 1; 58 | -------------------------------------------------------------------------------- /lib/Grpc/Constants.pm: -------------------------------------------------------------------------------- 1 | package Grpc::Constants; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Grpc::XS::Constants; 7 | 8 | use base qw(Exporter); 9 | 10 | our @EXPORT_OK = qw( 11 | GRPC_CALL_OK 12 | GRPC_CALL_ERROR 13 | GRPC_CALL_ERROR_NOT_ON_SERVER 14 | GRPC_CALL_ERROR_NOT_ON_CLIENT 15 | GRPC_CALL_ERROR_ALREADY_INVOKED 16 | GRPC_CALL_ERROR_NOT_INVOKED 17 | GRPC_CALL_ERROR_ALREADY_FINISHED 18 | GRPC_CALL_ERROR_TOO_MANY_OPERATIONS 19 | GRPC_CALL_ERROR_INVALID_FLAGS 20 | GRPC_WRITE_BUFFER_HINT 21 | GRPC_WRITE_NO_COMPRESS 22 | GRPC_STATUS_OK 23 | GRPC_STATUS_CANCELLED 24 | GRPC_STATUS_UNKNOWN 25 | GRPC_STATUS_INVALID_ARGUMENT 26 | GRPC_STATUS_DEADLINE_EXCEEDED 27 | GRPC_STATUS_NOT_FOUND 28 | GRPC_STATUS_ALREADY_EXISTS 29 | GRPC_STATUS_PERMISSION_DENIED 30 | GRPC_STATUS_UNAUTHENTICATED 31 | GRPC_STATUS_RESOURCE_EXHAUSTED 32 | GRPC_STATUS_FAILED_PRECONDITION 33 | GRPC_STATUS_ABORTED 34 | GRPC_STATUS_OUT_OF_RANGE 35 | GRPC_STATUS_UNIMPLEMENTED 36 | GRPC_STATUS_INTERNAL 37 | GRPC_STATUS_UNAVAILABLE 38 | GRPC_STATUS_DATA_LOSS 39 | GRPC_OP_SEND_INITIAL_METADATA 40 | GRPC_OP_SEND_MESSAGE 41 | GRPC_OP_SEND_CLOSE_FROM_CLIENT 42 | GRPC_OP_SEND_STATUS_FROM_SERVER 43 | GRPC_OP_RECV_INITIAL_METADATA 44 | GRPC_OP_RECV_MESSAGE 45 | GRPC_OP_RECV_STATUS_ON_CLIENT 46 | GRPC_OP_RECV_CLOSE_ON_SERVER 47 | GRPC_CHANNEL_IDLE 48 | GRPC_CHANNEL_CONNECTING 49 | GRPC_CHANNEL_READY 50 | GRPC_CHANNEL_TRANSIENT_FAILURE 51 | GRPC_CHANNEL_SHUTDOWN 52 | GPR_CLOCK_MONOTONIC 53 | GPR_CLOCK_REALTIME 54 | GPR_CLOCK_PRECISE 55 | GPR_TIMESPAN 56 | ); 57 | 58 | sub GRPC_CALL_OK { return Grpc::XS::Constants::CALL_OK; } 59 | sub GRPC_CALL_ERROR { return Grpc::XS::Constants::CALL_ERROR; } 60 | sub GRPC_CALL_ERROR_NOT_ON_SERVER { return Grpc::XS::Constants::CALL_ERROR_NOT_ON_SERVER; } 61 | sub GRPC_CALL_ERROR_NOT_ON_CLIENT { return Grpc::XS::Constants::CALL_ERROR_NOT_ON_CLIENT; } 62 | sub GRPC_CALL_ERROR_ALREADY_INVOKED { return Grpc::XS::Constants::CALL_ERROR_ALREADY_INVOKED; } 63 | sub GRPC_CALL_ERROR_NOT_INVOKED { return Grpc::XS::Constants::CALL_ERROR_NOT_INVOKED; } 64 | sub GRPC_CALL_ERROR_ALREADY_FINISHED { return Grpc::XS::Constants::CALL_ERROR_ALREADY_FINISHED; } 65 | sub GRPC_CALL_ERROR_TOO_MANY_OPERATIONS { return Grpc::XS::Constants::CALL_ERROR_TOO_MANY_OPERATIONS; } 66 | sub GRPC_CALL_ERROR_INVALID_FLAGS { return Grpc::XS::Constants::CALL_ERROR_INVALID_FLAGS; } 67 | sub GRPC_WRITE_BUFFER_HINT { return Grpc::XS::Constants::WRITE_BUFFER_HINT; } 68 | sub GRPC_WRITE_NO_COMPRESS { return Grpc::XS::Constants::WRITE_NO_COMPRESS; } 69 | sub GRPC_STATUS_OK { return Grpc::XS::Constants::STATUS_OK; } 70 | sub GRPC_STATUS_CANCELLED { return Grpc::XS::Constants::STATUS_CANCELLED; } 71 | sub GRPC_STATUS_UNKNOWN { return Grpc::XS::Constants::STATUS_UNKNOWN; } 72 | sub GRPC_STATUS_INVALID_ARGUMENT { return Grpc::XS::Constants::STATUS_INVALID_ARGUMENT; } 73 | sub GRPC_STATUS_DEADLINE_EXCEEDED { return Grpc::XS::Constants::STATUS_DEADLINE_EXCEEDED; } 74 | sub GRPC_STATUS_NOT_FOUND { return Grpc::XS::Constants::STATUS_NOT_FOUND; } 75 | sub GRPC_STATUS_ALREADY_EXISTS { return Grpc::XS::Constants::STATUS_ALREADY_EXISTS; } 76 | sub GRPC_STATUS_PERMISSION_DENIED { return Grpc::XS::Constants::STATUS_PERMISSION_DENIED; } 77 | sub GRPC_STATUS_UNAUTHENTICATED { return Grpc::XS::Constants::STATUS_UNAUTHENTICATED; } 78 | sub GRPC_STATUS_RESOURCE_EXHAUSTED { return Grpc::XS::Constants::STATUS_RESOURCE_EXHAUSTED; } 79 | sub GRPC_STATUS_FAILED_PRECONDITION { return Grpc::XS::Constants::STATUS_FAILED_PRECONDITION; } 80 | sub GRPC_STATUS_ABORTED { return Grpc::XS::Constants::STATUS_ABORTED; } 81 | sub GRPC_STATUS_OUT_OF_RANGE { return Grpc::XS::Constants::STATUS_OUT_OF_RANGE; } 82 | sub GRPC_STATUS_UNIMPLEMENTED { return Grpc::XS::Constants::STATUS_UNIMPLEMENTED; } 83 | sub GRPC_STATUS_INTERNAL { return Grpc::XS::Constants::STATUS_INTERNAL; } 84 | sub GRPC_STATUS_UNAVAILABLE { return Grpc::XS::Constants::STATUS_UNAVAILABLE; } 85 | sub GRPC_STATUS_DATA_LOSS { return Grpc::XS::Constants::STATUS_DATA_LOSS; } 86 | sub GRPC_OP_SEND_INITIAL_METADATA { return Grpc::XS::Constants::OP_SEND_INITIAL_METADATA; } 87 | sub GRPC_OP_SEND_MESSAGE { return Grpc::XS::Constants::OP_SEND_MESSAGE; } 88 | sub GRPC_OP_SEND_CLOSE_FROM_CLIENT { return Grpc::XS::Constants::OP_SEND_CLOSE_FROM_CLIENT; } 89 | sub GRPC_OP_SEND_STATUS_FROM_SERVER { return Grpc::XS::Constants::OP_SEND_STATUS_FROM_SERVER; } 90 | sub GRPC_OP_RECV_INITIAL_METADATA { return Grpc::XS::Constants::OP_RECV_INITIAL_METADATA; } 91 | sub GRPC_OP_RECV_MESSAGE { return Grpc::XS::Constants::OP_RECV_MESSAGE; } 92 | sub GRPC_OP_RECV_STATUS_ON_CLIENT { return Grpc::XS::Constants::OP_RECV_STATUS_ON_CLIENT; } 93 | sub GRPC_OP_RECV_CLOSE_ON_SERVER { return Grpc::XS::Constants::OP_RECV_CLOSE_ON_SERVER; } 94 | sub GRPC_CHANNEL_IDLE { return Grpc::XS::Constants::CHANNEL_IDLE; } 95 | sub GRPC_CHANNEL_CONNECTING { return Grpc::XS::Constants::CHANNEL_CONNECTING; } 96 | sub GRPC_CHANNEL_READY { return Grpc::XS::Constants::CHANNEL_READY; } 97 | sub GRPC_CHANNEL_TRANSIENT_FAILURE { return Grpc::XS::Constants::CHANNEL_TRANSIENT_FAILURE; } 98 | sub GRPC_CHANNEL_SHUTDOWN { return Grpc::XS::Constants::CHANNEL_FATAL_FAILURE; } 99 | sub GPR_CLOCK_MONOTONIC { return Grpc::XS::Constants::XS_GPR_CLOCK_MONOTONIC; } 100 | sub GPR_CLOCK_REALTIME { return Grpc::XS::Constants::XS_GPR_CLOCK_REALTIME; } 101 | sub GPR_CLOCK_PRECISE { return Grpc::XS::Constants::XS_GPR_CLOCK_PRECISE; } 102 | sub GPR_TIMESPAN { return Grpc::XS::Constants::XS_GPR_TIMESPAN; } 103 | 104 | 1; 105 | -------------------------------------------------------------------------------- /lib/Grpc/XS.pm: -------------------------------------------------------------------------------- 1 | package Grpc::XS; 2 | use strict; 3 | use warnings; 4 | use XSLoader; 5 | 6 | =head1 NAME 7 | 8 | Grpc::XS - binding to the grpc library. 9 | 10 | =cut 11 | 12 | =head1 DESCRIPTION 13 | 14 | This is the low-level binding to the L library. 15 | This implementation only supports a grpc client. 16 | 17 | This library is not intended to be used directly, but rather 18 | in combination with a protocol buffer implementation like the 19 | L module. 20 | 21 | =cut 22 | 23 | =head1 AUTHOR 24 | 25 | Vincent van Dam 26 | 27 | =cut 28 | 29 | =head1 COPYRIGHT AND LICENSE 30 | 31 | This software is copyright (c) 2017-2023 by Vincent van Dam. 32 | grpc-perl is licensed under the Apache License 2.0. 33 | 34 | =cut 35 | 36 | =head1 SEE ALSO 37 | 38 | L 39 | 40 | =cut 41 | 42 | our $VERSION = '0.38'; 43 | 44 | XSLoader::load(__PACKAGE__, $VERSION ); 45 | 46 | END { destroy(); } 47 | 48 | 1; 49 | -------------------------------------------------------------------------------- /lib/Grpc/XS/Call.pm: -------------------------------------------------------------------------------- 1 | package Grpc::XS::Call; 2 | use strict; 3 | use warnings; 4 | use Grpc::XS; 5 | 6 | 1; 7 | -------------------------------------------------------------------------------- /lib/Grpc/XS/CallCredentials.pm: -------------------------------------------------------------------------------- 1 | package Grpc::XS::CallCredentials; 2 | use strict; 3 | use warnings; 4 | use Grpc::XS; 5 | 6 | 1; 7 | -------------------------------------------------------------------------------- /lib/Grpc/XS/Channel.pm: -------------------------------------------------------------------------------- 1 | package Grpc::XS::Channel; 2 | use strict; 3 | use warnings; 4 | use Grpc::XS; 5 | 6 | 1; 7 | -------------------------------------------------------------------------------- /lib/Grpc/XS/ChannelCredentials.pm: -------------------------------------------------------------------------------- 1 | package Grpc::XS::ChannelCredentials; 2 | use strict; 3 | use warnings; 4 | use Grpc::XS; 5 | 6 | 1; 7 | -------------------------------------------------------------------------------- /lib/Grpc/XS/Constants.pm: -------------------------------------------------------------------------------- 1 | package Grpc::XS::Constants; 2 | use strict; 3 | use warnings; 4 | use Grpc::XS; 5 | 6 | 1; 7 | -------------------------------------------------------------------------------- /lib/Grpc/XS/Server.pm: -------------------------------------------------------------------------------- 1 | package Grpc::XS::Server; 2 | use strict; 3 | use warnings; 4 | use Grpc::XS; 5 | 6 | 1; 7 | -------------------------------------------------------------------------------- /lib/Grpc/XS/ServerCredentials.pm: -------------------------------------------------------------------------------- 1 | package Grpc::XS::ServerCredentials; 2 | use strict; 3 | use warnings; 4 | use Grpc::XS; 5 | 6 | 1; 7 | -------------------------------------------------------------------------------- /lib/Grpc/XS/Timeval.pm: -------------------------------------------------------------------------------- 1 | package Grpc::XS::Timeval; 2 | use strict; 3 | use warnings; 4 | use Grpc::XS; 5 | 6 | 1; 7 | -------------------------------------------------------------------------------- /t/00-compile.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | 6 | plan tests => 16; 7 | 8 | use_ok('Grpc::Client::AbstractCall'); 9 | use_ok('Grpc::Client::BaseStub'); 10 | use_ok('Grpc::Client::BidiStreamingCall'); 11 | use_ok('Grpc::Client::ClientStreamingCall'); 12 | use_ok('Grpc::Client::ServerStreamingCall'); 13 | use_ok('Grpc::Client::UnaryCall'); 14 | use_ok('Grpc::Constants'); 15 | use_ok('Grpc::XS'); 16 | use_ok('Grpc::XS::Call'); 17 | use_ok('Grpc::XS::CallCredentials'); 18 | use_ok('Grpc::XS::Channel'); 19 | use_ok('Grpc::XS::ChannelCredentials'); 20 | use_ok('Grpc::XS::Constants'); 21 | use_ok('Grpc::XS::Server'); 22 | use_ok('Grpc::XS::ServerCredentials'); 23 | use_ok('Grpc::XS::Timeval'); 24 | -------------------------------------------------------------------------------- /t/01-call_credentials.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | use Devel::Peek; 6 | 7 | use File::Basename; 8 | use File::Spec; 9 | my $path = File::Basename::dirname( File::Spec->rel2abs(__FILE__) ); 10 | 11 | plan tests => 28; 12 | 13 | delete @ENV{grep /https?_proxy/i, keys %ENV}; 14 | 15 | use_ok("Grpc::XS::CallCredentials"); 16 | 17 | ## ---------------------------------------------------------------------------- 18 | 19 | use_ok("Grpc::XS::Server"); 20 | use_ok("Grpc::XS::ChannelCredentials"); 21 | use_ok("Grpc::XS::ServerCredentials"); 22 | use_ok("Grpc::Constants"); 23 | 24 | sub file_get_contents { 25 | my $file = shift; 26 | open(F,"<".$file); 27 | my $content = join("",); 28 | close(F); 29 | return $content; 30 | } 31 | 32 | ##################################################### 33 | ## setup 34 | ##################################################### 35 | 36 | my $credentials = Grpc::XS::ChannelCredentials::createSsl( 37 | pem_root_certs => file_get_contents($path.'/data/ca.pem')); 38 | my $call_credentials = Grpc::XS::CallCredentials::createFromPlugin( 39 | \&callbackFunc); 40 | $credentials = Grpc::XS::ChannelCredentials::createComposite( 41 | $credentials, 42 | $call_credentials 43 | ); 44 | my $server_credentials = Grpc::XS::ServerCredentials::createSsl( 45 | pem_root_certs => undef, 46 | pem_private_key => file_get_contents($path.'/data/server1.key'), 47 | pem_cert_chain => file_get_contents($path.'/data/server1.pem')); 48 | 49 | my $server = new Grpc::XS::Server(); 50 | my $port = $server->addSecureHttp2Port('0.0.0.0:0',$server_credentials); 51 | $server->start(); 52 | 53 | my $host_override = 'foo.test.google.fr'; 54 | my $channel = new Grpc::XS::Channel( 55 | 'localhost:'.$port, 56 | 'grpc.ssl_target_name_override' => $host_override, 57 | 'grpc.default_authority' => $host_override, 58 | 'credentials' => $credentials, 59 | ); 60 | 61 | ##################################################### 62 | ## callback 63 | ##################################################### 64 | 65 | sub callbackFunc { 66 | my $context = shift; 67 | ok($context->{service_url} =~ /google/,"call back func failed service_url"); 68 | ok($context->{method_name} =~ /dummy/,"call back func failed method_name"); 69 | return { 'k1' => [ 'v1' ], 'k2' => [ 'v2','v3' ] }; 70 | } 71 | 72 | ##################################################### 73 | ## testCreateFromPlugin() 74 | ##################################################### 75 | 76 | my $deadline = Grpc::XS::Timeval::infFuture(); 77 | my $call = new Grpc::XS::Call($channel, 78 | '/abc/dummy_method', 79 | $deadline, 80 | $host_override); 81 | # Dump($call); 82 | 83 | my $event = $call->startBatch( 84 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 85 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 86 | ); 87 | ok($event->{send_metadata},"startBatch failed return send_metadata"); 88 | ok($event->{send_close},"startBatch failed return send_close"); 89 | 90 | ##################################################### 91 | 92 | $event = $server->requestCall(); 93 | my $metadata = $event->{metadata}; 94 | ok(ref($event->{metadata})=~/HASH/,"event->metadata is not a hash"); 95 | ok($event->{metadata}->{k1},"event->metadata->k1 does not exist"); 96 | ok($event->{metadata}->{k2},"event->metadata->k2 does not exist"); 97 | ok(ref($event->{metadata}->{k1})=~/ARRAY/,"event->metadata->k1 is not an array"); 98 | ok(ref($event->{metadata}->{k2})=~/ARRAY/,"event->metadata->k2 is not an array"); 99 | ok($event->{metadata}->{k1}->[0] eq 'v1',"event->metadata->k1 has wrong value"); 100 | ok($event->{metadata}->{k2}->[0] eq 'v2',"event->metadata->k1 has wrong value"); 101 | ok($event->{method} eq '/abc/dummy_method',"event->method has wrong value"); 102 | 103 | ##################################################### 104 | 105 | # print STDERR "event=".Dumper($event); 106 | # Dump($event); 107 | 108 | my $status_text = 'xyz'; 109 | my $server_call = $event->{call}; 110 | $event = $server_call->startBatch( 111 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 112 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 113 | 'metadata' => {}, 114 | 'code' => Grpc::Constants::GRPC_STATUS_OK(), 115 | 'details' => $status_text, 116 | }, 117 | Grpc::Constants::GRPC_OP_RECV_CLOSE_ON_SERVER() => 1, 118 | ); 119 | # print STDERR "event=".Dumper($event); 120 | # Dump($server_call); 121 | # Dump($call); 122 | 123 | ok($event->{send_metadata},"send_metadata is not true"); 124 | ok($event->{send_status},"send_status is not true"); 125 | ok(!$event->{cancelled},"cancelled is not false"); 126 | 127 | ##################################################### 128 | 129 | $event = $call->startBatch( 130 | Grpc::Constants::GRPC_OP_RECV_INITIAL_METADATA() => 1, 131 | Grpc::Constants::GRPC_OP_RECV_STATUS_ON_CLIENT() => 1, 132 | ); 133 | # print STDERR "event=".Dumper($event); 134 | 135 | ok(ref($event->{metadata})=~/HASH/,"event->metadata is not a hash"); 136 | ok(!(keys %{$event->{metadata}}),"event->metadata is not an empty hash"); 137 | 138 | my $status = $event->{status}; 139 | ok(ref($status->{metadata})=~/HASH/,"status->metadata is not a hash"); 140 | ok(!(keys %{$status->{metadata}}),"status->metadata is not an empty hash"); 141 | ok(exists($status->{code}),"status->code does not exist"); 142 | ok($status->{code} == Grpc::Constants::GRPC_STATUS_OK(),"status->code not STATUS_OK"); 143 | ok(exists($status->{details}),"status->details does not exist"); 144 | ok($status->{details} eq $status_text,"status->details does not contain ".$status_text); 145 | -------------------------------------------------------------------------------- /t/02-call.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | use Devel::Peek; 6 | 7 | plan tests => 15; 8 | 9 | #cancel 10 | #setCredentials 11 | 12 | delete @ENV{grep /https?_proxy/i, keys %ENV}; 13 | 14 | use_ok("Grpc::XS::Call"); 15 | use_ok("Grpc::XS::Channel"); 16 | use_ok("Grpc::XS::Timeval"); 17 | use_ok("Grpc::Constants"); 18 | 19 | my $channel= new Grpc::XS::Channel("channel"); 20 | my $deadline = Grpc::XS::Timeval::infFuture(); 21 | my $c = new Grpc::XS::Call($channel,"helloWorld",$deadline); 22 | isa_ok( $c, 'Grpc::XS::Call' ); 23 | can_ok( $c, 'startBatch' ); 24 | can_ok( $c, 'getPeer' ); 25 | can_ok( $c, 'cancel' ); 26 | can_ok( $c, 'setCredentials' ); 27 | undef $c; 28 | 29 | sub newCall { 30 | my $channel = shift; 31 | return new Grpc::XS::Call($channel, 32 | '/foo', 33 | Grpc::XS::Timeval::infFuture()); 34 | } 35 | 36 | my $server = new Grpc::XS::Server(); 37 | my $port = $server->addHttp2Port('0.0.0.0:0'); 38 | 39 | $channel = new Grpc::XS::Channel('localhost:'.$port); 40 | # Dump($channel); 41 | 42 | my $call; 43 | my $result; 44 | 45 | #check if hash works as input 46 | $call = newCall($channel); 47 | my %batch = ( 48 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 49 | ); 50 | $result = $call->startBatch(%batch); 51 | ok($result->{send_metadata},"hash as input for startBatch"); 52 | # Dump($result); 53 | 54 | ## testAddEmptyMetadata 55 | $call = newCall($channel); 56 | $result = $call->startBatch( 57 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {} ); 58 | ok($result->{send_metadata},"testAddEmptyMetadata"); 59 | 60 | ## testAddSingleMetadata 61 | $call = newCall($channel); 62 | $result = $call->startBatch( 63 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => { 'key' => ['value'] }, 64 | ); 65 | ok($result->{send_metadata},"testAddSingleMetadata"); 66 | 67 | ## testAddMultiValueMetadata 68 | $call = newCall($channel); 69 | $result = $call->startBatch( 70 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => { 'key' => ['value1', 'value2'] }, 71 | ); 72 | ok($result->{send_metadata},"testAddMultiValueMetadata"); 73 | 74 | ## testAddSingleAndMultiValueMetadata 75 | $call = newCall($channel); 76 | $result = $call->startBatch( 77 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => { 78 | 'key1' => ['value1'], 79 | 'key2' => ['value2', 'value3'] }, 80 | ); 81 | ok($result->{send_metadata},"testAddSingleAndMultiValueMetadata"); 82 | 83 | ## testGetPeer 84 | my $peer = $call->getPeer(); 85 | ok(defined($result),"testGetPeer"); 86 | -------------------------------------------------------------------------------- /t/03-channel_credentials.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | 6 | plan tests => 2; 7 | 8 | use_ok("Grpc::XS::ChannelCredentials"); 9 | 10 | my $c; 11 | 12 | #createDefault 13 | # $c = Grpc::XS::ChannelCredentials::createDefault(); 14 | # isa_ok( $c, 'Grpc::XS::ChannelCredentials' ); 15 | 16 | #createSsl 17 | 18 | #createComposite 19 | #createInsecure 20 | $c = Grpc::XS::ChannelCredentials::createInsecure(); 21 | ok( !defined($c), 'Grpc::XS::ChannelCredentials undef' ); 22 | -------------------------------------------------------------------------------- /t/04-channel.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | 6 | plan tests => 5; 7 | 8 | use_ok("Grpc::XS::Channel"); 9 | 10 | #new 11 | my $channel; 12 | eval { $channel = new Grpc::XS::Channel(); }; my $error = $@; 13 | ok($error && !$channel,"invalid constructor"); 14 | 15 | $channel = new Grpc::XS::Channel("channel"); 16 | isa_ok( $channel, 'Grpc::XS::Channel' ); 17 | 18 | $channel = new Grpc::XS::Channel("channel", a => "abc", b => "cde" ); 19 | isa_ok( $channel, 'Grpc::XS::Channel' ); 20 | 21 | $channel = new Grpc::XS::Channel("channel", a => 1, b => 2 ); 22 | isa_ok( $channel, 'Grpc::XS::Channel' ); 23 | 24 | #getTarget 25 | #getConnectivityState 26 | #watchConnectivityState 27 | #close 28 | -------------------------------------------------------------------------------- /t/05-constants.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | 6 | plan tests => 1; 7 | 8 | use_ok("Grpc::XS::Constants"); 9 | -------------------------------------------------------------------------------- /t/06-server_credentials.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | 6 | plan tests => 1; 7 | 8 | use_ok("Grpc::XS::ServerCredentials"); 9 | 10 | #createSsl 11 | -------------------------------------------------------------------------------- /t/07-server.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | 6 | plan tests => 1; 7 | 8 | use_ok("Grpc::XS::Server"); 9 | 10 | #new 11 | #requestCall 12 | #addSecureHttp2Port 13 | #start 14 | -------------------------------------------------------------------------------- /t/08-timeval.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | 6 | plan tests => 23; 7 | 8 | use_ok("Grpc::XS::Timeval"); 9 | use_ok("Grpc::Constants"); 10 | 11 | #similar 12 | #getClockType 13 | #sleepUntil 14 | 15 | my $new = new Grpc::XS::Timeval(); 16 | isa_ok( $new, 'Grpc::XS::Timeval' ); 17 | ok( $new->getClockType()==Grpc::Constants::GPR_CLOCK_REALTIME(),"ClockType = GPR_CLOCK_REALTIME"); 18 | 19 | my $zero = Grpc::XS::Timeval::zero(); 20 | isa_ok( $zero, 'Grpc::XS::Timeval' ); 21 | ok( $zero->getTvSec()==0,"tvsec = 0"); 22 | ok( $zero->getTvNsec()==0,"tvnsec = 0"); 23 | ok( $zero->getClockType()==Grpc::Constants::GPR_CLOCK_REALTIME(),"ClockType = GPR_CLOCK_REALTIME"); 24 | 25 | my $inf = Grpc::XS::Timeval::infFuture(); 26 | isa_ok( $inf, 'Grpc::XS::Timeval' ); 27 | ok( $inf->getClockType()==Grpc::Constants::GPR_CLOCK_REALTIME(),"ClockType = GPR_CLOCK_REALTIME"); 28 | 29 | $zero = Grpc::XS::Timeval::zero(); 30 | ok(Grpc::XS::Timeval::compare($zero, $zero)==0,"testCompareSame"); 31 | 32 | $zero = Grpc::XS::Timeval::zero(); 33 | my $past = Grpc::XS::Timeval::infPast(); 34 | ok(Grpc::XS::Timeval::compare($past, $zero)<0,"testPastIsLessThanZero 1"); 35 | ok(Grpc::XS::Timeval::compare($zero, $past)>0,"testPastIsLessThanZero 2"); 36 | 37 | $zero = Grpc::XS::Timeval::zero(); 38 | my $future = Grpc::XS::Timeval::infFuture(); 39 | ok(Grpc::XS::Timeval::compare($zero, $future)<0,"testFutureIsLessThanZero 1"); 40 | ok(Grpc::XS::Timeval::compare($future, $zero)>0,"testFutureIsLessThanZero 2"); 41 | 42 | $zero = Grpc::XS::Timeval::zero(); 43 | $future = Grpc::XS::Timeval::infFuture(); 44 | my $now = Grpc::XS::Timeval::now(); 45 | ok(Grpc::XS::Timeval::compare($zero,$now)<0,"testNowIsBetweenZeroAndFuture 1"); 46 | ok(Grpc::XS::Timeval::compare($now,$future)<0,"testNowIsBetweenZeroAndFuture 2"); 47 | 48 | $now = Grpc::XS::Timeval::now(); 49 | my $delta = new Grpc::XS::Timeval(1000); 50 | my $deadline = $now->add($delta); 51 | ok(Grpc::XS::Timeval::compare($deadline, $now)>0,"testNowAndAdd"); 52 | 53 | $now = Grpc::XS::Timeval::now(); 54 | $delta = new Grpc::XS::Timeval(1000); 55 | $deadline = $now->substract($delta); 56 | ok(Grpc::XS::Timeval::compare($deadline, $now)<0,"testNowAndAdd"); 57 | 58 | $now = Grpc::XS::Timeval::now(); 59 | $delta = new Grpc::XS::Timeval(1000); 60 | $deadline = $now->add($delta); 61 | my $back_to_now = $deadline->substract($delta); 62 | ok(Grpc::XS::Timeval::compare($back_to_now, $now)==0,"testAddAndSubtract"); 63 | 64 | my $fixed = new Grpc::XS::Timeval(1000); 65 | ok( $fixed->getTvSec()==0,"tvsec = 0"); 66 | ok( $fixed->getTvNsec()==1000000,"tvnsec = 1000000"); 67 | ok( $fixed->getClockType()==Grpc::Constants::GPR_TIMESPAN(),"ClockType = GPR_TIMESPAN"); 68 | -------------------------------------------------------------------------------- /t/09-abstract_call.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | 6 | plan tests => 1; 7 | 8 | use_ok("Grpc::Client::AbstractCall"); 9 | -------------------------------------------------------------------------------- /t/10-base_stub.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | 6 | plan tests => 2; 7 | 8 | use_ok("Grpc::Client::BaseStub"); 9 | use_ok("Grpc::XS::ChannelCredentials"); 10 | 11 | my $credentials = Grpc::XS::ChannelCredentials::createInsecure(); 12 | print STDERR Dumper($credentials); 13 | 14 | my $stub = new Grpc::Client::BaseStub( 15 | 'localhost:50051', 16 | credentials => $credentials, 17 | timeout => 1000000 ); 18 | print STDERR Dumper($stub); 19 | 20 | my $unmarshall = sub { return $_; }; 21 | my $result = $stub->_simpleRequest( 22 | method => "/test/timeout", 23 | deserialize => $unmarshall, 24 | argument => "testcall", 25 | metadata => undef, 26 | options => undef ); 27 | print STDERR Dumper($result); 28 | -------------------------------------------------------------------------------- /t/11-bidi_streaming_call.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | 6 | plan tests => 1; 7 | 8 | use_ok("Grpc::Client::BidiStreamingCall"); 9 | -------------------------------------------------------------------------------- /t/12-client_streaming_call.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | 6 | plan tests => 1; 7 | 8 | use_ok("Grpc::Client::ClientStreamingCall"); 9 | -------------------------------------------------------------------------------- /t/13-server_streaming_call.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | 6 | plan tests => 1; 7 | 8 | use_ok("Grpc::Client::ServerStreamingCall"); 9 | -------------------------------------------------------------------------------- /t/14-unary_call.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | 6 | plan tests => 1; 7 | 8 | use_ok("Grpc::Client::UnaryCall"); 9 | -------------------------------------------------------------------------------- /t/15-xs_end_to_end.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | use Devel::Peek; 6 | 7 | use File::Basename; 8 | use File::Spec; 9 | my $path = File::Basename::dirname( File::Spec->rel2abs(__FILE__) ); 10 | 11 | plan tests => 82; 12 | 13 | ## ---------------------------------------------------------------------------- 14 | 15 | delete @ENV{grep /https?_proxy/i, keys %ENV}; 16 | 17 | use_ok("Grpc::XS::Server"); 18 | use_ok("Grpc::XS::Channel"); 19 | use_ok("Grpc::XS::Call"); 20 | use_ok("Grpc::XS::Timeval"); 21 | use_ok("Grpc::Constants"); 22 | 23 | ##################################################### 24 | ## setup 25 | ##################################################### 26 | 27 | my $server = new Grpc::XS::Server(); 28 | my $port = $server->addHttp2Port('0.0.0.0:0'); 29 | my $channel = new Grpc::XS::Channel('localhost:'.$port); 30 | $server->start(); 31 | 32 | ##################################################### 33 | ## testSimpleRequestBody 34 | ##################################################### 35 | 36 | my $deadline = Grpc::XS::Timeval::infFuture(); 37 | my $status_text = 'xyz'; 38 | my $call = new Grpc::XS::Call($channel, 39 | 'dummy_method', 40 | $deadline); 41 | 42 | my $event = $call->startBatch( 43 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 44 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 45 | ); 46 | 47 | ok($event->{send_metadata},"startBatch failed return send_metadata"); 48 | ok($event->{send_close},"startBatch failed return send_close"); 49 | 50 | $event = $server->requestCall(); 51 | ok($event->{method} eq 'dummy_method',"event->method has wrong value"); 52 | my $server_call = $event->{call}; 53 | 54 | $event = $server_call->startBatch( 55 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 56 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 57 | 'metadata' => {}, 58 | 'code' => Grpc::Constants::GRPC_STATUS_OK(), 59 | 'details' => $status_text, 60 | }, 61 | Grpc::Constants::GRPC_OP_RECV_CLOSE_ON_SERVER() => 1, 62 | ); 63 | 64 | ok($event->{send_metadata},"send_metadata is not true"); 65 | ok($event->{send_status},"send_status is not true"); 66 | ok(!$event->{cancelled},"cancelled is not false"); 67 | 68 | $event = $call->startBatch( 69 | Grpc::Constants::GRPC_OP_RECV_INITIAL_METADATA() => 1, 70 | Grpc::Constants::GRPC_OP_RECV_STATUS_ON_CLIENT() => 1, 71 | ); 72 | 73 | my $status = $event->{status}; 74 | ok(ref($status->{metadata})=~/HASH/,"status->metadata is not a hash"); 75 | ok(!(keys %{$status->{metadata}}),"status->metadata is not an empty hash"); 76 | ok(exists($status->{code}),"status->code does not exist"); 77 | ok($status->{code} == Grpc::Constants::GRPC_STATUS_OK(),"status->code not STATUS_OK"); 78 | ok(exists($status->{details}),"status->details does not exist"); 79 | ok($status->{details} eq $status_text,"status->details does not contain ".$status_text); 80 | 81 | #undef $call; 82 | #undef $server_call; 83 | 84 | ##################################################### 85 | ## testMessageWriteFlags 86 | ##################################################### 87 | 88 | $deadline = Grpc::XS::Timeval::infFuture(); 89 | my $req_text = 'message_write_flags_test'; 90 | $status_text = 'xyz'; 91 | $call = new Grpc::XS::Call($channel, 92 | 'dummy_method', 93 | $deadline); 94 | 95 | $event = $call->startBatch( 96 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 97 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $req_text, 98 | 'flags' => Grpc::Constants::GRPC_WRITE_NO_COMPRESS(), }, 99 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 100 | ); 101 | 102 | ok($event->{send_metadata},"startBatch failed return send_metadata"); 103 | ok($event->{send_close},"startBatch failed return send_close"); 104 | 105 | $event = $server->requestCall(); 106 | ok($event->{method} eq 'dummy_method',"event->method has wrong value"); 107 | $server_call = $event->{call}; 108 | 109 | $event = $server_call->startBatch( 110 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 111 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 112 | 'metadata' => {}, 113 | 'code' => Grpc::Constants::GRPC_STATUS_OK(), 114 | 'details' => $status_text, 115 | }, 116 | ); 117 | 118 | $event = $call->startBatch( 119 | Grpc::Constants::GRPC_OP_RECV_INITIAL_METADATA() => 1, 120 | Grpc::Constants::GRPC_OP_RECV_STATUS_ON_CLIENT() => 1, 121 | ); 122 | 123 | $status = $event->{status}; 124 | ok(ref($status->{metadata})=~/HASH/,"status->metadata is not a hash"); 125 | ok(!(keys %{$status->{metadata}}),"status->metadata is not an empty hash"); 126 | ok(exists($status->{code}),"status->code does not exist"); 127 | ok($status->{code} == Grpc::Constants::GRPC_STATUS_OK(),"status->code not STATUS_OK"); 128 | ok(exists($status->{details}),"status->details does not exist"); 129 | ok($status->{details} eq $status_text,"status->details does not contain ".$status_text); 130 | 131 | #unset($call); 132 | #unset($server_call); 133 | 134 | ##################################################### 135 | ## testClientServerFullRequestResponse 136 | ##################################################### 137 | 138 | $deadline = Grpc::XS::Timeval::infFuture(); 139 | $req_text = 'client_server_full_request_response'; 140 | my $reply_text = 'reply:client_server_full_request_response'; 141 | $status_text = 'status:client_server_full_response_text'; 142 | 143 | $call = new Grpc::XS::Call($channel, 144 | 'dummy_method', 145 | $deadline); 146 | 147 | $event = $call->startBatch( 148 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 149 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 150 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $req_text }, 151 | ); 152 | 153 | ok($event->{send_metadata},"send_metadata is not true"); 154 | ok($event->{send_close},"send_close is not true"); 155 | ok($event->{send_message},"send_message is not true"); 156 | 157 | $event = $server->requestCall(); 158 | ok($event->{method} eq 'dummy_method',"event->method has wrong value"); 159 | $server_call = $event->{call}; 160 | 161 | $event = $server_call->startBatch( 162 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 163 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $reply_text }, 164 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 165 | 'metadata' => {}, 166 | 'code' => Grpc::Constants::GRPC_STATUS_OK(), 167 | 'details' => $status_text, 168 | }, 169 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => 1, 170 | Grpc::Constants::GRPC_OP_RECV_CLOSE_ON_SERVER() => 1, 171 | ); 172 | 173 | ok($event->{send_metadata},"send_metadata is not true"); 174 | ok($event->{send_status},"send_status is not true"); 175 | ok($event->{send_message},"send_message is not true"); 176 | ok(!$event->{cancelled},"cancelled is not false"); 177 | ok($event->{message} eq $req_text,"status->message does not contain ".$req_text); 178 | 179 | $event = $call->startBatch( 180 | Grpc::Constants::GRPC_OP_RECV_INITIAL_METADATA() => 1, 181 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => 1, 182 | Grpc::Constants::GRPC_OP_RECV_STATUS_ON_CLIENT() => 1, 183 | ); 184 | 185 | ok(ref($event->{metadata})=~/HASH/,"event->metadata is not a hash"); 186 | ok(!(keys %{$event->{metadata}}),"event->metadata is not an empty hash"); 187 | ok($event->{message} eq $reply_text,"event->message does not contain ".$reply_text); 188 | 189 | $status = $event->{status}; 190 | ok(ref($status->{metadata})=~/HASH/,"status->metadata is not a hash"); 191 | ok(!(keys %{$status->{metadata}}),"status->metadata is not an empty hash"); 192 | ok(exists($status->{code}),"status->code does not exist"); 193 | ok($status->{code} == Grpc::Constants::GRPC_STATUS_OK(),"status->code not STATUS_OK"); 194 | ok(exists($status->{details}),"status->details does not exist"); 195 | ok($status->{details} eq $status_text,"status->details does not contain ".$status_text); 196 | 197 | ##################################################### 198 | ## @expectedException InvalidArgumentException 199 | ## testInvalidClientMessageArray 200 | ##################################################### 201 | 202 | eval { 203 | $deadline = Grpc::XS::Timeval::infFuture(); 204 | $req_text = 'client_server_full_request_response'; 205 | $reply_text = 'reply:client_server_full_request_response'; 206 | $status_text = 'status:client_server_full_response_text'; 207 | 208 | $call = new Grpc::XS::Call($channel, 209 | 'dummy_method', 210 | $deadline); 211 | 212 | $event = $call->startBatch( 213 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 214 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 215 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => 'invalid', 216 | ); 217 | }; 218 | ok($@,"failed to trigger exception/testInvalidClientMessageArray"); 219 | 220 | ##################################################### 221 | ## @expectedException InvalidArgumentException 222 | ## testInvalidClientMessageString 223 | ##################################################### 224 | 225 | ## in perl 0 will be handled as a string as well 226 | 227 | #eval { 228 | # $deadline = Grpc::XS::Timeval::infFuture(); 229 | # $req_text = 'client_server_full_request_response'; 230 | # $reply_text = 'reply:client_server_full_request_response'; 231 | # $status_text = 'status:client_server_full_response_text'; 232 | # 233 | # $call = new Grpc::XS::Call($channel, 234 | # 'dummy_method', 235 | # $deadline); 236 | # 237 | # $event = $call->startBatch( 238 | # Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 239 | # Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 240 | # Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => 0 }, 241 | # ); 242 | #}; 243 | #ok($@,"failed to trigger exception/testInvalidClientMessageString"); 244 | 245 | ##################################################### 246 | ## @expectedException InvalidArgumentException 247 | ## testInvalidClientMessageFlags 248 | ##################################################### 249 | 250 | eval { 251 | $deadline = Grpc::XS::Timeval::infFuture(); 252 | $req_text = 'client_server_full_request_response'; 253 | $reply_text = 'reply:client_server_full_request_response'; 254 | $status_text = 'status:client_server_full_response_text'; 255 | 256 | $call = new Grpc::XS::Call($channel, 257 | 'dummy_method', 258 | $deadline); 259 | 260 | $event = $call->startBatch( 261 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 262 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 263 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => {'message' => 'abc', 264 | 'flags' => 'invalid'}, 265 | ); 266 | }; 267 | ok($@,"failed to trigger exception/testInvalidClientMessageFlags"); 268 | 269 | ##################################################### 270 | ## @expectedException InvalidArgumentException 271 | ## testInvalidServerStatusMetadata 272 | ##################################################### 273 | 274 | eval { 275 | $deadline = Grpc::XS::Timeval::infFuture(); 276 | $req_text = 'client_server_full_request_response'; 277 | $reply_text = 'reply:client_server_full_request_response'; 278 | $status_text = 'status:client_server_full_response_text'; 279 | 280 | $call = new Grpc::XS::Call($channel, 281 | 'dummy_method', 282 | $deadline); 283 | 284 | $event = $call->startBatch( 285 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 286 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 287 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $req_text }, 288 | ); 289 | 290 | ok($event->{send_metadata},"send_metadata is not true"); 291 | ok($event->{send_close},"send_close is not true"); 292 | ok($event->{send_message},"send_message is not true"); 293 | 294 | $event = $server->requestCall(); 295 | ok($event->{method} eq 'dummy_method',"event->method has wrong value"); 296 | $server_call = $event->{call}; 297 | 298 | $event = $server_call->startBatch( 299 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 300 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $reply_text }, 301 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 302 | 'metadata' => 'invalid', 303 | 'code' => Grpc::Constants::GRPC_STATUS_OK(), 304 | 'details' => $status_text, 305 | }, 306 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => 1, 307 | Grpc::Constants::GRPC_OP_RECV_CLOSE_ON_SERVER() => 1, 308 | ); 309 | }; 310 | ok($@,"failed to trigger exception/testInvalidServerStatusMetadata"); 311 | 312 | ##################################################### 313 | ## @expectedException InvalidArgumentException 314 | ## testInvalidServerStatusCode 315 | ##################################################### 316 | 317 | eval { 318 | $deadline = Grpc::XS::Timeval::infFuture(); 319 | $req_text = 'client_server_full_request_response'; 320 | $reply_text = 'reply:client_server_full_request_response'; 321 | $status_text = 'status:client_server_full_response_text'; 322 | 323 | $call = new Grpc::XS::Call($channel, 324 | 'dummy_method', 325 | $deadline); 326 | 327 | $event = $call->startBatch( 328 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 329 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 330 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $req_text }, 331 | ); 332 | 333 | ok($event->{send_metadata},"send_metadata is not true"); 334 | ok($event->{send_close},"send_close is not true"); 335 | ok($event->{send_message},"send_message is not true"); 336 | 337 | $event = $server->requestCall(); 338 | ok($event->{method} eq 'dummy_method',"event->method has wrong value"); 339 | $server_call = $event->{call}; 340 | 341 | $event = $server_call->startBatch( 342 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 343 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 344 | 'metadata' => [], 345 | 'code' => 'invalid', 346 | 'details' => $status_text, 347 | }, 348 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => 1, 349 | Grpc::Constants::GRPC_OP_RECV_CLOSE_ON_SERVER() => 1, 350 | ); 351 | }; 352 | ok($@,"failed to trigger exception/testInvalidServerStatusCode"); 353 | 354 | ##################################################### 355 | ## @expectedException InvalidArgumentException 356 | ## testMissingServerStatusCode 357 | ##################################################### 358 | 359 | eval { 360 | $deadline = Grpc::XS::Timeval::infFuture(); 361 | $req_text = 'client_server_full_request_response'; 362 | $reply_text = 'reply:client_server_full_request_response'; 363 | $status_text = 'status:client_server_full_response_text'; 364 | 365 | $call = new Grpc::XS::Call($channel, 366 | 'dummy_method', 367 | $deadline); 368 | 369 | $event = $call->startBatch( 370 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 371 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 372 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $req_text }, 373 | ); 374 | 375 | ok($event->{send_metadata},"send_metadata is not true"); 376 | ok($event->{send_close},"send_close is not true"); 377 | ok($event->{send_message},"send_message is not true"); 378 | 379 | $event = $server->requestCall(); 380 | ok($event->{method} eq 'dummy_method',"event->method has wrong value"); 381 | $server_call = $event->{call}; 382 | 383 | $event = $server_call->startBatch( 384 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 385 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $reply_text }, 386 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 387 | 'metadata' => {}, 388 | 'details' => $status_text, 389 | }, 390 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => 1, 391 | Grpc::Constants::GRPC_OP_RECV_CLOSE_ON_SERVER() => 1, 392 | ); 393 | }; 394 | ok($@,"failed to trigger exception/testMissingServerStatusCode"); 395 | 396 | ##################################################### 397 | ## @expectedException InvalidArgumentException 398 | ## testInvalidServerStatusDetails 399 | ##################################################### 400 | 401 | ## in perl 0 will be handled as a string as well 402 | 403 | #eval { 404 | # $deadline = Grpc::XS::Timeval::infFuture(); 405 | # $req_text = 'client_server_full_request_response'; 406 | # $reply_text = 'reply:client_server_full_request_response'; 407 | # $status_text = 'status:client_server_full_response_text'; 408 | # 409 | # $call = new Grpc::XS::Call($channel, 410 | # 'dummy_method', 411 | # $deadline); 412 | # 413 | # $event = $call->startBatch( 414 | # Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 415 | # Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 416 | # Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $req_text }, 417 | # ); 418 | # 419 | # ok($event->{send_metadata},"send_metadata is not true"); 420 | # ok($event->{send_close},"send_close is not true"); 421 | # ok($event->{send_message},"send_message is not true"); 422 | # 423 | # $event = $server->requestCall(); 424 | # ok($event->{method} eq 'dummy_method',"event->method has wrong value"); 425 | # $server_call = $event->{call}; 426 | # 427 | # $event = $server_call->startBatch( 428 | # Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 429 | # Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $reply_text }, 430 | # Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 431 | # 'metadata' => {}, 432 | # 'code' => Grpc::Constants::GRPC_STATUS_OK(), 433 | # 'details' => 0, 434 | # }, 435 | # Grpc::Constants::GRPC_OP_RECV_MESSAGE() => 1, 436 | # Grpc::Constants::GRPC_OP_RECV_CLOSE_ON_SERVER() => 1, 437 | # ); 438 | #}; 439 | #ok($@,"failed to trigger exception/testInvalidServerStatusDetails"); 440 | 441 | ##################################################### 442 | ## @expectedException InvalidArgumentException 443 | ## testMissingServerStatusDetails 444 | ##################################################### 445 | 446 | eval { 447 | $deadline = Grpc::XS::Timeval::infFuture(); 448 | $req_text = 'client_server_full_request_response'; 449 | $reply_text = 'reply:client_server_full_request_response'; 450 | $status_text = 'status:client_server_full_response_text'; 451 | 452 | $call = new Grpc::XS::Call($channel, 453 | 'dummy_method', 454 | $deadline); 455 | 456 | $event = $call->startBatch( 457 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 458 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 459 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $req_text }, 460 | ); 461 | 462 | ok($event->{send_metadata},"send_metadata is not true"); 463 | ok($event->{send_close},"send_close is not true"); 464 | ok($event->{send_message},"send_message is not true"); 465 | 466 | $event = $server->requestCall(); 467 | ok($event->{method} eq 'dummy_method',"event->method has wrong value"); 468 | $server_call = $event->{call}; 469 | 470 | $event = $server_call->startBatch( 471 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 472 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $reply_text }, 473 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 474 | 'metadata' => {}, 475 | 'code' => Grpc::Constants::GRPC_STATUS_OK(), 476 | }, 477 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => 1, 478 | Grpc::Constants::GRPC_OP_RECV_CLOSE_ON_SERVER() => 1, 479 | ); 480 | }; 481 | ok($@,"failed to trigger exception/testMissingServerStatusDetails"); 482 | 483 | ##################################################### 484 | ## @expectedException InvalidArgumentException 485 | ## testInvalidStartBatchKey 486 | ##################################################### 487 | 488 | eval { 489 | $deadline = Grpc::XS::Timeval::infFuture(); 490 | $req_text = 'client_server_full_request_response'; 491 | $reply_text = 'reply:client_server_full_request_response'; 492 | $status_text = 'status:client_server_full_response_text'; 493 | 494 | $call = new Grpc::XS::Call($channel, 495 | 'dummy_method', 496 | $deadline); 497 | 498 | $event = $call->startBatch( 499 | 9999999 => {}, 500 | ); 501 | }; 502 | ok($@,"failed to trigger exception/testInvalidStartBatchKey"); 503 | 504 | ##################################################### 505 | ## @expectedException LogicException 506 | ## testInvalidStartBatch 507 | ##################################################### 508 | 509 | eval { 510 | $deadline = Grpc::XS::Timeval::infFuture(); 511 | $req_text = 'client_server_full_request_response'; 512 | $reply_text = 'reply:client_server_full_request_response'; 513 | $status_text = 'status:client_server_full_response_text'; 514 | 515 | $call = new Grpc::XS::Call($channel, 516 | 'dummy_method', 517 | $deadline); 518 | 519 | $event = $call->startBatch( 520 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 521 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 522 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $req_text }, 523 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 524 | 'metadata' => {}, 525 | 'code' => Grpc::Constants::GRPC_STATUS_OK(), 526 | 'details' => 'abc', 527 | }, 528 | ); 529 | }; 530 | ok($@,"failed to trigger exception/testInvalidStartBatch"); 531 | 532 | ##################################################### 533 | ## testGetTarget() 534 | ##################################################### 535 | 536 | ok($channel->getTarget() ne "","getTarget returns value"); 537 | 538 | ##################################################### 539 | ## testGetConnectivityState() 540 | ##################################################### 541 | 542 | $channel = new Grpc::XS::Channel('localhost:'.$port); 543 | ok($channel->getConnectivityState() == Grpc::Constants::GRPC_CHANNEL_IDLE(), 544 | "connection is not idle"); 545 | 546 | ##################################################### 547 | ## testWatchConnectivityStateFailed() 548 | ##################################################### 549 | 550 | $channel = new Grpc::XS::Channel('localhost:'.$port); 551 | my $idle_state = $channel->getConnectivityState(); 552 | ok($idle_state == Grpc::Constants::GRPC_CHANNEL_IDLE(), 553 | "connection is not idle"); 554 | 555 | my $now = Grpc::XS::Timeval::now(); 556 | my $delta = new Grpc::XS::Timeval(500000); ## should timeout 557 | $deadline = $now->add($delta); 558 | 559 | ok(!$channel->watchConnectivityState($idle_state, $deadline), 560 | "should timeout watchConnectivityState"); 561 | 562 | ##################################################### 563 | ## testWatchConnectivityStateSuccess() 564 | ##################################################### 565 | 566 | $channel = new Grpc::XS::Channel('localhost:'.$port); 567 | $idle_state = $channel->getConnectivityState(1); 568 | ok($idle_state == Grpc::Constants::GRPC_CHANNEL_IDLE(), 569 | "connection is not idle"); 570 | 571 | $now = Grpc::XS::Timeval::now(); 572 | $delta = new Grpc::XS::Timeval(3000000); ## should finish well before 573 | $deadline = $now->add($delta); 574 | 575 | ok($channel->watchConnectivityState($idle_state, $deadline), 576 | "should not timeout watchConnectivityState"); 577 | 578 | my $new_state = $channel->getConnectivityState(); 579 | ok($idle_state != $new_state, "idle_state should not equal new_state"); 580 | 581 | ##################################################### 582 | ## testWatchConnectivityStateDoNothing() 583 | ##################################################### 584 | 585 | $channel = new Grpc::XS::Channel('localhost:'.$port); 586 | $idle_state = $channel->getConnectivityState(); 587 | ok($idle_state == Grpc::Constants::GRPC_CHANNEL_IDLE(), 588 | "connection is not idle"); 589 | 590 | $now = Grpc::XS::Timeval::now(); 591 | $delta = new Grpc::XS::Timeval(100000); 592 | $deadline = $now->add($delta); 593 | 594 | ok(!$channel->watchConnectivityState($idle_state, $deadline), 595 | "should timeout watchConnectivityState"); 596 | 597 | $new_state = $channel->getConnectivityState(); 598 | ok($new_state == Grpc::Constants::GRPC_CHANNEL_IDLE(), 599 | "connection is not idle"); 600 | 601 | ##################################################### 602 | ## @expectedException InvalidArgumentException 603 | ## testGetConnectivityStateInvalidParam() 604 | ##################################################### 605 | 606 | eval { 607 | $channel = new Grpc::XS::Channel('localhost:'.$port); 608 | $channel->getConnectivityState(new Grpc::XS::Timeval()); 609 | }; 610 | ok($@,"failed to trigger exception/testGetConnectivityStateInvalidParam"); 611 | 612 | ##################################################### 613 | ## @expectedException InvalidArgumentException 614 | ## testWatchConnectivityStateInvalidParam() 615 | ##################################################### 616 | 617 | eval { 618 | $channel = new Grpc::XS::Channel('localhost:'.$port); 619 | $channel->watchConnectivityState(0, 1000); 620 | }; 621 | ok($@,"failed to trigger exception/testWatchConnectivityStateInvalidParam"); 622 | 623 | ##################################################### 624 | ## testClose() 625 | ##################################################### 626 | 627 | $channel = new Grpc::XS::Channel('localhost:'.$port); 628 | ok(!defined($channel->close()),"channel should not return value"); 629 | 630 | ##################################################### 631 | ## testChannelConstructorInvalidParam() 632 | ## @expectedException InvalidArgumentException 633 | ##################################################### 634 | 635 | eval { 636 | $channel = new Grpc::XS::Channel('localhost:'.$port, undef); 637 | }; 638 | ok($@,"failed to trigger exception/InvalidArgumentException"); 639 | -------------------------------------------------------------------------------- /t/16-xs_secure_end_to_end.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | use Devel::Peek; 6 | 7 | use File::Basename; 8 | use File::Spec; 9 | my $path = File::Basename::dirname( File::Spec->rel2abs(__FILE__) ); 10 | 11 | plan tests => 46; 12 | 13 | use_ok("Grpc::XS::CallCredentials"); 14 | 15 | ## ---------------------------------------------------------------------------- 16 | 17 | delete @ENV{grep /https?_proxy/i, keys %ENV}; 18 | 19 | use_ok("Grpc::XS::Server"); 20 | use_ok("Grpc::XS::Channel"); 21 | use_ok("Grpc::XS::Call"); 22 | use_ok("Grpc::XS::Timeval"); 23 | use_ok("Grpc::Constants"); 24 | 25 | sub file_get_contents { 26 | my $file = shift; 27 | open(F,"<".$file); 28 | my $content = join("",); 29 | close(F); 30 | return $content; 31 | } 32 | 33 | ##################################################### 34 | ## setup 35 | ##################################################### 36 | 37 | my $credentials = Grpc::XS::ChannelCredentials::createSsl( 38 | pem_root_certs => file_get_contents($path.'/data/ca.pem')); 39 | my $server_credentials = Grpc::XS::ServerCredentials::createSsl( 40 | pem_root_certs => undef, 41 | pem_private_key => file_get_contents($path.'/data/server1.key'), 42 | pem_cert_chain => file_get_contents($path.'/data/server1.pem')); 43 | 44 | my $server = new Grpc::XS::Server(); 45 | my $port = $server->addSecureHttp2Port('0.0.0.0:0', 46 | $server_credentials); 47 | $server->start(); 48 | my $host_override = 'foo.test.google.fr'; 49 | my $channel = new Grpc::XS::Channel( 50 | 'localhost:'.$port, 51 | 'grpc.ssl_target_name_override' => $host_override, 52 | 'grpc.default_authority' => $host_override, 53 | 'credentials' => $credentials, 54 | ); 55 | 56 | ##################################################### 57 | ## testSimpleRequestBody 58 | ##################################################### 59 | 60 | my $deadline = Grpc::XS::Timeval::infFuture(); 61 | my $status_text = 'xyz'; 62 | my $call = new Grpc::XS::Call($channel, 63 | 'dummy_method', 64 | $deadline, 65 | $host_override); 66 | 67 | my $event = $call->startBatch( 68 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 69 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 70 | ); 71 | 72 | ok($event->{send_metadata},"startBatch failed return send_metadata"); 73 | ok($event->{send_close},"startBatch failed return send_close"); 74 | 75 | $event = $server->requestCall(); 76 | ok($event->{method} eq 'dummy_method',"event->method has wrong value"); 77 | my $server_call = $event->{call}; 78 | 79 | $event = $server_call->startBatch( 80 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 81 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 82 | 'metadata' => {}, 83 | 'code' => Grpc::Constants::GRPC_STATUS_OK(), 84 | 'details' => $status_text, 85 | }, 86 | Grpc::Constants::GRPC_OP_RECV_CLOSE_ON_SERVER() => 1, 87 | ); 88 | 89 | ok($event->{send_metadata},"send_metadata is not true"); 90 | ok($event->{send_status},"send_status is not true"); 91 | ok(!$event->{cancelled},"cancelled is not false"); 92 | 93 | $event = $call->startBatch( 94 | Grpc::Constants::GRPC_OP_RECV_INITIAL_METADATA() => 1, 95 | Grpc::Constants::GRPC_OP_RECV_STATUS_ON_CLIENT() => 1, 96 | ); 97 | 98 | ok(ref($event->{metadata})=~/HASH/,"event->metadata is not a hash"); 99 | ok(!(keys %{$event->{metadata}}),"event->metadata is not an empty hash"); 100 | 101 | my $status = $event->{status}; 102 | ok(ref($status->{metadata})=~/HASH/,"status->metadata is not a hash"); 103 | ok(!(keys %{$status->{metadata}}),"status->metadata is not an empty hash"); 104 | ok(exists($status->{code}),"status->code does not exist"); 105 | ok($status->{code} == Grpc::Constants::GRPC_STATUS_OK(),"status->code not STATUS_OK"); 106 | ok(exists($status->{details}),"status->details does not exist"); 107 | ok($status->{details} eq $status_text,"status->details does not contain ".$status_text); 108 | 109 | ##################################################### 110 | ## testMessageWriteFlags 111 | ##################################################### 112 | 113 | $deadline = Grpc::XS::Timeval::infFuture(); 114 | my $req_text = 'message_write_flags_test'; 115 | $status_text = 'xyz'; 116 | $call = new Grpc::XS::Call($channel, 117 | 'dummy_method', 118 | $deadline, 119 | $host_override); 120 | 121 | $event = $call->startBatch( 122 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 123 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $req_text, 124 | 'flags' => Grpc::Constants::GRPC_WRITE_NO_COMPRESS(), }, 125 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 126 | ); 127 | 128 | ok($event->{send_metadata},"startBatch failed return send_metadata"); 129 | ok($event->{send_close},"startBatch failed return send_close"); 130 | 131 | $event = $server->requestCall(); 132 | ok($event->{method} eq 'dummy_method',"event->method has wrong value"); 133 | $server_call = $event->{call}; 134 | 135 | $event = $server_call->startBatch( 136 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 137 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 138 | 'metadata' => {}, 139 | 'code' => Grpc::Constants::GRPC_STATUS_OK(), 140 | 'details' => $status_text, 141 | }, 142 | ); 143 | 144 | $event = $call->startBatch( 145 | Grpc::Constants::GRPC_OP_RECV_INITIAL_METADATA() => 1, 146 | Grpc::Constants::GRPC_OP_RECV_STATUS_ON_CLIENT() => 1, 147 | ); 148 | 149 | ok(ref($event->{metadata})=~/HASH/,"event->metadata is not a hash"); 150 | ok(!(keys %{$event->{metadata}}),"event->metadata is not an empty hash"); 151 | 152 | $status = $event->{status}; 153 | ok(ref($status->{metadata})=~/HASH/,"status->metadata is not a hash"); 154 | ok(!(keys %{$status->{metadata}}),"status->metadata is not an empty hash"); 155 | ok(exists($status->{code}),"status->code does not exist"); 156 | ok($status->{code} == Grpc::Constants::GRPC_STATUS_OK(),"status->code not STATUS_OK"); 157 | ok(exists($status->{details}),"status->details does not exist"); 158 | ok($status->{details} eq $status_text,"status->details does not contain ".$status_text); 159 | 160 | ##################################################### 161 | ## testClientServerFullRequestResponse 162 | ##################################################### 163 | 164 | $deadline = Grpc::XS::Timeval::infFuture(); 165 | $req_text = 'client_server_full_request_response'; 166 | my $reply_text = 'reply:client_server_full_request_response'; 167 | $status_text = 'status:client_server_full_response_text'; 168 | 169 | $call = new Grpc::XS::Call($channel, 170 | 'dummy_method', 171 | $deadline, 172 | $host_override); 173 | 174 | $event = $call->startBatch( 175 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 176 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 177 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $req_text }, 178 | ); 179 | 180 | #$this->assertTrue($event->send_metadata); 181 | #$this->assertTrue($event->send_close); 182 | #$this->assertTrue($event->send_message); 183 | 184 | $event = $server->requestCall(); 185 | #$this->assertSame('dummy_method', $event->method); 186 | $server_call = $event->{call}; 187 | 188 | $event = $server_call->startBatch( 189 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 190 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 'message' => $reply_text }, 191 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 192 | 'metadata' => {}, 193 | 'code' => Grpc::Constants::GRPC_STATUS_OK(), 194 | 'details' => $status_text, 195 | }, 196 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => 1, 197 | Grpc::Constants::GRPC_OP_RECV_CLOSE_ON_SERVER() => 1, 198 | ); 199 | 200 | ok($event->{send_metadata},"send_metadata is not true"); 201 | ok($event->{send_status},"send_status is not true"); 202 | ok($event->{send_message},"send_message is not true"); 203 | ok(!$event->{cancelled},"cancelled is not false"); 204 | ok($req_text eq $event->{message},"message does not equal $req_text"); 205 | 206 | $event = $call->startBatch( 207 | Grpc::Constants::GRPC_OP_RECV_INITIAL_METADATA() => 1, 208 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => 1, 209 | Grpc::Constants::GRPC_OP_RECV_STATUS_ON_CLIENT() => 1, 210 | ); 211 | 212 | ok(ref($event->{metadata})=~/HASH/,"event->metadata is not a hash"); 213 | ok(!(keys %{$event->{metadata}}),"event->metadata is not an empty hash"); 214 | ok($reply_text eq $event->{message},"message does not equal $reply_text"); 215 | 216 | $status = $event->{status}; 217 | ok(ref($status->{metadata})=~/HASH/,"status->metadata is not a hash"); 218 | ok(!(keys %{$status->{metadata}}),"status->metadata is not an empty hash"); 219 | ok(exists($status->{code}),"status->code does not exist"); 220 | ok($status->{code} == Grpc::Constants::GRPC_STATUS_OK(),"status->code not STATUS_OK"); 221 | ok(exists($status->{details}),"status->details does not exist"); 222 | ok($status->{details} eq $status_text,"status->details does not contain ".$status_text); 223 | 224 | # I was not able to trigger a plugin calls without ssl/composite credentials, so let's test it there 225 | subtest "plugin credentials error handling" => sub { 226 | my $always_die = sub { 227 | die "HERE"; 228 | }; 229 | 230 | my $not_a_reference = sub { 231 | return "string"; 232 | }; 233 | 234 | my $not_a_hashref = sub { 235 | return [5, 7, 9]; 236 | }; 237 | 238 | my $nothing = sub { 239 | return; 240 | }; 241 | 242 | my @tests = ( 243 | [$always_die, "always_die", qr/HERE/], 244 | [$not_a_reference, "not_a_reference", qr/calback returned non-reference/], 245 | [$not_a_hashref, 'not_a_hashref', qr/callback returned invalid metadata/], 246 | [$nothing, 'nothing', qr/calback returned non-reference/], 247 | ); 248 | 249 | for my $test (@tests) { 250 | my ($plugin, $name, $expect_like) = @$test; 251 | 252 | subtest $name => sub { 253 | my $call_creds = Grpc::XS::CallCredentials::createFromPlugin($plugin); 254 | 255 | my $credentials = Grpc::XS::ChannelCredentials::createComposite($credentials, $call_creds); 256 | my $channel = new Grpc::XS::Channel( 257 | 'localhost:'.$port, 258 | 'grpc.ssl_target_name_override' => $host_override, 259 | 'grpc.default_authority' => $host_override, 260 | 'credentials' => $credentials, 261 | ); 262 | 263 | my $call = new Grpc::XS::Call( 264 | $channel, 265 | '/dummy_method', 266 | $deadline, 267 | $host_override 268 | ); 269 | 270 | my $event = $call->startBatch( 271 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 272 | Grpc::Constants::GRPC_OP_SEND_CLOSE_FROM_CLIENT() => 1, 273 | Grpc::Constants::GRPC_OP_RECV_STATUS_ON_CLIENT() => 1, 274 | ); 275 | 276 | my $details = $event->{status}{details}; 277 | if ($details =~ $expect_like) { 278 | ok 1, "Status looks good" 279 | } else { 280 | # bail out immediately, or we can become stuck on the next iteration 281 | die "'$details' doesn't look like $expect_like"; 282 | } 283 | }; 284 | } 285 | }; 286 | -------------------------------------------------------------------------------- /t/17-fork_friendliness.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use strict; 3 | use Data::Dumper; 4 | use Test::More; 5 | use Devel::Peek; 6 | 7 | use Grpc::XS::Server; 8 | use Grpc::XS::Channel; 9 | use Grpc::XS::Call; 10 | 11 | my $ITERATIONS = 1; 12 | 13 | sub run_server { 14 | my $do_exit = shift; 15 | 16 | Grpc::XS::init(); 17 | my $server = new Grpc::XS::Server(); 18 | my $port = $server->addHttp2Port('0.0.0.0:0'); 19 | my $channel = new Grpc::XS::Channel('localhost:'.$port); 20 | $server->start(); 21 | exit 3 if $do_exit; 22 | } 23 | 24 | foreach my $i (1..$ITERATIONS) { 25 | run_server(); 26 | 27 | Grpc::XS::destroy(); 28 | my $pid1 = fork() || run_server(1); 29 | my $pid2 = fork() || run_server(1); 30 | 31 | waitpid $pid1, 0; 32 | is $?, 256 * 3; 33 | 34 | waitpid $pid2, 0; 35 | is $?, 256 * 3; 36 | 37 | run_server(); 38 | } 39 | 40 | done_testing; 41 | -------------------------------------------------------------------------------- /t/data/README: -------------------------------------------------------------------------------- 1 | CONFIRMEDTESTKEY 2 | -------------------------------------------------------------------------------- /t/data/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDfTCCAmWgAwIBAgIUeFbAjK3THrsyd7V8n/LgCKQO95AwDQYJKoZIhvcNAQEL 3 | BQAwVjEPMA0GA1UEAxMGdGVzdGNhMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz 4 | IFB0eSBMdGQxEzARBgNVBAgTClNvbWUtU3RhdGUxCzAJBgNVBAYTAkFVMB4XDTIw 5 | MDgwMzEyMzUxNVoXDTMwMDgwMTEyMzUyMFowVjEPMA0GA1UEAxMGdGVzdGNhMSEw 6 | HwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAgTClNvbWUt 7 | U3RhdGUxCzAJBgNVBAYTAkFVMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC 8 | AQEAp9Sw1/oKCBVRDQWq8HhfT5iHHiYgxGiZ65nr9lJnKAjkmYon7FRdODG9gW+n 9 | twOtdVasWQa2BW36yGtACeNqVYRIT7RWiqr0f8p7Zrt4s7iAsgAC6yBlwzKLcYlq 10 | W9Bav6Hd1rv6vE+DUwUFdmQ+7HnQXU0PKO3rsk41PpseD/bie8KClJOCT50DaAZY 11 | IZuMwEz6Y6FAZKg3g6scawFGsX9lrg5mctVvoU9MNse0Y1DtXJj6LZI0rXD4Ddgf 12 | K9xaCxsqewAHrEOh0wpcf4bVt8OUwsrw7SFcoq7SGhl9/XrxIgjIsxE2fhxzC8As 13 | DmnGgTT4iqrHXurORx5cHPYRFQIDAQABo0MwQTAPBgNVHRMBAf8EBTADAQH/MA8G 14 | A1UdDwEB/wQFAwMHBAAwHQYDVR0OBBYEFGs8fFHLcFpwVvQl0LTIfAIOn8CzMA0G 15 | CSqGSIb3DQEBCwUAA4IBAQBRRIVdi3INUAsBniqpNQOo7QgMGziWrqCoZN6f52zJ 16 | /xANG6osxNxk6XQW7nd1Rsv2DqBAsuC91P1qXEX2olKXB1tJKVpn494x7L1ZQ0lM 17 | pGsrPEI56iLUiu1frE+fkd27Eci5wWGDgUSpkbIdlYtfjqQ3yRpBtyUQiMH5Za3U 18 | 4NiWH4N+0n/NAU5lV/AY+T7Fuih0lnNiJam9ncBXR/T4p8ztnfWGYYCSITGrtfvL 19 | HuHGQ42rsEIXir+N6tt/raeUXtWB6qW98+HOPg4XfwUb+LCN0vXRhHGPd88fjWMW 20 | AjaT2QdrfTDc9ld2D/G0Ew9FLgwG4lJ6Hh68hmZxT0Qq 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /t/data/server1.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAp9Sw1/oKCBVRDQWq8HhfT5iHHiYgxGiZ65nr9lJnKAjkmYon 3 | 7FRdODG9gW+ntwOtdVasWQa2BW36yGtACeNqVYRIT7RWiqr0f8p7Zrt4s7iAsgAC 4 | 6yBlwzKLcYlqW9Bav6Hd1rv6vE+DUwUFdmQ+7HnQXU0PKO3rsk41PpseD/bie8KC 5 | lJOCT50DaAZYIZuMwEz6Y6FAZKg3g6scawFGsX9lrg5mctVvoU9MNse0Y1DtXJj6 6 | LZI0rXD4DdgfK9xaCxsqewAHrEOh0wpcf4bVt8OUwsrw7SFcoq7SGhl9/XrxIgjI 7 | sxE2fhxzC8AsDmnGgTT4iqrHXurORx5cHPYRFQIDAQABAoIBAC7KFi9wTyGnx7vo 8 | q5p1BKVtMaZPHO2E9/x+IhG/MXEw4YKIyM6TSpXjECEo3sogJVjEPDJps49Z9QOx 9 | Gt5m33tN8Nb/wOzR8m46JiMi+uin9IErjaS9mIFN8yBJHjTQpqwsVWuLZsDfLg6U 10 | JMy37+7/mv6YADT5967zLNbyafQq/anZyePa+CL+gNeZUTaGd/XOA379Fpri6xZ4 11 | gMmW5oQoCSrzWnyk667kziAR4eGrFEe0DksNQ3f1pp9mMVEm332mnKdPrO8HLnaS 12 | iiW3UmHS31spWK89ogWPN8N7Xsf5Ct3ft/dq7qeRU31OgpOxR+MYs0JxT7RbC0rD 13 | eoNHzeECgYEA04IOoti5Q6j/URNelTCVCBXeEKiVdXuB4giNV86KfSaRZGXmNfbz 14 | RoqaqPtNmppLO5umw2E9VJdBH1U2rUyvNPOLVP8raF9AQHM/LnW6pkfMoRoVvSKj 15 | wdBbYjdtPXyN81Cb3uBT0WKwVtkd53YCcXOJpWORpPuIivYsS/LwhX0CgYEAyyKR 16 | Mx6wpTbBSZvmgbF19QSxuoM7IN7Jv23BWJU9EsdhcHm8T3oVk8mCuqzJORtTNJgS 17 | D82kC7bzWgtpboSsw7v/Kcpms0yQL9HcAuTbeGDF2pB49LtlY72V/G/OelLGa7IW 18 | iT3X6tLfSDLGI3EaoFvUdPlUUN5Hvpql6HIQLXkCgYBXTHUSDfXZ0WUgZ6hAV78L 19 | iUsRASQ/S2z+iJ0eQueyZDraf5yXZYhf81GGscMELu8ieUpJllckFRISBq/8s4wV 20 | hPhbar7V44q3j1niqUES5Mu3KvcSC7wfgQpW7Z2vJTvJ09miEmFGKT+zeQX4xSKZ 21 | BSEpHIG+4PsosBb0eh+AWQKBgG/DcxQnPJWKDMrNJgQReY73qDmwXmX1bhcO8iQk 22 | 6Fder7PMptkrmJeZdX3z9zqeWCdFzBo50JpJbJcMVFPkV5HR69A4dk0MQQCufFhz 23 | RnVy+SkJ+CLewCgidVVQxs/ynw0+DLwx9IxUvVjh5rY1UqsMG1bIn6Vmxx6Nw96i 24 | c1gxAoGAKOO8auj41tcBeJtdZybANCMZhps5ZnfQz2uVZE13oPoHaxBTn0l6MzLG 25 | 83nCtmmTunl+d+RUgdJyLO9w1EkHcffh1lwN9yDw3t46cLOUePK8+tQnCgXf7WWE 26 | Qj3zm3Tl9PiqIzYeB7FA9Yu6tg5eBFTw4G5PolHMIbNU7uj5ol4= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /t/data/server1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEEjCCAvqgAwIBAgIUF7v640qBiNk8ZDdRapfRgztimbkwDQYJKoZIhvcNAQEL 3 | BQAwVjEPMA0GA1UEAxMGdGVzdGNhMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz 4 | IFB0eSBMdGQxEzARBgNVBAgTClNvbWUtU3RhdGUxCzAJBgNVBAYTAkFVMB4XDTIw 5 | MDgwMzEyNDEzOVoXDTMwMDUwMzEyNDE1NFowZTEaMBgGA1UEAwwRKi50ZXN0Lmdv 6 | b2dsZS5jb20xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEQMA4GA1UEBxMHQ2hpY2Fn 7 | bzERMA8GA1UECBMISWxsaW5vaXMxCzAJBgNVBAYTAlVTMIIBIjANBgkqhkiG9w0B 8 | AQEFAAOCAQ8AMIIBCgKCAQEAp9Sw1/oKCBVRDQWq8HhfT5iHHiYgxGiZ65nr9lJn 9 | KAjkmYon7FRdODG9gW+ntwOtdVasWQa2BW36yGtACeNqVYRIT7RWiqr0f8p7Zrt4 10 | s7iAsgAC6yBlwzKLcYlqW9Bav6Hd1rv6vE+DUwUFdmQ+7HnQXU0PKO3rsk41Ppse 11 | D/bie8KClJOCT50DaAZYIZuMwEz6Y6FAZKg3g6scawFGsX9lrg5mctVvoU9MNse0 12 | Y1DtXJj6LZI0rXD4DdgfK9xaCxsqewAHrEOh0wpcf4bVt8OUwsrw7SFcoq7SGhl9 13 | /XrxIgjIsxE2fhxzC8AsDmnGgTT4iqrHXurORx5cHPYRFQIDAQABo4HIMIHFMAwG 14 | A1UdEwEB/wQCMAAwTwYDVR0RBEgwRoIQKi50ZXN0Lmdvb2dsZS5mcoIYd2F0ZXJ6 15 | b29pLnRlc3QuZ29vZ2xlLmJlghIqLnRlc3QueW91dHViZS5jb22HBMCoAQMwEwYD 16 | VR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0PAQH/BAUDAwegADAdBgNVHQ4EFgQUazx8 17 | UctwWnBW9CXQtMh8Ag6fwLMwHwYDVR0jBBgwFoAUazx8UctwWnBW9CXQtMh8Ag6f 18 | wLMwDQYJKoZIhvcNAQELBQADggEBACeQPYuvqhZ/LA+vtz8U9fvIaf+124Nzu1oh 19 | jrbd6t4S5RjTwyTfpRo3KAeYbZUufVVvGkxKyXFJMZu14MBDCsmyfVyaiF84QFFc 20 | G01Y8fXr/ccC85l3GrN5M6jGilLALihyqzyTZ9gxeORR37hrzf9cF/hEJVUNvqND 21 | uMWY7UVS0iSnYD4kbIPZI3cUEbxGdUcmPffADAOISgUkDIAYihdFYnXhemGkANsk 22 | ELBNp9HUC+pgMjYJ7HQaK9/1XIff8wDyQwStl2H7zQ5N+pNfJ5S/Y6U365WA8137 23 | /p0nPUVrChW9pUWwL30K9IsrP0DBKvA/aUsz+DgtxtMjD8KUic8= 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /typemap: -------------------------------------------------------------------------------- 1 | Grpc::XS::Call T_PTROBJ 2 | Grpc::XS::CallCredentials T_PTROBJ 3 | Grpc::XS::Channel T_PTROBJ 4 | Grpc::XS::ChannelCredentials T_PTROBJ 5 | Grpc::XS::Server T_PTROBJ 6 | Grpc::XS::ServerCredentials T_PTROBJ 7 | Grpc::XS::Timeval T_PTROBJ 8 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | #include "EXTERN.h" 2 | #include "perl.h" 3 | #include "XSUB.h" 4 | #define NEED_my_strlcpy 5 | #include "ppport.h" 6 | 7 | #include "util.h" 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #if !defined(GRPC_VERSION_1_1) 14 | #include 15 | #else 16 | #include 17 | #endif 18 | #include 19 | 20 | bool module_initialized = false; 21 | grpc_completion_queue *completion_queue; 22 | 23 | grpc_byte_buffer *string_to_byte_buffer(char *string, size_t length) { 24 | gpr_slice slice = gpr_slice_from_copied_buffer(string, length); 25 | grpc_byte_buffer *buffer = grpc_raw_byte_buffer_create(&slice, 1); 26 | gpr_slice_unref(slice); 27 | return buffer; 28 | } 29 | 30 | void byte_buffer_to_string(grpc_byte_buffer *buffer, char **out_string, 31 | size_t *out_length) { 32 | if (buffer == NULL) { 33 | *out_string = NULL; 34 | *out_length = 0; 35 | return; 36 | } 37 | size_t length = grpc_byte_buffer_length(buffer); 38 | char *string = calloc(length + 1, sizeof(char)); 39 | size_t offset = 0; 40 | grpc_byte_buffer_reader reader; 41 | grpc_byte_buffer_reader_init(&reader, buffer); 42 | gpr_slice next; 43 | while (grpc_byte_buffer_reader_next(&reader, &next) != 0) { 44 | memcpy(string + offset, GPR_SLICE_START_PTR(next), GPR_SLICE_LENGTH(next)); 45 | offset += GPR_SLICE_LENGTH(next); 46 | gpr_slice_unref(next); 47 | } 48 | *out_string = string; 49 | *out_length = length; 50 | } 51 | 52 | void grpc_perl_init() { 53 | if (module_initialized) { 54 | return; 55 | } 56 | module_initialized = true; 57 | grpc_init(); 58 | grpc_perl_init_completion_queue(); 59 | } 60 | 61 | void grpc_perl_destroy() { 62 | if (!module_initialized) { 63 | return; 64 | } 65 | grpc_perl_shutdown_completion_queue(); 66 | #if defined(GRPC_HAS_SHUTDOWN_BLOCKING) 67 | grpc_shutdown_blocking(); 68 | #else 69 | grpc_shutdown(); 70 | #endif 71 | module_initialized = false; 72 | } 73 | 74 | void grpc_perl_init_completion_queue() { 75 | #if defined(GRPC_VERSION_1_4) 76 | grpc_completion_queue_attributes attr; 77 | 78 | attr.version = 1; 79 | attr.cq_completion_type = GRPC_CQ_PLUCK; 80 | attr.cq_polling_type = GRPC_CQ_DEFAULT_POLLING; 81 | 82 | completion_queue = grpc_completion_queue_create(grpc_completion_queue_factory_lookup(&attr), &attr, NULL); 83 | #else 84 | completion_queue = grpc_completion_queue_create(NULL); 85 | #endif 86 | } 87 | 88 | void grpc_perl_shutdown_completion_queue() { 89 | grpc_completion_queue_shutdown(completion_queue); 90 | #if defined(GRPC_VERSION_1_4) 91 | while (grpc_completion_queue_pluck(completion_queue, NULL, 92 | gpr_inf_future(GPR_CLOCK_REALTIME), 93 | NULL).type != GRPC_QUEUE_SHUTDOWN); 94 | #else 95 | while (grpc_completion_queue_next(completion_queue, 96 | gpr_inf_future(GPR_CLOCK_REALTIME), 97 | NULL).type != GRPC_QUEUE_SHUTDOWN); 98 | #endif 99 | grpc_completion_queue_destroy(completion_queue); 100 | completion_queue = NULL; 101 | } 102 | 103 | void perl_grpc_read_args_array(HV *hash, grpc_channel_args *args) { 104 | // handle hashes 105 | if (SvTYPE(hash)!=SVt_PVHV) { 106 | croak("Expected hash for perl_grpc_read_args_array() args"); 107 | } 108 | 109 | char* key; 110 | I32 keylen; 111 | SV* value; 112 | 113 | // count items in hash 114 | args->num_args = 0; 115 | hv_iterinit(hash); 116 | while((value = hv_iternextsv(hash,&key,&keylen))!=NULL) { 117 | args->num_args += 1; 118 | } 119 | 120 | args->args = calloc(args->num_args, sizeof(grpc_arg)); 121 | 122 | int args_index = 0; 123 | hv_iterinit(hash); 124 | while((value = hv_iternextsv(hash,&key,&keylen))!=NULL) { 125 | if (SvOK(value)) { 126 | args->args[args_index].key = key; 127 | if (SvIOK(value)) { 128 | args->args[args_index].type = GRPC_ARG_INTEGER; 129 | args->args[args_index].value.integer = SvIV(value); 130 | args->args[args_index].value.string = NULL; 131 | } else { 132 | args->args[args_index].type = GRPC_ARG_STRING; 133 | args->args[args_index].value.string = SvPV_nolen(value); 134 | } 135 | } else { 136 | croak("args values must be int or string"); 137 | } 138 | args_index++; 139 | } 140 | } 141 | 142 | /* Creates and returns a perl hash object with the data in a 143 | * grpc_metadata_array. Returns NULL on failure */ 144 | HV* grpc_parse_metadata_array(grpc_metadata_array *metadata_array) { 145 | HV* hash = newHV(); 146 | grpc_metadata *elements = metadata_array->metadata; 147 | grpc_metadata *elem; 148 | 149 | // hash->{key} = [val] 150 | int i; 151 | int count = metadata_array->count; 152 | SV *inner_value; 153 | #if defined(GRPC_VERSION_1_2) 154 | SV *key; 155 | HE *temp_fetch; 156 | #else 157 | SV **temp_fetch; 158 | #endif 159 | for (i = 0; i < count; i++) { 160 | elem = &elements[i]; 161 | #if defined(GRPC_VERSION_1_2) 162 | key = sv_2mortal(grpc_slice_to_sv(elem->key)); 163 | temp_fetch = hv_fetch_ent(hash, key, 0, 0); 164 | inner_value = temp_fetch ? HeVAL(temp_fetch) : NULL; 165 | #else 166 | temp_fetch = hv_fetch(hash, elem->key, strlen(elem->key), 0); 167 | inner_value = temp_fetch ? *temp_fetch : NULL; 168 | #endif 169 | if (inner_value) { 170 | if(!SvROK(inner_value)) { 171 | croak("Metadata hash somehow contains wrong types."); 172 | return NULL; 173 | } 174 | av_push( (AV*)SvRV(inner_value), grpc_slice_or_buffer_length_to_sv(elem->value) ); 175 | } else { 176 | AV* av = newAV(); 177 | av_push( av, grpc_slice_or_buffer_length_to_sv(elem->value) ); 178 | #if defined(GRPC_VERSION_1_2) 179 | hv_store_ent(hash,key,newRV_inc((SV*)av),0); 180 | #else 181 | hv_store(hash,elem->key,strlen(elem->key),newRV_inc((SV*)av),0); 182 | #endif 183 | } 184 | } 185 | 186 | return hash; 187 | } 188 | 189 | /* Populates a grpc_metadata_array with the data in a perl hash object. 190 | Returns TRUE on success and FALSE on failure. 191 | Shouldn't use croak, as it is used in grpc callbacks, and everything explosed with a segfault. 192 | */ 193 | bool create_metadata_array(HV *hash, grpc_metadata_array *metadata) { 194 | // First thing to do is to make grpc_metadata_array_destroy() safe no matter what 195 | grpc_metadata_array_init(metadata); 196 | metadata->capacity = 0; 197 | metadata->metadata = NULL; 198 | 199 | // handle hashes 200 | if (SvTYPE(hash)!=SVt_PVHV) { 201 | warn("Expected hash for create_metadata_array() args"); 202 | return FALSE; 203 | } 204 | 205 | int i; 206 | char* key; 207 | I32 keylen; 208 | SV* value; 209 | 210 | // count items in hash 211 | metadata->capacity = 0; 212 | hv_iterinit(hash); 213 | while((value = hv_iternextsv(hash,&key,&keylen))!=NULL) { 214 | if (!SvROK(value)) { 215 | warn("expected array ref in metadata value %s, ignoring...",key); 216 | continue; 217 | } 218 | value = SvRV(value); 219 | if (SvTYPE(value)!=SVt_PVAV) { 220 | warn("expected array ref in metadata value %s, ignoring...",key); 221 | continue; 222 | } 223 | metadata->capacity += av_len((AV*)value)+1; 224 | } 225 | 226 | if(metadata->capacity > 0) { 227 | metadata->metadata = gpr_malloc(metadata->capacity * sizeof(grpc_metadata)); 228 | } else { 229 | metadata->metadata = NULL; 230 | return TRUE; 231 | } 232 | 233 | metadata->count = 0; 234 | hv_iterinit(hash); 235 | while((value = hv_iternextsv(hash,&key,&keylen))!=NULL) { 236 | if (!SvROK(value)) { 237 | //warn("expected array ref in metadata value %s, ignoring...",key); 238 | continue; 239 | } 240 | value = SvRV(value); 241 | if (SvTYPE(value)!=SVt_PVAV) { 242 | //warn("expected array ref in metadata value %s, ignoring...",key); 243 | continue; 244 | } 245 | for(i=0;imetadata[metadata->count].key = grpc_slice_from_copied_string(key); 250 | metadata->metadata[metadata->count].value = 251 | grpc_slice_from_sv(*inner_value); 252 | #else 253 | metadata->metadata[metadata->count].key = key; 254 | metadata->metadata[metadata->count].value = 255 | strdup(SvPV(*inner_value,metadata->metadata[metadata->count].value_length)); 256 | #endif 257 | metadata->count += 1; 258 | } else { 259 | warn("args values must be int or string"); 260 | return FALSE; 261 | } 262 | } 263 | } 264 | 265 | return TRUE; 266 | } 267 | 268 | /* Callback function for plugin creds API */ 269 | #if defined(GRPC_VERSION_1_7) 270 | int plugin_get_metadata(void *ptr, grpc_auth_metadata_context context, 271 | grpc_credentials_plugin_metadata_cb cb, 272 | void *user_data, 273 | grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX], 274 | size_t *num_creds_md, grpc_status_code *status, 275 | const char **error_details) { 276 | #else 277 | void plugin_get_metadata(void *ptr, grpc_auth_metadata_context context, 278 | grpc_credentials_plugin_metadata_cb cb, 279 | void *user_data) { 280 | #endif 281 | static char error_details_buf[1024]; 282 | 283 | SV* callback = (SV*)ptr; 284 | SV* err_tmp; 285 | int has_error = FALSE; 286 | char *error_details_out = NULL; 287 | grpc_metadata_array metadata; 288 | 289 | dSP; 290 | ENTER; 291 | 292 | HV* hash = newHV(); 293 | hv_stores(hash,"service_url", newSVpv(context.service_url,0)); 294 | hv_stores(hash,"method_name", newSVpv(context.method_name,0)); 295 | 296 | SAVETMPS; 297 | PUSHMARK(sp); 298 | XPUSHs(sv_2mortal((SV*)newRV_noinc((SV*)hash))); 299 | PUTBACK; 300 | int count = perl_call_sv(callback, G_SCALAR|G_EVAL); 301 | SPAGAIN; 302 | 303 | err_tmp = ERRSV; 304 | if (SvTRUE(err_tmp)) { 305 | has_error = TRUE; 306 | my_strlcpy(error_details_buf, SvPV_nolen(err_tmp), sizeof(error_details_buf)); 307 | error_details_out = error_details_buf; 308 | POPs; 309 | } else if (count!=1) { 310 | has_error = TRUE; 311 | error_details_out = "callback returned more/less than 1 value"; 312 | POPs; 313 | } else { 314 | SV* retval = POPs; 315 | 316 | if (SvROK(retval)) { // create_metadata_array() segfaults without this check 317 | if (!create_metadata_array((HV*)SvRV(retval), &metadata)) { 318 | has_error = TRUE; 319 | error_details_out = "callback returned invalid metadata"; 320 | grpc_metadata_array_destroy(&metadata); 321 | } 322 | } else { 323 | has_error = TRUE; 324 | error_details_out = "calback returned non-reference"; 325 | } 326 | } 327 | 328 | PUTBACK; 329 | FREETMPS; 330 | LEAVE; 331 | 332 | // TODO Documentation says that for GRPC_VERSION_1_7-style API a 333 | // callback MUST be called from a different thread. This doesn't 334 | // crash right now, but definitely should be fixed. 335 | // GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX is 4, so we can return 336 | // up to that amount of metadata items synchronously. 337 | if ( has_error ) { 338 | grpc_status_code code = GRPC_STATUS_INVALID_ARGUMENT; 339 | cb(user_data, NULL, 0, code, error_details_out); 340 | } else { 341 | grpc_status_code code = GRPC_STATUS_OK; 342 | cb(user_data, metadata.metadata, metadata.count, code, NULL); 343 | } 344 | 345 | #if defined(GRPC_VERSION_1_7) 346 | return 0; 347 | #endif 348 | } 349 | 350 | /* Cleanup function for plugin creds API */ 351 | void plugin_destroy_state(void *ptr) { 352 | SV *state = (SV *)ptr; 353 | SvREFCNT_dec(state); 354 | } 355 | 356 | #if defined(GRPC_VERSION_1_2) 357 | SV *grpc_slice_to_sv(grpc_slice slice) { 358 | char *slice_str = grpc_slice_to_c_string(slice); 359 | SV *sv = newSVpv(slice_str, GRPC_SLICE_LENGTH(slice)); 360 | gpr_free(slice_str); 361 | return sv; 362 | } 363 | 364 | grpc_slice grpc_slice_from_sv(SV *sv) { 365 | STRLEN length; 366 | const char *buffer = SvPV(sv, length); 367 | return grpc_slice_from_copied_buffer(buffer, length); 368 | } 369 | #endif 370 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | #ifndef GRPC_PERL_UTIL_H 2 | #define GRPC_PERL_UTIL_H 3 | 4 | #include "EXTERN.h" 5 | #include "perl.h" 6 | #include "XSUB.h" 7 | #include "ppport.h" 8 | 9 | #include 10 | #include 11 | #if defined(GRPC_VERSION_1_1) 12 | #include 13 | #endif 14 | 15 | grpc_byte_buffer *string_to_byte_buffer(char *string, size_t length); 16 | 17 | void byte_buffer_to_string(grpc_byte_buffer *buffer, char **out_string, 18 | size_t *out_length); 19 | 20 | void grpc_perl_init(); 21 | void grpc_perl_destroy(); 22 | 23 | /* The global completion queue for all operations */ 24 | extern grpc_completion_queue *completion_queue; 25 | 26 | /* Initializes the completion queue */ 27 | void grpc_perl_init_completion_queue(); 28 | 29 | /* Shut down the completion queue */ 30 | void grpc_perl_shutdown_completion_queue(); 31 | 32 | void perl_grpc_read_args_array(HV *hash, grpc_channel_args *args); 33 | HV* grpc_parse_metadata_array(grpc_metadata_array *metadata_array); 34 | bool create_metadata_array(HV *hash, grpc_metadata_array *metadata); 35 | 36 | #if defined(GRPC_VERSION_1_7) 37 | int plugin_get_metadata(void *ptr, grpc_auth_metadata_context context, 38 | grpc_credentials_plugin_metadata_cb cb, 39 | void *user_data, 40 | grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX], 41 | size_t *num_creds_md, grpc_status_code *status, 42 | const char **error_details); 43 | #else 44 | void plugin_get_metadata(void *ptr, grpc_auth_metadata_context context, 45 | grpc_credentials_plugin_metadata_cb cb, 46 | void *user_data); 47 | #endif 48 | 49 | void plugin_destroy_state(void *ptr); 50 | 51 | #if defined(GRPC_VERSION_1_2) 52 | SV *grpc_slice_to_sv(grpc_slice slice); 53 | grpc_slice grpc_slice_from_sv(SV *sv); 54 | #endif 55 | 56 | #if defined(GRPC_VERSION_1_2) 57 | #define grpc_slice_or_string_to_sv(slice) grpc_slice_to_sv((slice)) 58 | #define grpc_slice_or_buffer_length_to_sv(slice) grpc_slice_to_sv((slice)) 59 | #else 60 | #define grpc_slice_or_string_to_sv(string) newSVpvn((string), strlen(string)) 61 | #define grpc_slice_or_buffer_length_to_sv(buffer) newSVpvn((buffer), (buffer##_length)) 62 | #endif 63 | 64 | #endif 65 | --------------------------------------------------------------------------------