├── .gitignore ├── .travis.yml ├── AUTHORS ├── LICENSE ├── README.md ├── alloptions_test.go ├── examples ├── alloptions │ ├── alloptions.nrpc.go │ ├── alloptions.pb.go │ ├── alloptions.proto │ ├── alloptions_test.go │ ├── main.go │ └── testrunner_test.go ├── helloworld │ ├── greeter_client │ │ └── main.go │ ├── greeter_server │ │ ├── main.go │ │ ├── main_test.go │ │ └── testrunner_test.go │ └── helloworld │ │ ├── helloworld.go │ │ ├── helloworld.nrpc.go │ │ ├── helloworld.pb.go │ │ └── helloworld.proto ├── metrics_helloworld │ ├── helloworld │ │ ├── helloworld.go │ │ ├── helloworld.nrpc.go │ │ ├── helloworld.pb.go │ │ └── helloworld.proto │ ├── metrics_greeter_client │ │ └── main.go │ └── metrics_greeter_server │ │ └── main.go └── nooption │ ├── nooption.go │ ├── nooption.nrpc.go │ ├── nooption.pb.go │ └── nooption.proto ├── go.mod ├── go.sum ├── helloworld_test.go ├── nrpc.go ├── nrpc.pb.go ├── nrpc.proto ├── nrpc_test.go ├── nrpc_test.proto ├── nrpcpb_test.go ├── protoc-gen-nrpc ├── main.go └── tmpl.go └── testrunner_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | examples/helloworld/greeter_client/greeter_client 2 | examples/helloworld/greeter_server/greeter_server 3 | examples/alloptions/alloptions 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.13 5 | 6 | before_script: 7 | - curl -sSL https://github.com/google/protobuf/releases/download/v3.12.1/protoc-3.12.1-linux-x86_64.zip -o protoc.zip 8 | - sudo unzip -d /usr/local protoc.zip 9 | - sudo chmod a+x /usr/local/bin/protoc 10 | - sudo chmod -R a+rx /usr/local/include/google 11 | - go get google.golang.org/protobuf/cmd/protoc-gen-go 12 | - go install google.golang.org/protobuf/cmd/protoc-gen-go 13 | 14 | script: 15 | - go test ./... 16 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | RapidLoop, Inc. 2 | Christophe de Vienne, Orus.io 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nRPC 2 | 3 | [![Build Status](https://travis-ci.org/nats-rpc/nrpc.svg?branch=master)](https://travis-ci.org/nats-rpc/nrpc) 4 | 5 | nRPC is an RPC framework like [gRPC](https://grpc.io/), but for 6 | [NATS](https://nats.io/). 7 | 8 | It can generate a Go client and server from the same .proto file that you'd 9 | use to generate gRPC clients and servers. The server is generated as a NATS 10 | [MsgHandler](https://godoc.org/github.com/nats-io/nats.go#MsgHandler). 11 | 12 | The [Specifications](https://github.com/nats-rpc/nrpc/wiki/Specifications) 13 | describes how nRPC translates protobuf services and methods into NATS patterns. 14 | 15 | ## Why NATS? 16 | 17 | Doing RPC over NATS' 18 | [request-response model](http://nats.io/documentation/concepts/nats-req-rep/) 19 | has some advantages over a gRPC model: 20 | 21 | - **Minimal service discovery**: The clients and servers only need to know the 22 | endpoints of a NATS cluster. The clients do not need to discover the 23 | endpoints of individual services they depend on. 24 | - **Load balancing without load balancers**: Stateless microservices can be 25 | hosted redundantly and connected to the same NATS cluster. The incoming 26 | requests can then be random-routed among these using NATS 27 | [queueing](http://nats.io/documentation/concepts/nats-queueing/). There is 28 | no need to setup a (high availability) load balancer per microservice. 29 | 30 | The lunch is not always free, however. At scale, the NATS cluster itself can 31 | become a bottleneck. Features of gRPC like streaming and advanced auth are not 32 | available. 33 | 34 | Still, NATS - and nRPC - offer much lower operational complexity if your 35 | scale and requirements fit. 36 | 37 | At RapidLoop, we use this model for our [OpsDash](https://www.opsdash.com) 38 | SaaS product in production and are quite happy with it. nRPC is the third 39 | iteration of an internal library. 40 | 41 | ## Overview 42 | 43 | nRPC comes with a protobuf compiler plugin `protoc-gen-nrpc`, which generates 44 | Go code from a .proto file. 45 | 46 | Given a .proto file like [helloworld.proto](https://github.com/grpc/grpc-go/blob/master/examples/helloworld/helloworld/helloworld.proto), the usage is like this: 47 | 48 | ``` 49 | $ ls 50 | helloworld.proto 51 | $ protoc --go_out=. --nrpc_out=. helloworld.proto 52 | $ ls 53 | helloworld.nrpc.go helloworld.pb.go helloworld.proto 54 | ``` 55 | 56 | The .pb.go file, which contains the definitions for the message classes, is 57 | generated by the standard Go plugin for protoc. The .nrpc.go file, which 58 | contains the definitions for a client, a server interface, and a NATS handler 59 | is generated by the nRPC plugin. 60 | 61 | Have a look at the generated and example files: 62 | 63 | - the service definition [helloworld.proto](https://github.com/nats-rpc/nrpc/tree/master/examples/helloworld/helloworld/helloworld.proto) 64 | - the generated nrpc go file [helloworld.nrpc.go](https://github.com/nats-rpc/nrpc/tree/master/examples/helloworld/helloworld/helloworld.nrpc.go) 65 | - an example server [greeter_server/main.go](https://github.com/nats-rpc/nrpc/tree/master/examples/helloworld/greeter_server/main.go) 66 | - an example client [greeter_client/main.go](https://github.com/nats-rpc/nrpc/tree/master/examples/helloworld/greeter_client/main.go) 67 | 68 | ### How It Works 69 | 70 | The .proto file defines messages (like HelloRequest and HelloReply in the 71 | example) and services (Greeter) that have methods (SayHello). 72 | 73 | The messages are generated as Go structs by the regular Go protobuf compiler 74 | plugin and gets written out to \*.pb.go files. 75 | 76 | For the rest, nRPC generates three logical pieces. 77 | 78 | The first is a Go interface type (GreeterServer) which your actual 79 | microservice code should implement: 80 | 81 | ``` 82 | // This is what is contained in the .proto file 83 | service Greeter { 84 | rpc SayHello (HelloRequest) returns (HelloReply) {} 85 | } 86 | 87 | // This is the generated interface which you've to implement 88 | type GreeterServer interface { 89 | SayHello(ctx context.Context, req HelloRequest) (resp HelloReply, err error) 90 | } 91 | ``` 92 | 93 | The second is a client (GreeterClient struct). This struct has 94 | methods with appropriate types, that correspond to the service definition. The 95 | client code will marshal and wrap the request object (HelloRequest) and do a 96 | NATS `Request`. 97 | 98 | ``` 99 | // The client is associated with a NATS connection. 100 | func NewGreeterClient(nc *nats.Conn) *GreeterClient {...} 101 | 102 | // And has properly typed methods that will marshal and perform a NATS request. 103 | func (c *GreeterClient) SayHello(req HelloRequest) (resp HelloReply, err error) {...} 104 | ``` 105 | 106 | The third and final piece is the handler (GreeterHandler). Given a NATS 107 | connection and a server implementation, it can accept NATS requests in the 108 | format sent by the client above. It should be installed as a message handler for 109 | a particular NATS subject (defaults to the name of the service) using the 110 | NATS Subscribe() or QueueSubscribe() methods. It will invoke the appropriate 111 | method of the GreeterServer interface upon receiving the appropriate request. 112 | 113 | ``` 114 | // A handler is associated with a NATS connection and a server implementation. 115 | func NewGreeterHandler(ctx context.Context, nc *nats.Conn, s GreeterServer) *GreeterHandler {...} 116 | 117 | // It has a method that can (should) be used as a NATS message handler. 118 | func (h *GreeterHandler) Handler(msg *nats.Msg) {...} 119 | ``` 120 | 121 | Standing up a microservice involves: 122 | 123 | - writing the .proto service definition file 124 | - generating the \*.pb.go and \*.nrpc.go files 125 | - implementing the server interface 126 | - writing a main app that will connect to NATS and start the handler ([see 127 | example](https://github.com/nats-rpc/nrpc/blob/master/examples/helloworld/greeter_server/main.go)) 128 | 129 | To call the service: 130 | 131 | - import the package that contains the generated *.nrpc.go files 132 | - in the client code, connect to NATS 133 | - create a Caller object and call the methods as necessary ([see example](https://github.com/nats-rpc/nrpc/blob/master/examples/helloworld/greeter_client/main.go)) 134 | 135 | ## Features 136 | 137 | The following wiki pages describe nRPC features in more detail: 138 | 139 | - [Load Balancing](https://github.com/nats-rpc/nrpc/wiki/Load-Balancing) 140 | - [Metrics Instrumentation](https://github.com/nats-rpc/nrpc/wiki/Metrics-Instrumentation) 141 | using Prometheus 142 | 143 | ## Installation 144 | 145 | nRPC needs Go 1.11 or higher. $GOPATH/bin needs to be in $PATH for the protoc 146 | invocation to work. To generate code, you need the protobuf compiler (which 147 | you can install from [here](https://github.com/google/protobuf/releases)) 148 | and the nRPC protoc plugin. 149 | 150 | To install the nRPC protoc plugin: 151 | 152 | ``` 153 | $ go install github.com/nats-rpc/nrpc/protoc-gen-nrpc@latest 154 | ``` 155 | 156 | To build and run the example greeter_server: 157 | 158 | ``` 159 | $ go install github.com/nats-rpc/nrpc/examples/helloworld/greeter_server@latest 160 | $ greeter_server 161 | server is running, ^C quits. 162 | ``` 163 | 164 | To build and run the example greeter_client: 165 | 166 | ``` 167 | $ go install github.com/nats-rpc/nrpc/examples/helloworld/greeter_client@latest 168 | $ greeter_client 169 | Greeting: Hello world 170 | $ 171 | ``` 172 | 173 | ## Documentation 174 | 175 | To learn more about describing gRPC services using .proto files, see [here](https://grpc.io/docs/guides/concepts.html). 176 | To learn more about NATS, start with their [website](https://nats.io/). To 177 | learn more about nRPC, um, read the source code. 178 | 179 | ## Status 180 | 181 | nRPC is in alpha. This means that it will work, but APIs may change without 182 | notice. 183 | 184 | Currently there is support only for Go clients and servers. 185 | 186 | Built by RapidLoop. Released under Apache 2.0 license. 187 | -------------------------------------------------------------------------------- /alloptions_test.go: -------------------------------------------------------------------------------- 1 | package nrpc 2 | 3 | import ( 4 | //"bytes" 5 | //"os" 6 | "os/exec" 7 | "testing" 8 | //"time" 9 | ) 10 | 11 | func TestAllOptionsExample(t *testing.T) { 12 | // make sure protoc-gen-nrpc is up to date 13 | installGenRPC := exec.Command("go", "install", "./protoc-gen-nrpc") 14 | if out, err := installGenRPC.CombinedOutput(); err != nil { 15 | t.Fatal("Install protoc-gen-nrpc failed", err, ":\n", string(out)) 16 | } 17 | // generate the sources 18 | generate := exec.Command("go", "generate", "./examples/alloptions") 19 | if out, err := generate.CombinedOutput(); err != nil { 20 | t.Fatal("Generate failed", err, ":\n", string(out)) 21 | } 22 | // build 23 | build := exec.Command("go", "build", 24 | "-o", "./examples/alloptions/alloptions", 25 | "./examples/alloptions") 26 | if out, err := build.CombinedOutput(); err != nil { 27 | t.Fatal("Buid failed", err, string(out)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/alloptions/alloptions.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.29.0 4 | // protoc v4.22.2 5 | // source: alloptions.proto 6 | 7 | package main 8 | 9 | import ( 10 | nrpc "github.com/nats-rpc/nrpc" 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | type StringArg struct { 25 | state protoimpl.MessageState 26 | sizeCache protoimpl.SizeCache 27 | unknownFields protoimpl.UnknownFields 28 | 29 | Arg1 string `protobuf:"bytes,1,opt,name=arg1,proto3" json:"arg1,omitempty"` 30 | } 31 | 32 | func (x *StringArg) Reset() { 33 | *x = StringArg{} 34 | if protoimpl.UnsafeEnabled { 35 | mi := &file_alloptions_proto_msgTypes[0] 36 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 37 | ms.StoreMessageInfo(mi) 38 | } 39 | } 40 | 41 | func (x *StringArg) String() string { 42 | return protoimpl.X.MessageStringOf(x) 43 | } 44 | 45 | func (*StringArg) ProtoMessage() {} 46 | 47 | func (x *StringArg) ProtoReflect() protoreflect.Message { 48 | mi := &file_alloptions_proto_msgTypes[0] 49 | if protoimpl.UnsafeEnabled && x != nil { 50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 51 | if ms.LoadMessageInfo() == nil { 52 | ms.StoreMessageInfo(mi) 53 | } 54 | return ms 55 | } 56 | return mi.MessageOf(x) 57 | } 58 | 59 | // Deprecated: Use StringArg.ProtoReflect.Descriptor instead. 60 | func (*StringArg) Descriptor() ([]byte, []int) { 61 | return file_alloptions_proto_rawDescGZIP(), []int{0} 62 | } 63 | 64 | func (x *StringArg) GetArg1() string { 65 | if x != nil { 66 | return x.Arg1 67 | } 68 | return "" 69 | } 70 | 71 | type SimpleStringReply struct { 72 | state protoimpl.MessageState 73 | sizeCache protoimpl.SizeCache 74 | unknownFields protoimpl.UnknownFields 75 | 76 | Reply string `protobuf:"bytes,1,opt,name=reply,proto3" json:"reply,omitempty"` 77 | } 78 | 79 | func (x *SimpleStringReply) Reset() { 80 | *x = SimpleStringReply{} 81 | if protoimpl.UnsafeEnabled { 82 | mi := &file_alloptions_proto_msgTypes[1] 83 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 84 | ms.StoreMessageInfo(mi) 85 | } 86 | } 87 | 88 | func (x *SimpleStringReply) String() string { 89 | return protoimpl.X.MessageStringOf(x) 90 | } 91 | 92 | func (*SimpleStringReply) ProtoMessage() {} 93 | 94 | func (x *SimpleStringReply) ProtoReflect() protoreflect.Message { 95 | mi := &file_alloptions_proto_msgTypes[1] 96 | if protoimpl.UnsafeEnabled && x != nil { 97 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 98 | if ms.LoadMessageInfo() == nil { 99 | ms.StoreMessageInfo(mi) 100 | } 101 | return ms 102 | } 103 | return mi.MessageOf(x) 104 | } 105 | 106 | // Deprecated: Use SimpleStringReply.ProtoReflect.Descriptor instead. 107 | func (*SimpleStringReply) Descriptor() ([]byte, []int) { 108 | return file_alloptions_proto_rawDescGZIP(), []int{1} 109 | } 110 | 111 | func (x *SimpleStringReply) GetReply() string { 112 | if x != nil { 113 | return x.Reply 114 | } 115 | return "" 116 | } 117 | 118 | var File_alloptions_proto protoreflect.FileDescriptor 119 | 120 | var file_alloptions_proto_rawDesc = []byte{ 121 | 0x0a, 0x10, 0x61, 0x6c, 0x6c, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 122 | 0x74, 0x6f, 0x12, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x1a, 0x0f, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x6e, 123 | 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1f, 0x0a, 0x09, 0x53, 0x74, 0x72, 124 | 0x69, 0x6e, 0x67, 0x41, 0x72, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x72, 0x67, 0x31, 0x18, 0x01, 125 | 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x67, 0x31, 0x22, 0x29, 0x0a, 0x11, 0x53, 0x69, 126 | 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 127 | 0x14, 0x0a, 0x05, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 128 | 0x72, 0x65, 0x70, 0x6c, 0x79, 0x32, 0xeb, 0x02, 0x0a, 0x10, 0x53, 0x76, 0x63, 0x43, 0x75, 0x73, 129 | 0x74, 0x6f, 0x6d, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x52, 0x0a, 0x0d, 0x4d, 0x74, 130 | 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x0f, 0x2e, 0x6d, 0x61, 131 | 0x69, 0x6e, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x41, 0x72, 0x67, 0x1a, 0x17, 0x2e, 0x6d, 132 | 0x61, 0x69, 0x6e, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 133 | 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x17, 0x82, 0xb2, 0x19, 0x0f, 0x6d, 0x74, 0x5f, 0x73, 0x69, 134 | 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x98, 0xb2, 0x19, 0x01, 0x12, 0x2c, 135 | 0x0a, 0x0b, 0x4d, 0x74, 0x56, 0x6f, 0x69, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x0f, 0x2e, 136 | 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x41, 0x72, 0x67, 0x1a, 0x0a, 137 | 0x2e, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x6f, 0x69, 0x64, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x0b, 138 | 0x4d, 0x74, 0x4e, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0f, 0x2e, 0x6e, 0x72, 139 | 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6d, 140 | 0x61, 0x69, 0x6e, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 141 | 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0f, 0x4d, 0x74, 0x53, 0x74, 0x72, 142 | 0x65, 0x61, 0x6d, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x0f, 0x2e, 0x6d, 0x61, 0x69, 143 | 0x6e, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x41, 0x72, 0x67, 0x1a, 0x17, 0x2e, 0x6d, 0x61, 144 | 0x69, 0x6e, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 145 | 0x65, 0x70, 0x6c, 0x79, 0x22, 0x04, 0x90, 0xb2, 0x19, 0x01, 0x12, 0x43, 0x0a, 0x16, 0x4d, 0x74, 146 | 0x56, 0x6f, 0x69, 0x64, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x65, 0x64, 0x52, 147 | 0x65, 0x70, 0x6c, 0x79, 0x12, 0x0a, 0x2e, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x6f, 0x69, 0x64, 148 | 0x1a, 0x17, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x74, 149 | 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x04, 0x90, 0xb2, 0x19, 0x01, 0x1a, 150 | 0x12, 0xc2, 0xf3, 0x18, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x73, 0x75, 0x62, 0x6a, 151 | 0x65, 0x63, 0x74, 0x32, 0xbc, 0x02, 0x0a, 0x10, 0x53, 0x76, 0x63, 0x53, 0x75, 0x62, 0x6a, 0x65, 152 | 0x63, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x4a, 0x0a, 0x13, 0x4d, 0x74, 0x57, 0x69, 153 | 0x74, 0x68, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 154 | 0x0a, 0x2e, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x6f, 0x69, 0x64, 0x1a, 0x17, 0x2e, 0x6d, 0x61, 155 | 0x69, 0x6e, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 156 | 0x65, 0x70, 0x6c, 0x79, 0x22, 0x0e, 0x8a, 0xb2, 0x19, 0x03, 0x6d, 0x70, 0x31, 0x8a, 0xb2, 0x19, 157 | 0x03, 0x6d, 0x70, 0x32, 0x12, 0x5b, 0x0a, 0x20, 0x4d, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 158 | 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x57, 0x69, 0x74, 0x68, 0x53, 0x75, 0x62, 0x6a, 0x65, 159 | 0x63, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x0a, 0x2e, 0x6e, 0x72, 0x70, 0x63, 0x2e, 160 | 0x56, 0x6f, 0x69, 0x64, 0x1a, 0x17, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x53, 0x69, 0x6d, 0x70, 161 | 0x6c, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x12, 0x8a, 162 | 0xb2, 0x19, 0x03, 0x6d, 0x70, 0x31, 0x8a, 0xb2, 0x19, 0x03, 0x6d, 0x70, 0x32, 0x90, 0xb2, 0x19, 163 | 0x01, 0x12, 0x28, 0x0a, 0x09, 0x4d, 0x74, 0x4e, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x0a, 164 | 0x2e, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x6f, 0x69, 0x64, 0x1a, 0x0d, 0x2e, 0x6e, 0x72, 0x70, 165 | 0x63, 0x2e, 0x4e, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x12, 0x4d, 166 | 0x74, 0x4e, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x57, 0x50, 0x61, 0x72, 0x61, 0x6d, 167 | 0x73, 0x12, 0x0f, 0x2e, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 168 | 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 169 | 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x07, 0x8a, 0xb2, 0x19, 170 | 0x03, 0x6d, 0x70, 0x31, 0x1a, 0x0c, 0xca, 0xf3, 0x18, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 171 | 0x69, 0x64, 0x32, 0x4d, 0x0a, 0x10, 0x4e, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x53, 172 | 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x4d, 0x74, 0x4e, 0x6f, 0x52, 0x65, 173 | 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0f, 0x2e, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x52, 174 | 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x53, 0x69, 175 | 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 176 | 0x00, 0x42, 0x24, 0x82, 0xb5, 0x18, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x8a, 0xb5, 0x18, 0x08, 0x69, 177 | 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x90, 0xb5, 0x18, 0x01, 0x98, 0xb5, 0x18, 0x01, 0x5a, 178 | 0x06, 0x2e, 0x3b, 0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 179 | } 180 | 181 | var ( 182 | file_alloptions_proto_rawDescOnce sync.Once 183 | file_alloptions_proto_rawDescData = file_alloptions_proto_rawDesc 184 | ) 185 | 186 | func file_alloptions_proto_rawDescGZIP() []byte { 187 | file_alloptions_proto_rawDescOnce.Do(func() { 188 | file_alloptions_proto_rawDescData = protoimpl.X.CompressGZIP(file_alloptions_proto_rawDescData) 189 | }) 190 | return file_alloptions_proto_rawDescData 191 | } 192 | 193 | var file_alloptions_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 194 | var file_alloptions_proto_goTypes = []interface{}{ 195 | (*StringArg)(nil), // 0: main.StringArg 196 | (*SimpleStringReply)(nil), // 1: main.SimpleStringReply 197 | (*nrpc.NoRequest)(nil), // 2: nrpc.NoRequest 198 | (*nrpc.Void)(nil), // 3: nrpc.Void 199 | (*nrpc.NoReply)(nil), // 4: nrpc.NoReply 200 | } 201 | var file_alloptions_proto_depIdxs = []int32{ 202 | 0, // 0: main.SvcCustomSubject.MtSimpleReply:input_type -> main.StringArg 203 | 0, // 1: main.SvcCustomSubject.MtVoidReply:input_type -> main.StringArg 204 | 2, // 2: main.SvcCustomSubject.MtNoRequest:input_type -> nrpc.NoRequest 205 | 0, // 3: main.SvcCustomSubject.MtStreamedReply:input_type -> main.StringArg 206 | 3, // 4: main.SvcCustomSubject.MtVoidReqStreamedReply:input_type -> nrpc.Void 207 | 3, // 5: main.SvcSubjectParams.MtWithSubjectParams:input_type -> nrpc.Void 208 | 3, // 6: main.SvcSubjectParams.MtStreamedReplyWithSubjectParams:input_type -> nrpc.Void 209 | 3, // 7: main.SvcSubjectParams.MtNoReply:input_type -> nrpc.Void 210 | 2, // 8: main.SvcSubjectParams.MtNoRequestWParams:input_type -> nrpc.NoRequest 211 | 2, // 9: main.NoRequestService.MtNoRequest:input_type -> nrpc.NoRequest 212 | 1, // 10: main.SvcCustomSubject.MtSimpleReply:output_type -> main.SimpleStringReply 213 | 3, // 11: main.SvcCustomSubject.MtVoidReply:output_type -> nrpc.Void 214 | 1, // 12: main.SvcCustomSubject.MtNoRequest:output_type -> main.SimpleStringReply 215 | 1, // 13: main.SvcCustomSubject.MtStreamedReply:output_type -> main.SimpleStringReply 216 | 1, // 14: main.SvcCustomSubject.MtVoidReqStreamedReply:output_type -> main.SimpleStringReply 217 | 1, // 15: main.SvcSubjectParams.MtWithSubjectParams:output_type -> main.SimpleStringReply 218 | 1, // 16: main.SvcSubjectParams.MtStreamedReplyWithSubjectParams:output_type -> main.SimpleStringReply 219 | 4, // 17: main.SvcSubjectParams.MtNoReply:output_type -> nrpc.NoReply 220 | 1, // 18: main.SvcSubjectParams.MtNoRequestWParams:output_type -> main.SimpleStringReply 221 | 1, // 19: main.NoRequestService.MtNoRequest:output_type -> main.SimpleStringReply 222 | 10, // [10:20] is the sub-list for method output_type 223 | 0, // [0:10] is the sub-list for method input_type 224 | 0, // [0:0] is the sub-list for extension type_name 225 | 0, // [0:0] is the sub-list for extension extendee 226 | 0, // [0:0] is the sub-list for field type_name 227 | } 228 | 229 | func init() { file_alloptions_proto_init() } 230 | func file_alloptions_proto_init() { 231 | if File_alloptions_proto != nil { 232 | return 233 | } 234 | if !protoimpl.UnsafeEnabled { 235 | file_alloptions_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 236 | switch v := v.(*StringArg); i { 237 | case 0: 238 | return &v.state 239 | case 1: 240 | return &v.sizeCache 241 | case 2: 242 | return &v.unknownFields 243 | default: 244 | return nil 245 | } 246 | } 247 | file_alloptions_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 248 | switch v := v.(*SimpleStringReply); i { 249 | case 0: 250 | return &v.state 251 | case 1: 252 | return &v.sizeCache 253 | case 2: 254 | return &v.unknownFields 255 | default: 256 | return nil 257 | } 258 | } 259 | } 260 | type x struct{} 261 | out := protoimpl.TypeBuilder{ 262 | File: protoimpl.DescBuilder{ 263 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 264 | RawDescriptor: file_alloptions_proto_rawDesc, 265 | NumEnums: 0, 266 | NumMessages: 2, 267 | NumExtensions: 0, 268 | NumServices: 3, 269 | }, 270 | GoTypes: file_alloptions_proto_goTypes, 271 | DependencyIndexes: file_alloptions_proto_depIdxs, 272 | MessageInfos: file_alloptions_proto_msgTypes, 273 | }.Build() 274 | File_alloptions_proto = out.File 275 | file_alloptions_proto_rawDesc = nil 276 | file_alloptions_proto_goTypes = nil 277 | file_alloptions_proto_depIdxs = nil 278 | } 279 | -------------------------------------------------------------------------------- /examples/alloptions/alloptions.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package main; 4 | 5 | option go_package = ".;main"; 6 | 7 | import "nrpc/nrpc.proto"; 8 | 9 | option (nrpc.packageSubject) = "root"; 10 | option (nrpc.packageSubjectParams) = "instance"; 11 | 12 | option (nrpc.serviceSubjectRule) = TOLOWER; 13 | option (nrpc.methodSubjectRule) = TOLOWER; 14 | 15 | service SvcCustomSubject { 16 | option (nrpc.serviceSubject) = 'custom_subject'; 17 | 18 | rpc MtSimpleReply(StringArg) returns (SimpleStringReply) { 19 | option (nrpc.methodSubject) = "mt_simple_reply"; 20 | option (nrpc.pollingEnabled) = true; 21 | } 22 | rpc MtVoidReply(StringArg) returns (nrpc.Void) {} 23 | rpc MtNoRequest(nrpc.NoRequest) returns (SimpleStringReply) {} 24 | 25 | rpc MtStreamedReply(StringArg) returns (SimpleStringReply) { 26 | option (nrpc.streamedReply) = true; 27 | } 28 | rpc MtVoidReqStreamedReply(nrpc.Void) returns (SimpleStringReply) { 29 | option (nrpc.streamedReply) = true; 30 | } 31 | } 32 | 33 | service SvcSubjectParams { 34 | option (nrpc.serviceSubjectParams) = "clientid"; 35 | 36 | rpc MtWithSubjectParams(nrpc.Void) returns (SimpleStringReply) { 37 | option (nrpc.methodSubjectParams) = "mp1"; 38 | option (nrpc.methodSubjectParams) = "mp2"; 39 | } 40 | rpc MtStreamedReplyWithSubjectParams(nrpc.Void) returns (SimpleStringReply) { 41 | option (nrpc.streamedReply) = true; 42 | option (nrpc.methodSubjectParams) = "mp1"; 43 | option (nrpc.methodSubjectParams) = "mp2"; 44 | } 45 | rpc MtNoReply(nrpc.Void) returns (nrpc.NoReply) {} 46 | rpc MtNoRequestWParams(nrpc.NoRequest) returns (SimpleStringReply) { 47 | option (nrpc.methodSubjectParams) = "mp1"; 48 | } 49 | } 50 | 51 | service NoRequestService { 52 | rpc MtNoRequest(nrpc.NoRequest) returns (SimpleStringReply) {} 53 | } 54 | 55 | message StringArg { 56 | string arg1 = 1; 57 | } 58 | 59 | message SimpleStringReply { 60 | string reply = 1; 61 | } 62 | -------------------------------------------------------------------------------- /examples/alloptions/alloptions_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "sync" 9 | "testing" 10 | "time" 11 | 12 | "github.com/nats-io/nats.go" 13 | "github.com/nats-rpc/nrpc" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | type TestingLogWriter struct { 18 | t *testing.T 19 | } 20 | 21 | func (w TestingLogWriter) Write(p []byte) (int, error) { 22 | w.t.Log(string(p)) 23 | return len(p), nil 24 | } 25 | 26 | type BasicServerImpl struct { 27 | t *testing.T 28 | handler *SvcCustomSubjectHandler 29 | handler2 *SvcSubjectParamsHandler 30 | } 31 | 32 | func (s BasicServerImpl) MtSimpleReply( 33 | ctx context.Context, args *StringArg, 34 | ) (*SimpleStringReply, error) { 35 | if instance := nrpc.GetRequest(ctx).PackageParam("instance"); instance != "default" { 36 | s.t.Errorf("Got an invalid package param instance: '%s'", instance) 37 | } 38 | return &SimpleStringReply{Reply: args.Arg1}, nil 39 | } 40 | 41 | func (s BasicServerImpl) MtVoidReply( 42 | ctx context.Context, args *StringArg, 43 | ) error { 44 | if args.GetArg1() == "please fail" { 45 | return errors.New("Error") 46 | } 47 | return nil 48 | } 49 | 50 | func (s BasicServerImpl) MtStreamedReply( 51 | ctx context.Context, req *StringArg, send func(rep *SimpleStringReply), 52 | ) error { 53 | if req.GetArg1() == "please fail" { 54 | panic("Failing") 55 | } 56 | if req.GetArg1() == "very long call" { 57 | select { 58 | case <-ctx.Done(): 59 | return ctx.Err() 60 | case <-time.After(time.Minute): 61 | time.Sleep(time.Minute) 62 | return nil 63 | } 64 | } 65 | time.Sleep(time.Second) 66 | send(&SimpleStringReply{Reply: "msg1"}) 67 | time.Sleep(250 * time.Millisecond) 68 | send(&SimpleStringReply{Reply: "msg2"}) 69 | time.Sleep(250 * time.Millisecond) 70 | send(&SimpleStringReply{Reply: "msg3"}) 71 | time.Sleep(250 * time.Millisecond) 72 | return nil 73 | } 74 | 75 | func (s BasicServerImpl) MtVoidReqStreamedReply( 76 | ctx context.Context, send func(rep *SimpleStringReply), 77 | ) error { 78 | time.Sleep(2 * time.Second) 79 | send(&SimpleStringReply{Reply: "hi"}) 80 | return nil 81 | } 82 | 83 | func (s BasicServerImpl) MtNoReply(ctx context.Context) { 84 | s.t.Log("Will publish to MtNoRequest") 85 | s.handler.MtNoRequestPublish("default", &SimpleStringReply{Reply: "Hi there"}) 86 | s.handler2.MtNoRequestWParamsPublish("default", "me", "mtvalue", &SimpleStringReply{Reply: "Hi there"}) 87 | } 88 | 89 | func (s BasicServerImpl) MtWithSubjectParams( 90 | ctx context.Context, mp1 string, mp2 string, 91 | ) (*SimpleStringReply, error) { 92 | var err error 93 | if mp1 != "p1" { 94 | err = fmt.Errorf("Expects method param mp1 = 'p1', got '%s'", mp1) 95 | } 96 | if mp2 != "p2" { 97 | err = fmt.Errorf("Expects method param mp2 = 'p2', got '%s'", mp2) 98 | } 99 | return &SimpleStringReply{Reply: "Hi"}, err 100 | } 101 | 102 | func (s BasicServerImpl) MtStreamedReplyWithSubjectParams( 103 | ctx context.Context, mp1 string, mp2 string, send func(rep *SimpleStringReply), 104 | ) error { 105 | send(&SimpleStringReply{Reply: mp1}) 106 | send(&SimpleStringReply{Reply: mp2}) 107 | return nil 108 | } 109 | 110 | func TestAll(t *testing.T) { 111 | c, err := nats.Connect(natsURL) 112 | if err != nil { 113 | t.Fatal(err) 114 | } 115 | 116 | log.SetOutput(TestingLogWriter{t}) 117 | 118 | t.Run("MultiProtocolPublish", func(t *testing.T) { 119 | log.SetOutput(TestingLogWriter{t}) 120 | handler := NewSvcCustomSubjectHandler(context.Background(), c, BasicServerImpl{t, nil, nil}) 121 | handler.SetEncodings([]string{"protobuf", "json"}) 122 | 123 | c1 := NewSvcCustomSubjectClient(c, "default") 124 | 125 | for _, protocol := range []string{"protobuf", "json"} { 126 | t.Run(protocol, func(t *testing.T) { 127 | c1.Encoding = protocol 128 | if protocol == "protobuf" { 129 | require.Equal(t, "root.default.custom_subject.mtnorequest", c1.MtNoRequestSubject()) 130 | } else { 131 | require.Equal(t, "root.default.custom_subject.mtnorequest."+protocol, c1.MtNoRequestSubject()) 132 | } 133 | sub, err := c1.MtNoRequestSubscribeSync() 134 | if err != nil { 135 | t.Fatal(err) 136 | } 137 | defer sub.Unsubscribe() 138 | 139 | if err := handler.MtNoRequestPublish( 140 | "default", &SimpleStringReply{Reply: "test"}, 141 | ); err != nil { 142 | t.Fatal(t) 143 | } 144 | 145 | msg, err := sub.Next(time.Second) 146 | if err != nil { 147 | t.Fatal(err) 148 | } 149 | require.Equal(t, "test", msg.GetReply()) 150 | }) 151 | } 152 | }) 153 | 154 | t.Run("NoConcurrency", func(t *testing.T) { 155 | log.SetOutput(TestingLogWriter{t}) 156 | handler1 := NewSvcCustomSubjectHandler(context.Background(), c, BasicServerImpl{t, nil, nil}) 157 | impl := BasicServerImpl{t, handler1, nil} 158 | handler2 := NewSvcSubjectParamsHandler(context.Background(), c, &impl) 159 | impl.handler2 = handler2 160 | 161 | if handler1.Subject() != "root.*.custom_subject.>" { 162 | t.Fatal("Invalid subject", handler1.Subject()) 163 | } 164 | if handler2.Subject() != "root.*.svcsubjectparams.*.>" { 165 | t.Fatal("Invalid subject", handler2.Subject()) 166 | } 167 | 168 | for _, encoding := range []string{"protobuf", "json"} { 169 | t.Run("Encoding_"+encoding, commonTests(c, handler1, handler2, encoding)) 170 | } 171 | }) 172 | 173 | t.Run("WithConcurrency", func(t *testing.T) { 174 | log.SetOutput(TestingLogWriter{t}) 175 | pool := nrpc.NewWorkerPool(context.Background(), 2, 5, 4*time.Second) 176 | 177 | handler1 := NewSvcCustomSubjectConcurrentHandler(pool, c, BasicServerImpl{t, nil, nil}) 178 | impl := BasicServerImpl{t, handler1, nil} 179 | handler2 := NewSvcSubjectParamsConcurrentHandler(pool, c, &impl) 180 | impl.handler2 = handler2 181 | 182 | if handler1.Subject() != "root.*.custom_subject.>" { 183 | t.Fatal("Invalid subject", handler1.Subject()) 184 | } 185 | if handler2.Subject() != "root.*.svcsubjectparams.*.>" { 186 | t.Fatal("Invalid subject", handler2.Subject()) 187 | } 188 | 189 | for _, encoding := range []string{"protobuf", "json"} { 190 | t.Run("Encoding_"+encoding, commonTests(c, handler1, handler2, encoding)) 191 | } 192 | 193 | // Now a few tests very specific to concurrency handling 194 | 195 | s, err := c.QueueSubscribe(handler1.Subject(), "queue", handler1.Handler) 196 | if err != nil { 197 | t.Fatal(err) 198 | } 199 | defer s.Unsubscribe() 200 | s, err = c.QueueSubscribe(handler2.Subject(), "queue", handler2.Handler) 201 | if err != nil { 202 | t.Fatal(err) 203 | } 204 | defer s.Unsubscribe() 205 | 206 | c1 := NewSvcCustomSubjectClient(c, "default") 207 | // c2 := NewSvcSubjectParamsClient(c, "default", "me") 208 | 209 | t.Run("Concurrent Stream calls", func(t *testing.T) { 210 | log.SetOutput(TestingLogWriter{t}) 211 | var resList []string 212 | var wg sync.WaitGroup 213 | resChan := make(chan string, 2) 214 | go func() { 215 | for r := range resChan { 216 | resList = append(resList, r) 217 | } 218 | }() 219 | for i := 0; i != 2; i++ { 220 | wg.Add(1) 221 | go func() { 222 | defer wg.Done() 223 | err := c1.MtStreamedReply( 224 | context.Background(), 225 | &StringArg{Arg1: "arg"}, 226 | func(ctx context.Context, rep *SimpleStringReply) { 227 | fmt.Println("received", rep) 228 | resChan <- rep.GetReply() 229 | }) 230 | if err != nil { 231 | t.Error(err) 232 | } 233 | }() 234 | } 235 | wg.Wait() 236 | close(resChan) 237 | 238 | expectsStringSlice(t, 239 | []string{"msg1", "msg1", "msg2", "msg2", "msg3", "msg3"}, 240 | resList) 241 | }) 242 | 243 | t.Run("Too many concurrent Stream calls", func(t *testing.T) { 244 | log.SetOutput(TestingLogWriter{t}) 245 | pool.SetMaxPendingDuration(2 * time.Second) 246 | var resList []string 247 | var wg sync.WaitGroup 248 | resChan := make(chan string, 2) 249 | go func() { 250 | for r := range resChan { 251 | resList = append(resList, r) 252 | } 253 | }() 254 | for i := 0; i != 7; i++ { 255 | wg.Add(1) 256 | time.Sleep(50 * time.Millisecond) 257 | go func(i int) { 258 | defer wg.Done() 259 | err := c1.MtStreamedReply( 260 | context.Background(), 261 | &StringArg{Arg1: "arg"}, 262 | func(ctx context.Context, rep *SimpleStringReply) { 263 | fmt.Println("received", rep) 264 | resChan <- rep.GetReply() 265 | }) 266 | if i >= 4 { 267 | if nrpcErr, ok := err.(*nrpc.Error); !ok || nrpcErr.Type != nrpc.Error_SERVERTOOBUSY { 268 | t.Errorf("Should get a SERVERTOOBUSY error, got %v", err) 269 | } 270 | } else if err != nil { 271 | t.Errorf("Should succeed but got: %s", err) 272 | } 273 | }(i) 274 | } 275 | 276 | // Wait so the 7 calls are already queued 277 | time.Sleep(200 * time.Millisecond) 278 | 279 | // The 7th call should get a SERVERTOOBUSY error 280 | err := c1.MtStreamedReply( 281 | context.Background(), 282 | &StringArg{Arg1: "arg"}, 283 | func(ctx context.Context, rep *SimpleStringReply) { 284 | fmt.Println("received", rep) 285 | }) 286 | if err == nil { 287 | t.Error("Should get an error") 288 | } else if nrpcErr, ok := err.(*nrpc.Error); ok { 289 | if nrpcErr.Type != nrpc.Error_SERVERTOOBUSY { 290 | t.Errorf("Should get a SERVERTOOBUSY, got %v", nrpcErr.Type) 291 | } 292 | } else { 293 | t.Errorf("Should get a nrpcError, got %v", err) 294 | } 295 | 296 | wg.Wait() 297 | close(resChan) 298 | }) 299 | 300 | pool.Close(time.Second) 301 | }) 302 | } 303 | 304 | func commonTests( 305 | conn *nats.Conn, 306 | handler1 *SvcCustomSubjectHandler, 307 | handler2 *SvcSubjectParamsHandler, 308 | encoding string, 309 | ) func(t *testing.T) { 310 | return func(t *testing.T) { 311 | handler1.SetEncodings([]string{encoding}) 312 | handler2.SetEncodings([]string{encoding}) 313 | s, err := conn.QueueSubscribe(handler1.Subject(), "queue", handler1.Handler) 314 | if err != nil { 315 | t.Fatal(err) 316 | } 317 | defer s.Unsubscribe() 318 | s, err = conn.QueueSubscribe(handler2.Subject(), "queue", handler2.Handler) 319 | if err != nil { 320 | t.Fatal(err) 321 | } 322 | defer s.Unsubscribe() 323 | 324 | c1 := NewSvcCustomSubjectClient(conn, "default") 325 | c2 := NewSvcSubjectParamsClient(conn, "default", "me") 326 | 327 | c1.Encoding = encoding 328 | c2.Encoding = encoding 329 | 330 | r, err := c1.MtSimpleReply(&StringArg{Arg1: "hi"}) 331 | if err != nil { 332 | t.Fatal(err) 333 | } 334 | if r.GetReply() != "hi" { 335 | t.Error("Invalid reply:", r.GetReply()) 336 | } 337 | 338 | if err := c1.MtSimpleReplyPoll(&StringArg{Arg1: "hi"}, 1, 339 | func(r *SimpleStringReply) error { 340 | if r.GetReply() != "hi" { 341 | return fmt.Errorf("Invalid reply: %s", r.GetReply()) 342 | } 343 | return nil 344 | }, 345 | ); err != nil { 346 | t.Fatal(err) 347 | } 348 | 349 | if err := c1.MtVoidReply(&StringArg{Arg1: "hi"}); err != nil { 350 | t.Error("Unexpected error:", err) 351 | } 352 | 353 | err = c1.MtVoidReply(&StringArg{Arg1: "please fail"}) 354 | if err == nil { 355 | t.Error("Expected an error") 356 | } 357 | 358 | t.Run("StreamedReply", func(t *testing.T) { 359 | log.SetOutput(TestingLogWriter{t}) 360 | t.Run("Simple", func(t *testing.T) { 361 | log.SetOutput(TestingLogWriter{t}) 362 | var resList []string 363 | err := c1.MtStreamedReply( 364 | context.Background(), 365 | &StringArg{Arg1: "arg"}, 366 | func(ctx context.Context, rep *SimpleStringReply) { 367 | fmt.Println("received", rep) 368 | resList = append(resList, rep.GetReply()) 369 | }) 370 | if err != nil { 371 | t.Fatal(err) 372 | } 373 | if resList[0] != "msg1" { 374 | t.Errorf("Expected 'msg1', got '%s'", resList[0]) 375 | } 376 | if resList[1] != "msg2" { 377 | t.Errorf("Expected 'msg2', got '%s'", resList[1]) 378 | } 379 | if resList[2] != "msg3" { 380 | t.Errorf("Expected 'msg3', got '%s'", resList[2]) 381 | } 382 | }) 383 | 384 | t.Run("Error", func(t *testing.T) { 385 | log.SetOutput(TestingLogWriter{t}) 386 | err := c1.MtStreamedReply(context.Background(), 387 | &StringArg{Arg1: "please fail"}, 388 | func(ctx context.Context, rep *SimpleStringReply) { 389 | t.Fatal("Should not receive anything") 390 | }) 391 | if err == nil { 392 | t.Fatal("Expected an error, got nil") 393 | } 394 | }) 395 | 396 | t.Run("Cancel", func(t *testing.T) { 397 | log.SetOutput(TestingLogWriter{t}) 398 | ctx, cancel := context.WithTimeout(context.Background(), 7*time.Second) 399 | defer cancel() 400 | err := c1.MtStreamedReply(ctx, 401 | &StringArg{Arg1: "very long call"}, 402 | func(context.Context, *SimpleStringReply) { 403 | t.Fatal("Should not receive anything") 404 | }) 405 | if err != nrpc.ErrCanceled { 406 | t.Fatal("Expects a ErrCanceled error, got ", err) 407 | } 408 | }) 409 | 410 | t.Run("VoidRequest", func(t *testing.T) { 411 | log.SetOutput(TestingLogWriter{t}) 412 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 413 | defer cancel() 414 | err := c1.MtVoidReqStreamedReply(ctx, func(context.Context, *SimpleStringReply) {}) 415 | if err != nil { 416 | fmt.Print(err) 417 | t.Error(err) 418 | } 419 | }) 420 | }) 421 | 422 | t.Run("SubjectParams", func(t *testing.T) { 423 | log.SetOutput(TestingLogWriter{t}) 424 | r, err = c2.MtWithSubjectParams("p1", "p2") 425 | if err != nil { 426 | t.Fatal(err) 427 | } 428 | if r.GetReply() != "Hi" { 429 | t.Error("Invalid reply:", r.GetReply()) 430 | } 431 | 432 | r, err = c2.MtWithSubjectParams("invalid", "p2") 433 | if err == nil { 434 | t.Error("Expected an error") 435 | } 436 | if nErr, ok := err.(*nrpc.Error); ok { 437 | if nErr.Type != nrpc.Error_CLIENT || nErr.Message != "Expects method param mp1 = 'p1', got 'invalid'" { 438 | t.Errorf("Unexpected error %#v", nErr) 439 | } 440 | } else { 441 | t.Errorf("Expected a nrpc.Error, got %#v", err) 442 | } 443 | }) 444 | 445 | t.Run("StreamedReply with SubjectParams", func(t *testing.T) { 446 | log.SetOutput(TestingLogWriter{t}) 447 | var resList []string 448 | err := c2.MtStreamedReplyWithSubjectParams( 449 | context.Background(), 450 | "arg1", "arg2", 451 | func(ctx context.Context, rep *SimpleStringReply) { 452 | resList = append(resList, rep.GetReply()) 453 | }) 454 | if err != nil { 455 | t.Fatal(err) 456 | } 457 | if resList[0] != "arg1" { 458 | t.Errorf("Expected 'arg1', got '%s'", resList[0]) 459 | } 460 | if resList[1] != "arg2" { 461 | t.Errorf("Expected 'arg2', got '%s'", resList[1]) 462 | } 463 | }) 464 | 465 | t.Run("NoRequest method with params", func(t *testing.T) { 466 | log.SetOutput(TestingLogWriter{t}) 467 | sub, err := c2.MtNoRequestWParamsSubscribeSync( 468 | "mtvalue", 469 | ) 470 | if err != nil { 471 | t.Fatal(err) 472 | } 473 | defer sub.Unsubscribe() 474 | c2.MtNoReply() 475 | reply, err := sub.Next(time.Second) 476 | if err != nil { 477 | t.Fatal(err) 478 | } 479 | if reply.GetReply() != "Hi there" { 480 | t.Errorf("Expected 'Hi there', got %s", reply.GetReply()) 481 | } 482 | }) 483 | t.Run("NoRequest method subscriptions", func(t *testing.T) { 484 | log.SetOutput(TestingLogWriter{t}) 485 | if encoding != "protobuf" { 486 | t.Skip() 487 | } 488 | fmt.Println("Subscribing") 489 | sub1, err := c1.MtNoRequestSubscribeSync() 490 | if err != nil { 491 | t.Fatal(err) 492 | } 493 | defer sub1.Unsubscribe() 494 | repChan := make(chan string, 2) 495 | sub2, err := c1.MtNoRequestSubscribe(func(msg *SimpleStringReply) { 496 | repChan <- msg.GetReply() 497 | }) 498 | if err != nil { 499 | t.Fatal(err) 500 | } 501 | defer sub2.Unsubscribe() 502 | msgChan, sub3, err := c1.MtNoRequestSubscribeChan() 503 | if err != nil { 504 | t.Fatal(err) 505 | } 506 | defer sub3.Unsubscribe() 507 | go func() { 508 | msg := <-msgChan 509 | repChan <- msg.GetReply() 510 | }() 511 | 512 | err = c2.MtNoReply() 513 | if err != nil { 514 | t.Fatal(err) 515 | } 516 | msg, err := sub1.Next(time.Second) 517 | if err != nil { 518 | t.Fatal(err) 519 | } 520 | if msg.GetReply() != "Hi there" { 521 | t.Errorf("Expected 'Hi there', got '%s'", msg.GetReply()) 522 | } 523 | for range []int{0, 1} { 524 | select { 525 | case rep := <-repChan: 526 | if rep != "Hi there" { 527 | t.Errorf("Expected 'Hi there', got '%s'", rep) 528 | } 529 | case <-time.After(time.Second): 530 | t.Fatal("timeout") 531 | } 532 | } 533 | }) 534 | } 535 | } 536 | 537 | func expectsStringSlice(t *testing.T, expected, actual []string) { 538 | if len(expected) != len(actual) { 539 | t.Errorf("Expected %v, got %v", expected, actual) 540 | return 541 | } 542 | for i := range expected { 543 | if expected[i] != actual[i] { 544 | t.Errorf("Expected %v, got %v", expected, actual) 545 | return 546 | } 547 | } 548 | } 549 | -------------------------------------------------------------------------------- /examples/alloptions/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | 12 | "github.com/nats-io/nats.go" 13 | "github.com/nats-rpc/nrpc" 14 | ) 15 | 16 | //go:generate protoc -I. -I ../../.. --go_out . --nrpc_out . alloptions.proto 17 | 18 | type ServerImpl struct { 19 | handler *SvcCustomSubjectHandler 20 | handler2 *SvcSubjectParamsHandler 21 | } 22 | 23 | func (s ServerImpl) MtSimpleReply( 24 | ctx context.Context, args *StringArg, 25 | ) (*SimpleStringReply, error) { 26 | fmt.Println("MtSimpleReply: " + args.GetArg1()) 27 | if instance := nrpc.GetRequest(ctx).PackageParam("instance"); instance != "default" { 28 | return nil, fmt.Errorf("Got an invalid package param instance: '%s'", instance) 29 | } 30 | return &SimpleStringReply{Reply: args.Arg1}, nil 31 | } 32 | 33 | func (s ServerImpl) MtVoidReply( 34 | ctx context.Context, args *StringArg, 35 | ) error { 36 | fmt.Println("MtVoidReply: " + args.GetArg1()) 37 | if args.GetArg1() == "please fail" { 38 | return errors.New("Error") 39 | } 40 | return nil 41 | } 42 | 43 | func (s ServerImpl) MtStreamedReply( 44 | ctx context.Context, req *StringArg, send func(rep *SimpleStringReply), 45 | ) error { 46 | fmt.Println("MtStreamedReply: " + req.GetArg1()) 47 | if req.GetArg1() == "please fail" { 48 | panic("Failing") 49 | } 50 | if req.GetArg1() == "very long call" { 51 | select { 52 | case <-ctx.Done(): 53 | return ctx.Err() 54 | case <-time.After(time.Minute): 55 | time.Sleep(time.Minute) 56 | return nil 57 | } 58 | } 59 | time.Sleep(time.Second) 60 | send(&SimpleStringReply{Reply: "msg1"}) 61 | time.Sleep(250 * time.Millisecond) 62 | send(&SimpleStringReply{Reply: "msg2"}) 63 | time.Sleep(250 * time.Millisecond) 64 | send(&SimpleStringReply{Reply: "msg3"}) 65 | time.Sleep(250 * time.Millisecond) 66 | return nil 67 | } 68 | 69 | func (s ServerImpl) MtVoidReqStreamedReply( 70 | ctx context.Context, send func(rep *SimpleStringReply), 71 | ) error { 72 | fmt.Println("MtVoidReqStreamedReply") 73 | time.Sleep(2 * time.Second) 74 | send(&SimpleStringReply{Reply: "hi"}) 75 | return nil 76 | } 77 | 78 | func (s ServerImpl) MtNoReply(ctx context.Context) { 79 | fmt.Println("MtNoReply") 80 | if err := s.handler.MtNoRequestPublish("default", &SimpleStringReply{Reply: "Hi there"}); err != nil { 81 | fmt.Println(err) 82 | } 83 | if err := s.handler2.MtNoRequestWParamsPublish("default", "me", "mtvalue", &SimpleStringReply{Reply: "Hi there"}); err != nil { 84 | fmt.Println(err) 85 | } 86 | } 87 | 88 | func (s ServerImpl) MtWithSubjectParams( 89 | ctx context.Context, mp1 string, mp2 string, 90 | ) (*SimpleStringReply, error) { 91 | fmt.Println("MtWithSubjectParams: ", mp1, mp2) 92 | var err error 93 | if mp1 != "p1" { 94 | err = fmt.Errorf("Expects method param mp1 = 'p1', got '%s'", mp1) 95 | } 96 | if mp2 != "p2" { 97 | err = fmt.Errorf("Expects method param mp2 = 'p2', got '%s'", mp2) 98 | } 99 | return &SimpleStringReply{Reply: "Hi"}, err 100 | } 101 | 102 | func (s ServerImpl) MtStreamedReplyWithSubjectParams( 103 | ctx context.Context, mp1 string, mp2 string, send func(rep *SimpleStringReply), 104 | ) error { 105 | fmt.Println("MtStreamedReplyWithSubjectParams: ", mp1, mp2) 106 | send(&SimpleStringReply{Reply: mp1}) 107 | send(&SimpleStringReply{Reply: mp2}) 108 | return nil 109 | } 110 | 111 | func main() { 112 | c, err := nats.Connect("localhost:4222") 113 | if err != nil { 114 | fmt.Println(err) 115 | return 116 | } 117 | 118 | pool := nrpc.NewWorkerPool(context.Background(), 2, 5, 4*time.Second) 119 | 120 | impl := ServerImpl{nil, nil} 121 | handler1 := NewSvcCustomSubjectConcurrentHandler(pool, c, &impl) 122 | handler2 := NewSvcSubjectParamsConcurrentHandler(pool, c, &impl) 123 | impl.handler = handler1 124 | impl.handler2 = handler2 125 | 126 | s, err := c.QueueSubscribe(handler1.Subject(), "queue", handler1.Handler) 127 | if err != nil { 128 | panic(err) 129 | } 130 | defer s.Unsubscribe() 131 | s, err = c.QueueSubscribe(handler2.Subject(), "queue", handler2.Handler) 132 | if err != nil { 133 | panic(err) 134 | } 135 | defer s.Unsubscribe() 136 | 137 | done := make(chan os.Signal, 1) 138 | signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) 139 | fmt.Println("Blocking, press ctrl+c to continue...") 140 | <-done // Will block here until user hits ctrl+c 141 | } 142 | -------------------------------------------------------------------------------- /examples/alloptions/testrunner_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | "github.com/nats-io/nats-server/v2/logger" 10 | natsServer "github.com/nats-io/nats-server/v2/server" 11 | ) 12 | 13 | var natsURL string 14 | 15 | func TestMain(m *testing.M) { 16 | gnatsd := natsServer.New(&natsServer.Options{Port: natsServer.RANDOM_PORT}) 17 | gnatsd.SetLogger( 18 | logger.NewStdLogger(false, false, false, false, false), 19 | false, false) 20 | go gnatsd.Start() 21 | defer gnatsd.Shutdown() 22 | 23 | if !gnatsd.ReadyForConnections(time.Second) { 24 | log.Fatal("Cannot start the gnatsd server") 25 | } 26 | natsURL = "nats://" + gnatsd.Addr().String() 27 | 28 | os.Exit(m.Run()) 29 | } 30 | -------------------------------------------------------------------------------- /examples/helloworld/greeter_client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "time" 8 | 9 | "github.com/nats-io/nats.go" 10 | 11 | // This is the package containing the generated *.pb.go and *.nrpc.go 12 | // files. 13 | "github.com/nats-rpc/nrpc/examples/helloworld/helloworld" 14 | ) 15 | 16 | func main() { 17 | var natsURL = nats.DefaultURL 18 | if len(os.Args) == 2 { 19 | natsURL = os.Args[1] 20 | } 21 | // Connect to the NATS server. 22 | nc, err := nats.Connect(natsURL, nats.Timeout(5*time.Second)) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | defer nc.Close() 27 | 28 | // This is our generated client. 29 | cli := helloworld.NewGreeterClient(nc) 30 | 31 | // Contact the server and print out its response. 32 | resp, err := cli.SayHello(&helloworld.HelloRequest{Name: "world"}) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | 37 | // print 38 | fmt.Printf("Greeting: %s\n", resp.GetMessage()) 39 | } 40 | -------------------------------------------------------------------------------- /examples/helloworld/greeter_server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "time" 10 | 11 | "github.com/nats-io/nats.go" 12 | 13 | // This is the package containing the generated *.pb.go and *.nrpc.go 14 | // files. 15 | "github.com/nats-rpc/nrpc/examples/helloworld/helloworld" 16 | ) 17 | 18 | // server implements the helloworld.GreeterServer interface. 19 | type server struct{} 20 | 21 | // SayHello is an implementation of the SayHello method from the definition of 22 | // the Greeter service. 23 | func (s *server) SayHello(ctx context.Context, req *helloworld.HelloRequest) (resp *helloworld.HelloReply, err error) { 24 | return &helloworld.HelloReply{Message: "Hello " + req.Name}, nil 25 | } 26 | 27 | func main() { 28 | var natsURL = nats.DefaultURL 29 | if len(os.Args) == 2 { 30 | natsURL = os.Args[1] 31 | } 32 | // Connect to the NATS server. 33 | nc, err := nats.Connect(natsURL, nats.Timeout(5*time.Second)) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | defer nc.Close() 38 | 39 | // Our server implementation. 40 | s := &server{} 41 | 42 | // The NATS handler from the helloworld.nrpc.proto file. 43 | h := helloworld.NewGreeterHandler(context.TODO(), nc, s) 44 | 45 | // Start a NATS subscription using the handler. You can also use the 46 | // QueueSubscribe() method for a load-balanced set of servers. 47 | sub, err := nc.Subscribe(h.Subject(), h.Handler) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | defer sub.Unsubscribe() 52 | 53 | // Keep running until ^C. 54 | fmt.Println("server is running, ^C quits.") 55 | c := make(chan os.Signal, 1) 56 | signal.Notify(c, os.Interrupt) 57 | <-c 58 | close(c) 59 | } 60 | -------------------------------------------------------------------------------- /examples/helloworld/greeter_server/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/nats-io/nats.go" 9 | 10 | // This is the package containing the generated *.pb.go and *.nrpc.go 11 | // files. 12 | "github.com/nats-rpc/nrpc/examples/helloworld/helloworld" 13 | ) 14 | 15 | func TestBasic(t *testing.T) { 16 | // Connect to the NATS server. 17 | nc, err := nats.Connect(natsURL, nats.Timeout(5*time.Second)) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | defer nc.Close() 22 | 23 | // Our server implementation. 24 | s := &server{} 25 | 26 | // The NATS handler from the helloworld.nrpc.proto file. 27 | h := helloworld.NewGreeterHandler(context.TODO(), nc, s) 28 | 29 | // Start a NATS subscription using the handler. You can also use the 30 | // QueueSubscribe() method for a load-balanced set of servers. 31 | sub, err := nc.Subscribe(h.Subject(), h.Handler) 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | defer sub.Unsubscribe() 36 | 37 | // This is our generated client. 38 | cli := helloworld.NewGreeterClient(nc) 39 | 40 | // Contact the server and print out its response. 41 | resp, err := cli.SayHello(&helloworld.HelloRequest{Name: "world"}) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | if resp.GetMessage() != "Hello world" { 46 | t.Fatalf("unexpected message: %s", resp.GetMessage()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/helloworld/greeter_server/testrunner_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | "github.com/nats-io/nats-server/v2/logger" 10 | natsServer "github.com/nats-io/nats-server/v2/server" 11 | ) 12 | 13 | var natsURL string 14 | 15 | func TestMain(m *testing.M) { 16 | gnatsd := natsServer.New(&natsServer.Options{Port: natsServer.RANDOM_PORT}) 17 | gnatsd.SetLogger( 18 | logger.NewStdLogger(false, false, false, false, false), 19 | false, false) 20 | go gnatsd.Start() 21 | defer gnatsd.Shutdown() 22 | 23 | if !gnatsd.ReadyForConnections(time.Second) { 24 | log.Fatal("Cannot start the gnatsd server") 25 | } 26 | natsURL = "nats://" + gnatsd.Addr().String() 27 | 28 | os.Exit(m.Run()) 29 | } 30 | -------------------------------------------------------------------------------- /examples/helloworld/helloworld/helloworld.go: -------------------------------------------------------------------------------- 1 | package helloworld 2 | 3 | //go:generate protoc -I. -I../../.. --go_out . --go_opt=paths=source_relative --nrpc_out . --nrpc_opt=paths=source_relative helloworld.proto 4 | -------------------------------------------------------------------------------- /examples/helloworld/helloworld/helloworld.nrpc.go: -------------------------------------------------------------------------------- 1 | // This code was autogenerated from helloworld.proto, do not edit. 2 | package helloworld 3 | 4 | import ( 5 | "context" 6 | "log" 7 | "time" 8 | 9 | "google.golang.org/protobuf/proto" 10 | "github.com/nats-io/nats.go" 11 | "github.com/nats-rpc/nrpc" 12 | ) 13 | 14 | // GreeterServer is the interface that providers of the service 15 | // Greeter should implement. 16 | type GreeterServer interface { 17 | SayHello(ctx context.Context, req *HelloRequest) (*HelloReply, error) 18 | } 19 | 20 | // GreeterHandler provides a NATS subscription handler that can serve a 21 | // subscription using a given GreeterServer implementation. 22 | type GreeterHandler struct { 23 | ctx context.Context 24 | workers *nrpc.WorkerPool 25 | nc nrpc.NatsConn 26 | server GreeterServer 27 | 28 | encodings []string 29 | } 30 | 31 | func NewGreeterHandler(ctx context.Context, nc nrpc.NatsConn, s GreeterServer) *GreeterHandler { 32 | return &GreeterHandler{ 33 | ctx: ctx, 34 | nc: nc, 35 | server: s, 36 | 37 | encodings: []string{"protobuf"}, 38 | } 39 | } 40 | 41 | func NewGreeterConcurrentHandler(workers *nrpc.WorkerPool, nc nrpc.NatsConn, s GreeterServer) *GreeterHandler { 42 | return &GreeterHandler{ 43 | workers: workers, 44 | nc: nc, 45 | server: s, 46 | } 47 | } 48 | 49 | // SetEncodings sets the output encodings when using a '*Publish' function 50 | func (h *GreeterHandler) SetEncodings(encodings []string) { 51 | h.encodings = encodings 52 | } 53 | 54 | func (h *GreeterHandler) Subject() string { 55 | return "Greeter.>" 56 | } 57 | 58 | func (h *GreeterHandler) Handler(msg *nats.Msg) { 59 | var ctx context.Context 60 | if h.workers != nil { 61 | ctx = h.workers.Context 62 | } else { 63 | ctx = h.ctx 64 | } 65 | request := nrpc.NewRequest(ctx, h.nc, msg.Subject, msg.Reply) 66 | // extract method name & encoding from subject 67 | _, _, name, tail, err := nrpc.ParseSubject( 68 | "", 0, "Greeter", 0, msg.Subject) 69 | if err != nil { 70 | log.Printf("GreeterHanlder: Greeter subject parsing failed: %v", err) 71 | return 72 | } 73 | 74 | request.MethodName = name 75 | request.SubjectTail = tail 76 | 77 | // call handler and form response 78 | var immediateError *nrpc.Error 79 | switch name { 80 | case "SayHello": 81 | _, request.Encoding, err = nrpc.ParseSubjectTail(0, request.SubjectTail) 82 | if err != nil { 83 | log.Printf("SayHelloHanlder: SayHello subject parsing failed: %v", err) 84 | break 85 | } 86 | var req HelloRequest 87 | if err := nrpc.Unmarshal(request.Encoding, msg.Data, &req); err != nil { 88 | log.Printf("SayHelloHandler: SayHello request unmarshal failed: %v", err) 89 | immediateError = &nrpc.Error{ 90 | Type: nrpc.Error_CLIENT, 91 | Message: "bad request received: " + err.Error(), 92 | } 93 | } else { 94 | request.Handler = func(ctx context.Context)(proto.Message, error){ 95 | innerResp, err := h.server.SayHello(ctx, &req) 96 | if err != nil { 97 | return nil, err 98 | } 99 | return innerResp, err 100 | } 101 | } 102 | default: 103 | log.Printf("GreeterHandler: unknown name %q", name) 104 | immediateError = &nrpc.Error{ 105 | Type: nrpc.Error_CLIENT, 106 | Message: "unknown name: " + name, 107 | } 108 | } 109 | if immediateError == nil { 110 | if h.workers != nil { 111 | // Try queuing the request 112 | if err := h.workers.QueueRequest(request); err != nil { 113 | log.Printf("nrpc: Error queuing the request: %s", err) 114 | } 115 | } else { 116 | // Run the handler synchronously 117 | request.RunAndReply() 118 | } 119 | } 120 | 121 | if immediateError != nil { 122 | if err := request.SendReply(nil, immediateError); err != nil { 123 | log.Printf("GreeterHandler: Greeter handler failed to publish the response: %s", err) 124 | } 125 | } else { 126 | } 127 | } 128 | 129 | type GreeterClient struct { 130 | nc nrpc.NatsConn 131 | Subject string 132 | Encoding string 133 | Timeout time.Duration 134 | } 135 | 136 | func NewGreeterClient(nc nrpc.NatsConn) *GreeterClient { 137 | return &GreeterClient{ 138 | nc: nc, 139 | Subject: "Greeter", 140 | Encoding: "protobuf", 141 | Timeout: 5 * time.Second, 142 | } 143 | } 144 | 145 | func (c *GreeterClient) SayHello(req *HelloRequest) (*HelloReply, error) { 146 | 147 | subject := c.Subject + "." + "SayHello" 148 | 149 | // call 150 | var resp = HelloReply{} 151 | if err := nrpc.Call(req, &resp, c.nc, subject, c.Encoding, c.Timeout); err != nil { 152 | return nil, err 153 | } 154 | 155 | return &resp, nil 156 | } 157 | 158 | type Client struct { 159 | nc nrpc.NatsConn 160 | defaultEncoding string 161 | defaultTimeout time.Duration 162 | Greeter *GreeterClient 163 | } 164 | 165 | func NewClient(nc nrpc.NatsConn) *Client { 166 | c := Client{ 167 | nc: nc, 168 | defaultEncoding: "protobuf", 169 | defaultTimeout: 5*time.Second, 170 | } 171 | c.Greeter = NewGreeterClient(nc) 172 | return &c 173 | } 174 | 175 | func (c *Client) SetEncoding(encoding string) { 176 | c.defaultEncoding = encoding 177 | if c.Greeter != nil { 178 | c.Greeter.Encoding = encoding 179 | } 180 | } 181 | 182 | func (c *Client) SetTimeout(t time.Duration) { 183 | c.defaultTimeout = t 184 | if c.Greeter != nil { 185 | c.Greeter.Timeout = t 186 | } 187 | } -------------------------------------------------------------------------------- /examples/helloworld/helloworld/helloworld.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by protoc-gen-go. DO NOT EDIT. 16 | // versions: 17 | // protoc-gen-go v1.29.0 18 | // protoc v4.22.2 19 | // source: helloworld.proto 20 | 21 | package helloworld 22 | 23 | import ( 24 | _ "github.com/nats-rpc/nrpc" 25 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 26 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 27 | reflect "reflect" 28 | sync "sync" 29 | ) 30 | 31 | const ( 32 | // Verify that this generated code is sufficiently up-to-date. 33 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 34 | // Verify that runtime/protoimpl is sufficiently up-to-date. 35 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 36 | ) 37 | 38 | // The request message containing the user's name. 39 | type HelloRequest struct { 40 | state protoimpl.MessageState 41 | sizeCache protoimpl.SizeCache 42 | unknownFields protoimpl.UnknownFields 43 | 44 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 45 | } 46 | 47 | func (x *HelloRequest) Reset() { 48 | *x = HelloRequest{} 49 | if protoimpl.UnsafeEnabled { 50 | mi := &file_helloworld_proto_msgTypes[0] 51 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 52 | ms.StoreMessageInfo(mi) 53 | } 54 | } 55 | 56 | func (x *HelloRequest) String() string { 57 | return protoimpl.X.MessageStringOf(x) 58 | } 59 | 60 | func (*HelloRequest) ProtoMessage() {} 61 | 62 | func (x *HelloRequest) ProtoReflect() protoreflect.Message { 63 | mi := &file_helloworld_proto_msgTypes[0] 64 | if protoimpl.UnsafeEnabled && x != nil { 65 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 66 | if ms.LoadMessageInfo() == nil { 67 | ms.StoreMessageInfo(mi) 68 | } 69 | return ms 70 | } 71 | return mi.MessageOf(x) 72 | } 73 | 74 | // Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead. 75 | func (*HelloRequest) Descriptor() ([]byte, []int) { 76 | return file_helloworld_proto_rawDescGZIP(), []int{0} 77 | } 78 | 79 | func (x *HelloRequest) GetName() string { 80 | if x != nil { 81 | return x.Name 82 | } 83 | return "" 84 | } 85 | 86 | // The response message containing the greetings 87 | type HelloReply struct { 88 | state protoimpl.MessageState 89 | sizeCache protoimpl.SizeCache 90 | unknownFields protoimpl.UnknownFields 91 | 92 | Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` 93 | } 94 | 95 | func (x *HelloReply) Reset() { 96 | *x = HelloReply{} 97 | if protoimpl.UnsafeEnabled { 98 | mi := &file_helloworld_proto_msgTypes[1] 99 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 100 | ms.StoreMessageInfo(mi) 101 | } 102 | } 103 | 104 | func (x *HelloReply) String() string { 105 | return protoimpl.X.MessageStringOf(x) 106 | } 107 | 108 | func (*HelloReply) ProtoMessage() {} 109 | 110 | func (x *HelloReply) ProtoReflect() protoreflect.Message { 111 | mi := &file_helloworld_proto_msgTypes[1] 112 | if protoimpl.UnsafeEnabled && x != nil { 113 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 114 | if ms.LoadMessageInfo() == nil { 115 | ms.StoreMessageInfo(mi) 116 | } 117 | return ms 118 | } 119 | return mi.MessageOf(x) 120 | } 121 | 122 | // Deprecated: Use HelloReply.ProtoReflect.Descriptor instead. 123 | func (*HelloReply) Descriptor() ([]byte, []int) { 124 | return file_helloworld_proto_rawDescGZIP(), []int{1} 125 | } 126 | 127 | func (x *HelloReply) GetMessage() string { 128 | if x != nil { 129 | return x.Message 130 | } 131 | return "" 132 | } 133 | 134 | var File_helloworld_proto protoreflect.FileDescriptor 135 | 136 | var file_helloworld_proto_rawDesc = []byte{ 137 | 0x0a, 0x10, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x70, 0x72, 0x6f, 138 | 0x74, 0x6f, 0x12, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x1a, 0x0a, 139 | 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, 140 | 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 141 | 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26, 142 | 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 143 | 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 144 | 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x49, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 145 | 0x72, 0x12, 0x3e, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x18, 0x2e, 146 | 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 147 | 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 148 | 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 149 | 0x00, 0x42, 0x69, 0x0a, 0x1b, 0x69, 0x6f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x78, 0x61, 150 | 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 151 | 0x42, 0x0f, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x50, 0x72, 0x6f, 0x74, 152 | 0x6f, 0x50, 0x01, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 153 | 0x6e, 0x61, 0x74, 0x73, 0x2d, 0x72, 0x70, 0x63, 0x2f, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x78, 154 | 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 155 | 0x64, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x62, 0x06, 0x70, 0x72, 156 | 0x6f, 0x74, 0x6f, 0x33, 157 | } 158 | 159 | var ( 160 | file_helloworld_proto_rawDescOnce sync.Once 161 | file_helloworld_proto_rawDescData = file_helloworld_proto_rawDesc 162 | ) 163 | 164 | func file_helloworld_proto_rawDescGZIP() []byte { 165 | file_helloworld_proto_rawDescOnce.Do(func() { 166 | file_helloworld_proto_rawDescData = protoimpl.X.CompressGZIP(file_helloworld_proto_rawDescData) 167 | }) 168 | return file_helloworld_proto_rawDescData 169 | } 170 | 171 | var file_helloworld_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 172 | var file_helloworld_proto_goTypes = []interface{}{ 173 | (*HelloRequest)(nil), // 0: helloworld.HelloRequest 174 | (*HelloReply)(nil), // 1: helloworld.HelloReply 175 | } 176 | var file_helloworld_proto_depIdxs = []int32{ 177 | 0, // 0: helloworld.Greeter.SayHello:input_type -> helloworld.HelloRequest 178 | 1, // 1: helloworld.Greeter.SayHello:output_type -> helloworld.HelloReply 179 | 1, // [1:2] is the sub-list for method output_type 180 | 0, // [0:1] is the sub-list for method input_type 181 | 0, // [0:0] is the sub-list for extension type_name 182 | 0, // [0:0] is the sub-list for extension extendee 183 | 0, // [0:0] is the sub-list for field type_name 184 | } 185 | 186 | func init() { file_helloworld_proto_init() } 187 | func file_helloworld_proto_init() { 188 | if File_helloworld_proto != nil { 189 | return 190 | } 191 | if !protoimpl.UnsafeEnabled { 192 | file_helloworld_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 193 | switch v := v.(*HelloRequest); i { 194 | case 0: 195 | return &v.state 196 | case 1: 197 | return &v.sizeCache 198 | case 2: 199 | return &v.unknownFields 200 | default: 201 | return nil 202 | } 203 | } 204 | file_helloworld_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 205 | switch v := v.(*HelloReply); i { 206 | case 0: 207 | return &v.state 208 | case 1: 209 | return &v.sizeCache 210 | case 2: 211 | return &v.unknownFields 212 | default: 213 | return nil 214 | } 215 | } 216 | } 217 | type x struct{} 218 | out := protoimpl.TypeBuilder{ 219 | File: protoimpl.DescBuilder{ 220 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 221 | RawDescriptor: file_helloworld_proto_rawDesc, 222 | NumEnums: 0, 223 | NumMessages: 2, 224 | NumExtensions: 0, 225 | NumServices: 1, 226 | }, 227 | GoTypes: file_helloworld_proto_goTypes, 228 | DependencyIndexes: file_helloworld_proto_depIdxs, 229 | MessageInfos: file_helloworld_proto_msgTypes, 230 | }.Build() 231 | File_helloworld_proto = out.File 232 | file_helloworld_proto_rawDesc = nil 233 | file_helloworld_proto_goTypes = nil 234 | file_helloworld_proto_depIdxs = nil 235 | } 236 | -------------------------------------------------------------------------------- /examples/helloworld/helloworld/helloworld.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | option java_multiple_files = true; 18 | option java_package = "io.grpc.examples.helloworld"; 19 | option java_outer_classname = "HelloWorldProto"; 20 | 21 | import "nrpc.proto"; 22 | 23 | package helloworld; 24 | 25 | option go_package = "github.com/nats-rpc/nrpc/examples/helloworld/helloworld"; 26 | 27 | // The greeting service definition. 28 | service Greeter { 29 | // Sends a greeting 30 | rpc SayHello (HelloRequest) returns (HelloReply) {} 31 | } 32 | 33 | // The request message containing the user's name. 34 | message HelloRequest { 35 | string name = 1; 36 | } 37 | 38 | // The response message containing the greetings 39 | message HelloReply { 40 | string message = 1; 41 | } 42 | -------------------------------------------------------------------------------- /examples/metrics_helloworld/helloworld/helloworld.go: -------------------------------------------------------------------------------- 1 | package helloworld 2 | 3 | //go:generate protoc --go_out . --go_opt=paths=source_relative --nrpc_out plugins=prometheus:. --nrpc_opt=paths=source_relative helloworld.proto 4 | -------------------------------------------------------------------------------- /examples/metrics_helloworld/helloworld/helloworld.nrpc.go: -------------------------------------------------------------------------------- 1 | // This code was autogenerated from helloworld.proto, do not edit. 2 | package helloworld 3 | 4 | import ( 5 | "context" 6 | "log" 7 | "time" 8 | 9 | "google.golang.org/protobuf/proto" 10 | "github.com/nats-io/nats.go" 11 | "github.com/prometheus/client_golang/prometheus" 12 | "github.com/nats-rpc/nrpc" 13 | ) 14 | 15 | // GreeterServer is the interface that providers of the service 16 | // Greeter should implement. 17 | type GreeterServer interface { 18 | SayHello(ctx context.Context, req HelloRequest) (resp HelloReply, err error) 19 | } 20 | 21 | var ( 22 | // The request completion time, measured at client-side. 23 | clientRCTForGreeter = prometheus.NewSummaryVec( 24 | prometheus.SummaryOpts{ 25 | Name: "nrpc_client_request_completion_time_seconds", 26 | Help: "The request completion time for calls, measured client-side.", 27 | Objectives: map[float64]float64{0.9: 0.01, 0.95: 0.01, 0.99: 0.001}, 28 | ConstLabels: map[string]string{ 29 | "service": "Greeter", 30 | }, 31 | }, 32 | []string{"method"}) 33 | 34 | // The handler execution time, measured at server-side. 35 | serverHETForGreeter = prometheus.NewSummaryVec( 36 | prometheus.SummaryOpts{ 37 | Name: "nrpc_server_handler_execution_time_seconds", 38 | Help: "The handler execution time for calls, measured server-side.", 39 | Objectives: map[float64]float64{0.9: 0.01, 0.95: 0.01, 0.99: 0.001}, 40 | ConstLabels: map[string]string{ 41 | "service": "Greeter", 42 | }, 43 | }, 44 | []string{"method"}) 45 | 46 | // The counts of calls made by the client, classified by result type. 47 | clientCallsForGreeter = prometheus.NewCounterVec( 48 | prometheus.CounterOpts{ 49 | Name: "nrpc_client_calls_count", 50 | Help: "The count of calls made by the client.", 51 | ConstLabels: map[string]string{ 52 | "service": "Greeter", 53 | }, 54 | }, 55 | []string{"method", "encoding", "result_type"}) 56 | 57 | // The counts of requests handled by the server, classified by result type. 58 | serverRequestsForGreeter = prometheus.NewCounterVec( 59 | prometheus.CounterOpts{ 60 | Name: "nrpc_server_requests_count", 61 | Help: "The count of requests handled by the server.", 62 | ConstLabels: map[string]string{ 63 | "service": "Greeter", 64 | }, 65 | }, 66 | []string{"method", "encoding", "result_type"}) 67 | ) 68 | 69 | // GreeterHandler provides a NATS subscription handler that can serve a 70 | // subscription using a given GreeterServer implementation. 71 | type GreeterHandler struct { 72 | ctx context.Context 73 | workers *nrpc.WorkerPool 74 | nc nrpc.NatsConn 75 | server GreeterServer 76 | 77 | encodings []string 78 | } 79 | 80 | func NewGreeterHandler(ctx context.Context, nc nrpc.NatsConn, s GreeterServer) *GreeterHandler { 81 | return &GreeterHandler{ 82 | ctx: ctx, 83 | nc: nc, 84 | server: s, 85 | 86 | encodings: []string{"protobuf"}, 87 | } 88 | } 89 | 90 | func NewGreeterConcurrentHandler(workers *nrpc.WorkerPool, nc nrpc.NatsConn, s GreeterServer) *GreeterHandler { 91 | return &GreeterHandler{ 92 | workers: workers, 93 | nc: nc, 94 | server: s, 95 | } 96 | } 97 | 98 | // SetEncodings sets the output encodings when using a '*Publish' function 99 | func (h *GreeterHandler) SetEncodings(encodings []string) { 100 | h.encodings = encodings 101 | } 102 | 103 | func (h *GreeterHandler) Subject() string { 104 | return "Greeter.>" 105 | } 106 | 107 | func (h *GreeterHandler) Handler(msg *nats.Msg) { 108 | var ctx context.Context 109 | if h.workers != nil { 110 | ctx = h.workers.Context 111 | } else { 112 | ctx = h.ctx 113 | } 114 | request := nrpc.NewRequest(ctx, h.nc, msg.Subject, msg.Reply) 115 | // extract method name & encoding from subject 116 | _, _, name, tail, err := nrpc.ParseSubject( 117 | "", 0, "Greeter", 0, msg.Subject) 118 | if err != nil { 119 | log.Printf("GreeterHanlder: Greeter subject parsing failed: %v", err) 120 | return 121 | } 122 | 123 | request.MethodName = name 124 | request.SubjectTail = tail 125 | 126 | // call handler and form response 127 | var immediateError *nrpc.Error 128 | switch name { 129 | case "SayHello": 130 | _, request.Encoding, err = nrpc.ParseSubjectTail(0, request.SubjectTail) 131 | if err != nil { 132 | log.Printf("SayHelloHanlder: SayHello subject parsing failed: %v", err) 133 | break 134 | } 135 | var req HelloRequest 136 | if err := nrpc.Unmarshal(request.Encoding, msg.Data, &req); err != nil { 137 | log.Printf("SayHelloHandler: SayHello request unmarshal failed: %v", err) 138 | immediateError = &nrpc.Error{ 139 | Type: nrpc.Error_CLIENT, 140 | Message: "bad request received: " + err.Error(), 141 | } 142 | serverRequestsForGreeter.WithLabelValues( 143 | "SayHello", request.Encoding, "unmarshal_fail").Inc() 144 | } else { 145 | request.Handler = func(ctx context.Context)(proto.Message, error){ 146 | innerResp, err := h.server.SayHello(ctx, req) 147 | if err != nil { 148 | return nil, err 149 | } 150 | return &innerResp, err 151 | } 152 | } 153 | default: 154 | log.Printf("GreeterHandler: unknown name %q", name) 155 | immediateError = &nrpc.Error{ 156 | Type: nrpc.Error_CLIENT, 157 | Message: "unknown name: " + name, 158 | } 159 | serverRequestsForGreeter.WithLabelValues( 160 | "Greeter", request.Encoding, "name_fail").Inc() 161 | } 162 | request.AfterReply = func(request *nrpc.Request, success, replySuccess bool) { 163 | if !replySuccess { 164 | serverRequestsForGreeter.WithLabelValues( 165 | request.MethodName, request.Encoding, "sendreply_fail").Inc() 166 | } 167 | if success { 168 | serverRequestsForGreeter.WithLabelValues( 169 | request.MethodName, request.Encoding, "success").Inc() 170 | } else { 171 | serverRequestsForGreeter.WithLabelValues( 172 | request.MethodName, request.Encoding, "handler_fail").Inc() 173 | } 174 | // report metric to Prometheus 175 | serverHETForGreeter.WithLabelValues(request.MethodName).Observe( 176 | request.Elapsed().Seconds()) 177 | } 178 | if immediateError == nil { 179 | if h.workers != nil { 180 | // Try queuing the request 181 | if err := h.workers.QueueRequest(request); err != nil { 182 | log.Printf("nrpc: Error queuing the request: %s", err) 183 | } 184 | } else { 185 | // Run the handler synchronously 186 | request.RunAndReply() 187 | } 188 | } 189 | 190 | if immediateError != nil { 191 | if err := request.SendReply(nil, immediateError); err != nil { 192 | log.Printf("GreeterHandler: Greeter handler failed to publish the response: %s", err) 193 | serverRequestsForGreeter.WithLabelValues( 194 | request.MethodName, request.Encoding, "handler_fail").Inc() 195 | } 196 | serverHETForGreeter.WithLabelValues(request.MethodName).Observe( 197 | request.Elapsed().Seconds()) 198 | } else { 199 | } 200 | } 201 | 202 | type GreeterClient struct { 203 | nc nrpc.NatsConn 204 | Subject string 205 | Encoding string 206 | Timeout time.Duration 207 | } 208 | 209 | func NewGreeterClient(nc nrpc.NatsConn) *GreeterClient { 210 | return &GreeterClient{ 211 | nc: nc, 212 | Subject: "Greeter", 213 | Encoding: "protobuf", 214 | Timeout: 5 * time.Second, 215 | } 216 | } 217 | 218 | func (c *GreeterClient) SayHello(req HelloRequest) (resp HelloReply, err error) { 219 | start := time.Now() 220 | 221 | subject := c.Subject + "." + "SayHello" 222 | 223 | // call 224 | err = nrpc.Call(&req, &resp, c.nc, subject, c.Encoding, c.Timeout) 225 | if err != nil { 226 | clientCallsForGreeter.WithLabelValues( 227 | "SayHello", c.Encoding, "call_fail").Inc() 228 | return // already logged 229 | } 230 | 231 | // report total time taken to Prometheus 232 | elapsed := time.Since(start).Seconds() 233 | clientRCTForGreeter.WithLabelValues("SayHello").Observe(elapsed) 234 | clientCallsForGreeter.WithLabelValues( 235 | "SayHello", c.Encoding, "success").Inc() 236 | 237 | return 238 | } 239 | 240 | type Client struct { 241 | nc nrpc.NatsConn 242 | defaultEncoding string 243 | defaultTimeout time.Duration 244 | Greeter *GreeterClient 245 | } 246 | 247 | func NewClient(nc nrpc.NatsConn) *Client { 248 | c := Client{ 249 | nc: nc, 250 | defaultEncoding: "protobuf", 251 | defaultTimeout: 5*time.Second, 252 | } 253 | c.Greeter = NewGreeterClient(nc) 254 | return &c 255 | } 256 | 257 | func (c *Client) SetEncoding(encoding string) { 258 | c.defaultEncoding = encoding 259 | if c.Greeter != nil { 260 | c.Greeter.Encoding = encoding 261 | } 262 | } 263 | 264 | func (c *Client) SetTimeout(t time.Duration) { 265 | c.defaultTimeout = t 266 | if c.Greeter != nil { 267 | c.Greeter.Timeout = t 268 | } 269 | } 270 | 271 | func init() { 272 | // register metrics for service Greeter 273 | prometheus.MustRegister(clientRCTForGreeter) 274 | prometheus.MustRegister(serverHETForGreeter) 275 | prometheus.MustRegister(clientCallsForGreeter) 276 | prometheus.MustRegister(serverRequestsForGreeter) 277 | } -------------------------------------------------------------------------------- /examples/metrics_helloworld/helloworld/helloworld.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by protoc-gen-go. DO NOT EDIT. 16 | // versions: 17 | // protoc-gen-go v1.29.0 18 | // protoc v4.22.2 19 | // source: helloworld.proto 20 | 21 | package helloworld 22 | 23 | import ( 24 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 25 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 26 | reflect "reflect" 27 | sync "sync" 28 | ) 29 | 30 | const ( 31 | // Verify that this generated code is sufficiently up-to-date. 32 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 33 | // Verify that runtime/protoimpl is sufficiently up-to-date. 34 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 35 | ) 36 | 37 | // The request message containing the user's name. 38 | type HelloRequest struct { 39 | state protoimpl.MessageState 40 | sizeCache protoimpl.SizeCache 41 | unknownFields protoimpl.UnknownFields 42 | 43 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 44 | } 45 | 46 | func (x *HelloRequest) Reset() { 47 | *x = HelloRequest{} 48 | if protoimpl.UnsafeEnabled { 49 | mi := &file_helloworld_proto_msgTypes[0] 50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 51 | ms.StoreMessageInfo(mi) 52 | } 53 | } 54 | 55 | func (x *HelloRequest) String() string { 56 | return protoimpl.X.MessageStringOf(x) 57 | } 58 | 59 | func (*HelloRequest) ProtoMessage() {} 60 | 61 | func (x *HelloRequest) ProtoReflect() protoreflect.Message { 62 | mi := &file_helloworld_proto_msgTypes[0] 63 | if protoimpl.UnsafeEnabled && x != nil { 64 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 65 | if ms.LoadMessageInfo() == nil { 66 | ms.StoreMessageInfo(mi) 67 | } 68 | return ms 69 | } 70 | return mi.MessageOf(x) 71 | } 72 | 73 | // Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead. 74 | func (*HelloRequest) Descriptor() ([]byte, []int) { 75 | return file_helloworld_proto_rawDescGZIP(), []int{0} 76 | } 77 | 78 | func (x *HelloRequest) GetName() string { 79 | if x != nil { 80 | return x.Name 81 | } 82 | return "" 83 | } 84 | 85 | // The response message containing the greetings 86 | type HelloReply struct { 87 | state protoimpl.MessageState 88 | sizeCache protoimpl.SizeCache 89 | unknownFields protoimpl.UnknownFields 90 | 91 | Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` 92 | } 93 | 94 | func (x *HelloReply) Reset() { 95 | *x = HelloReply{} 96 | if protoimpl.UnsafeEnabled { 97 | mi := &file_helloworld_proto_msgTypes[1] 98 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 99 | ms.StoreMessageInfo(mi) 100 | } 101 | } 102 | 103 | func (x *HelloReply) String() string { 104 | return protoimpl.X.MessageStringOf(x) 105 | } 106 | 107 | func (*HelloReply) ProtoMessage() {} 108 | 109 | func (x *HelloReply) ProtoReflect() protoreflect.Message { 110 | mi := &file_helloworld_proto_msgTypes[1] 111 | if protoimpl.UnsafeEnabled && x != nil { 112 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 113 | if ms.LoadMessageInfo() == nil { 114 | ms.StoreMessageInfo(mi) 115 | } 116 | return ms 117 | } 118 | return mi.MessageOf(x) 119 | } 120 | 121 | // Deprecated: Use HelloReply.ProtoReflect.Descriptor instead. 122 | func (*HelloReply) Descriptor() ([]byte, []int) { 123 | return file_helloworld_proto_rawDescGZIP(), []int{1} 124 | } 125 | 126 | func (x *HelloReply) GetMessage() string { 127 | if x != nil { 128 | return x.Message 129 | } 130 | return "" 131 | } 132 | 133 | var File_helloworld_proto protoreflect.FileDescriptor 134 | 135 | var file_helloworld_proto_rawDesc = []byte{ 136 | 0x0a, 0x10, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x70, 0x72, 0x6f, 137 | 0x74, 0x6f, 0x12, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x22, 0x22, 138 | 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 139 | 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 140 | 0x6d, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 141 | 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 142 | 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x49, 0x0a, 0x07, 0x47, 0x72, 143 | 0x65, 0x65, 0x74, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 144 | 0x6f, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 145 | 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 146 | 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 147 | 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x71, 0x0a, 0x1b, 0x69, 0x6f, 0x2e, 0x67, 0x72, 0x70, 0x63, 148 | 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 149 | 0x6f, 0x72, 0x6c, 0x64, 0x42, 0x0f, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64, 150 | 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 151 | 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x61, 0x74, 0x73, 0x2d, 0x72, 0x70, 0x63, 0x2f, 0x6e, 0x72, 0x70, 152 | 0x63, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 153 | 0x63, 0x73, 0x5f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2f, 0x68, 0x65, 154 | 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 155 | } 156 | 157 | var ( 158 | file_helloworld_proto_rawDescOnce sync.Once 159 | file_helloworld_proto_rawDescData = file_helloworld_proto_rawDesc 160 | ) 161 | 162 | func file_helloworld_proto_rawDescGZIP() []byte { 163 | file_helloworld_proto_rawDescOnce.Do(func() { 164 | file_helloworld_proto_rawDescData = protoimpl.X.CompressGZIP(file_helloworld_proto_rawDescData) 165 | }) 166 | return file_helloworld_proto_rawDescData 167 | } 168 | 169 | var file_helloworld_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 170 | var file_helloworld_proto_goTypes = []interface{}{ 171 | (*HelloRequest)(nil), // 0: helloworld.HelloRequest 172 | (*HelloReply)(nil), // 1: helloworld.HelloReply 173 | } 174 | var file_helloworld_proto_depIdxs = []int32{ 175 | 0, // 0: helloworld.Greeter.SayHello:input_type -> helloworld.HelloRequest 176 | 1, // 1: helloworld.Greeter.SayHello:output_type -> helloworld.HelloReply 177 | 1, // [1:2] is the sub-list for method output_type 178 | 0, // [0:1] is the sub-list for method input_type 179 | 0, // [0:0] is the sub-list for extension type_name 180 | 0, // [0:0] is the sub-list for extension extendee 181 | 0, // [0:0] is the sub-list for field type_name 182 | } 183 | 184 | func init() { file_helloworld_proto_init() } 185 | func file_helloworld_proto_init() { 186 | if File_helloworld_proto != nil { 187 | return 188 | } 189 | if !protoimpl.UnsafeEnabled { 190 | file_helloworld_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 191 | switch v := v.(*HelloRequest); i { 192 | case 0: 193 | return &v.state 194 | case 1: 195 | return &v.sizeCache 196 | case 2: 197 | return &v.unknownFields 198 | default: 199 | return nil 200 | } 201 | } 202 | file_helloworld_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 203 | switch v := v.(*HelloReply); i { 204 | case 0: 205 | return &v.state 206 | case 1: 207 | return &v.sizeCache 208 | case 2: 209 | return &v.unknownFields 210 | default: 211 | return nil 212 | } 213 | } 214 | } 215 | type x struct{} 216 | out := protoimpl.TypeBuilder{ 217 | File: protoimpl.DescBuilder{ 218 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 219 | RawDescriptor: file_helloworld_proto_rawDesc, 220 | NumEnums: 0, 221 | NumMessages: 2, 222 | NumExtensions: 0, 223 | NumServices: 1, 224 | }, 225 | GoTypes: file_helloworld_proto_goTypes, 226 | DependencyIndexes: file_helloworld_proto_depIdxs, 227 | MessageInfos: file_helloworld_proto_msgTypes, 228 | }.Build() 229 | File_helloworld_proto = out.File 230 | file_helloworld_proto_rawDesc = nil 231 | file_helloworld_proto_goTypes = nil 232 | file_helloworld_proto_depIdxs = nil 233 | } 234 | -------------------------------------------------------------------------------- /examples/metrics_helloworld/helloworld/helloworld.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | option java_multiple_files = true; 18 | option java_package = "io.grpc.examples.helloworld"; 19 | option java_outer_classname = "HelloWorldProto"; 20 | 21 | package helloworld; 22 | 23 | option go_package = "github.com/nats-rpc/nrpc/examples/metrics_helloworld/helloworld"; 24 | 25 | // The greeting service definition. 26 | service Greeter { 27 | // Sends a greeting 28 | rpc SayHello (HelloRequest) returns (HelloReply) {} 29 | } 30 | 31 | // The request message containing the user's name. 32 | message HelloRequest { 33 | string name = 1; 34 | } 35 | 36 | // The response message containing the greetings 37 | message HelloReply { 38 | string message = 1; 39 | } 40 | -------------------------------------------------------------------------------- /examples/metrics_helloworld/metrics_greeter_client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/nats-io/nats.go" 10 | 11 | // This is the package containing the generated *.pb.go and *.nrpc.go 12 | // files. 13 | "github.com/nats-rpc/nrpc/examples/metrics_helloworld/helloworld" 14 | 15 | // If you've used the prometheus plugin when generating the code, you 16 | // can import the HTTP handler of Prometheus to serve up the metrics. 17 | "github.com/prometheus/client_golang/prometheus/promhttp" 18 | ) 19 | 20 | func main() { 21 | // Connect to the NATS server. 22 | nc, err := nats.Connect(nats.DefaultURL, nats.Timeout(5*time.Second)) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | defer nc.Close() 27 | 28 | // This is our generated client. 29 | cli := helloworld.NewGreeterClient(nc) 30 | 31 | // Contact the server and print out its response. 32 | resp, err := cli.SayHello(helloworld.HelloRequest{Name: "world"}) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | 37 | // print 38 | fmt.Printf("Greeting: %s\n", resp.Message) 39 | 40 | // Do this block only if you generated the code with the prometheus plugin. 41 | fmt.Println("Check metrics at http://localhost:6061/metrics. Hit ^C to exit.") 42 | http.Handle("/metrics", promhttp.Handler()) 43 | http.ListenAndServe(":6061", nil) 44 | } 45 | -------------------------------------------------------------------------------- /examples/metrics_helloworld/metrics_greeter_server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "math/rand" 9 | "net/http" 10 | "os" 11 | "os/signal" 12 | "time" 13 | 14 | "github.com/nats-io/nats.go" 15 | 16 | // This is the package containing the generated *.pb.go and *.nrpc.go 17 | // files. 18 | "github.com/nats-rpc/nrpc/examples/metrics_helloworld/helloworld" 19 | 20 | // If you've used the prometheus plugin when generating the code, you 21 | // can import the HTTP handler of Prometheus to serve up the metrics. 22 | "github.com/prometheus/client_golang/prometheus/promhttp" 23 | ) 24 | 25 | // server implements the helloworld.GreeterServer interface. 26 | type server struct{} 27 | 28 | // SayHello is an implementation of the SayHello method from the definition of 29 | // the Greeter service. 30 | func (s *server) SayHello(ctx context.Context, req helloworld.HelloRequest) (resp helloworld.HelloReply, err error) { 31 | resp.Message = "Hello " + req.Name 32 | if rand.Intn(10) < 7 { // will fail 70% of the time 33 | err = errors.New("random failure simulated") 34 | } 35 | time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) // random delay 36 | return 37 | } 38 | 39 | func main() { 40 | // Connect to the NATS server. 41 | nc, err := nats.Connect(nats.DefaultURL, nats.Timeout(5*time.Second)) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | defer nc.Close() 46 | 47 | // Our server implementation. 48 | s := &server{} 49 | rand.Seed(time.Now().UnixNano()) 50 | 51 | // The NATS handler from the helloworld.nrpc.proto file. 52 | h := helloworld.NewGreeterHandler(context.TODO(), nc, s) 53 | 54 | // Start a NATS subscription using the handler. You can also use the 55 | // QueueSubscribe() method for a load-balanced set of servers. 56 | sub, err := nc.Subscribe(h.Subject(), h.Handler) 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | defer sub.Unsubscribe() 61 | 62 | // Do this block only if you generated the code with the prometheus plugin. 63 | http.Handle("/metrics", promhttp.Handler()) 64 | go http.ListenAndServe(":6060", nil) 65 | 66 | // Keep running until ^C. 67 | fmt.Println("server is running, ^C quits.") 68 | c := make(chan os.Signal, 1) 69 | signal.Notify(c, os.Interrupt) 70 | <-c 71 | close(c) 72 | } 73 | -------------------------------------------------------------------------------- /examples/nooption/nooption.go: -------------------------------------------------------------------------------- 1 | package nooption 2 | 3 | //go:generate protoc --go_out . --go_opt=paths=source_relative --nrpc_out . --nrpc_opt=paths=source_relative nooption.proto 4 | -------------------------------------------------------------------------------- /examples/nooption/nooption.nrpc.go: -------------------------------------------------------------------------------- 1 | // This code was autogenerated from nooption.proto, do not edit. 2 | package nooption 3 | 4 | import ( 5 | "context" 6 | "log" 7 | "time" 8 | 9 | "google.golang.org/protobuf/proto" 10 | "github.com/nats-io/nats.go" 11 | "github.com/nats-rpc/nrpc" 12 | ) 13 | 14 | // GreeterServer is the interface that providers of the service 15 | // Greeter should implement. 16 | type GreeterServer interface { 17 | SayHello(ctx context.Context, req HelloRequest) (resp HelloReply, err error) 18 | } 19 | 20 | // GreeterHandler provides a NATS subscription handler that can serve a 21 | // subscription using a given GreeterServer implementation. 22 | type GreeterHandler struct { 23 | ctx context.Context 24 | workers *nrpc.WorkerPool 25 | nc nrpc.NatsConn 26 | server GreeterServer 27 | 28 | encodings []string 29 | } 30 | 31 | func NewGreeterHandler(ctx context.Context, nc nrpc.NatsConn, s GreeterServer) *GreeterHandler { 32 | return &GreeterHandler{ 33 | ctx: ctx, 34 | nc: nc, 35 | server: s, 36 | 37 | encodings: []string{"protobuf"}, 38 | } 39 | } 40 | 41 | func NewGreeterConcurrentHandler(workers *nrpc.WorkerPool, nc nrpc.NatsConn, s GreeterServer) *GreeterHandler { 42 | return &GreeterHandler{ 43 | workers: workers, 44 | nc: nc, 45 | server: s, 46 | } 47 | } 48 | 49 | // SetEncodings sets the output encodings when using a '*Publish' function 50 | func (h *GreeterHandler) SetEncodings(encodings []string) { 51 | h.encodings = encodings 52 | } 53 | 54 | func (h *GreeterHandler) Subject() string { 55 | return "Greeter.>" 56 | } 57 | 58 | func (h *GreeterHandler) Handler(msg *nats.Msg) { 59 | var ctx context.Context 60 | if h.workers != nil { 61 | ctx = h.workers.Context 62 | } else { 63 | ctx = h.ctx 64 | } 65 | request := nrpc.NewRequest(ctx, h.nc, msg.Subject, msg.Reply) 66 | // extract method name & encoding from subject 67 | _, _, name, tail, err := nrpc.ParseSubject( 68 | "", 0, "Greeter", 0, msg.Subject) 69 | if err != nil { 70 | log.Printf("GreeterHanlder: Greeter subject parsing failed: %v", err) 71 | return 72 | } 73 | 74 | request.MethodName = name 75 | request.SubjectTail = tail 76 | 77 | // call handler and form response 78 | var immediateError *nrpc.Error 79 | switch name { 80 | case "SayHello": 81 | _, request.Encoding, err = nrpc.ParseSubjectTail(0, request.SubjectTail) 82 | if err != nil { 83 | log.Printf("SayHelloHanlder: SayHello subject parsing failed: %v", err) 84 | break 85 | } 86 | var req HelloRequest 87 | if err := nrpc.Unmarshal(request.Encoding, msg.Data, &req); err != nil { 88 | log.Printf("SayHelloHandler: SayHello request unmarshal failed: %v", err) 89 | immediateError = &nrpc.Error{ 90 | Type: nrpc.Error_CLIENT, 91 | Message: "bad request received: " + err.Error(), 92 | } 93 | } else { 94 | request.Handler = func(ctx context.Context)(proto.Message, error){ 95 | innerResp, err := h.server.SayHello(ctx, req) 96 | if err != nil { 97 | return nil, err 98 | } 99 | return &innerResp, err 100 | } 101 | } 102 | default: 103 | log.Printf("GreeterHandler: unknown name %q", name) 104 | immediateError = &nrpc.Error{ 105 | Type: nrpc.Error_CLIENT, 106 | Message: "unknown name: " + name, 107 | } 108 | } 109 | if immediateError == nil { 110 | if h.workers != nil { 111 | // Try queuing the request 112 | if err := h.workers.QueueRequest(request); err != nil { 113 | log.Printf("nrpc: Error queuing the request: %s", err) 114 | } 115 | } else { 116 | // Run the handler synchronously 117 | request.RunAndReply() 118 | } 119 | } 120 | 121 | if immediateError != nil { 122 | if err := request.SendReply(nil, immediateError); err != nil { 123 | log.Printf("GreeterHandler: Greeter handler failed to publish the response: %s", err) 124 | } 125 | } else { 126 | } 127 | } 128 | 129 | type GreeterClient struct { 130 | nc nrpc.NatsConn 131 | Subject string 132 | Encoding string 133 | Timeout time.Duration 134 | } 135 | 136 | func NewGreeterClient(nc nrpc.NatsConn) *GreeterClient { 137 | return &GreeterClient{ 138 | nc: nc, 139 | Subject: "Greeter", 140 | Encoding: "protobuf", 141 | Timeout: 5 * time.Second, 142 | } 143 | } 144 | 145 | func (c *GreeterClient) SayHello(req HelloRequest) (resp HelloReply, err error) { 146 | 147 | subject := c.Subject + "." + "SayHello" 148 | 149 | // call 150 | err = nrpc.Call(&req, &resp, c.nc, subject, c.Encoding, c.Timeout) 151 | if err != nil { 152 | return // already logged 153 | } 154 | 155 | return 156 | } 157 | 158 | type Client struct { 159 | nc nrpc.NatsConn 160 | defaultEncoding string 161 | defaultTimeout time.Duration 162 | Greeter *GreeterClient 163 | } 164 | 165 | func NewClient(nc nrpc.NatsConn) *Client { 166 | c := Client{ 167 | nc: nc, 168 | defaultEncoding: "protobuf", 169 | defaultTimeout: 5*time.Second, 170 | } 171 | c.Greeter = NewGreeterClient(nc) 172 | return &c 173 | } 174 | 175 | func (c *Client) SetEncoding(encoding string) { 176 | c.defaultEncoding = encoding 177 | if c.Greeter != nil { 178 | c.Greeter.Encoding = encoding 179 | } 180 | } 181 | 182 | func (c *Client) SetTimeout(t time.Duration) { 183 | c.defaultTimeout = t 184 | if c.Greeter != nil { 185 | c.Greeter.Timeout = t 186 | } 187 | } -------------------------------------------------------------------------------- /examples/nooption/nooption.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.29.0 4 | // protoc v4.22.2 5 | // source: nooption.proto 6 | 7 | package nooption 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | // The request message containing the user's name. 24 | type HelloRequest struct { 25 | state protoimpl.MessageState 26 | sizeCache protoimpl.SizeCache 27 | unknownFields protoimpl.UnknownFields 28 | 29 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 30 | } 31 | 32 | func (x *HelloRequest) Reset() { 33 | *x = HelloRequest{} 34 | if protoimpl.UnsafeEnabled { 35 | mi := &file_nooption_proto_msgTypes[0] 36 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 37 | ms.StoreMessageInfo(mi) 38 | } 39 | } 40 | 41 | func (x *HelloRequest) String() string { 42 | return protoimpl.X.MessageStringOf(x) 43 | } 44 | 45 | func (*HelloRequest) ProtoMessage() {} 46 | 47 | func (x *HelloRequest) ProtoReflect() protoreflect.Message { 48 | mi := &file_nooption_proto_msgTypes[0] 49 | if protoimpl.UnsafeEnabled && x != nil { 50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 51 | if ms.LoadMessageInfo() == nil { 52 | ms.StoreMessageInfo(mi) 53 | } 54 | return ms 55 | } 56 | return mi.MessageOf(x) 57 | } 58 | 59 | // Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead. 60 | func (*HelloRequest) Descriptor() ([]byte, []int) { 61 | return file_nooption_proto_rawDescGZIP(), []int{0} 62 | } 63 | 64 | func (x *HelloRequest) GetName() string { 65 | if x != nil { 66 | return x.Name 67 | } 68 | return "" 69 | } 70 | 71 | // The response message containing the greetings 72 | type HelloReply struct { 73 | state protoimpl.MessageState 74 | sizeCache protoimpl.SizeCache 75 | unknownFields protoimpl.UnknownFields 76 | 77 | Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` 78 | } 79 | 80 | func (x *HelloReply) Reset() { 81 | *x = HelloReply{} 82 | if protoimpl.UnsafeEnabled { 83 | mi := &file_nooption_proto_msgTypes[1] 84 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 85 | ms.StoreMessageInfo(mi) 86 | } 87 | } 88 | 89 | func (x *HelloReply) String() string { 90 | return protoimpl.X.MessageStringOf(x) 91 | } 92 | 93 | func (*HelloReply) ProtoMessage() {} 94 | 95 | func (x *HelloReply) ProtoReflect() protoreflect.Message { 96 | mi := &file_nooption_proto_msgTypes[1] 97 | if protoimpl.UnsafeEnabled && x != nil { 98 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 99 | if ms.LoadMessageInfo() == nil { 100 | ms.StoreMessageInfo(mi) 101 | } 102 | return ms 103 | } 104 | return mi.MessageOf(x) 105 | } 106 | 107 | // Deprecated: Use HelloReply.ProtoReflect.Descriptor instead. 108 | func (*HelloReply) Descriptor() ([]byte, []int) { 109 | return file_nooption_proto_rawDescGZIP(), []int{1} 110 | } 111 | 112 | func (x *HelloReply) GetMessage() string { 113 | if x != nil { 114 | return x.Message 115 | } 116 | return "" 117 | } 118 | 119 | var File_nooption_proto protoreflect.FileDescriptor 120 | 121 | var file_nooption_proto_rawDesc = []byte{ 122 | 0x0a, 0x0e, 0x6e, 0x6f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 123 | 0x12, 0x08, 0x6e, 0x6f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, 124 | 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 125 | 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26, 126 | 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 127 | 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 128 | 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x45, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 129 | 0x72, 0x12, 0x3a, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x16, 0x2e, 130 | 0x6e, 0x6f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 131 | 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6e, 0x6f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 132 | 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x2c, 0x5a, 133 | 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x61, 0x74, 0x73, 134 | 0x2d, 0x72, 0x70, 0x63, 0x2f, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 135 | 0x65, 0x73, 0x2f, 0x6e, 0x6f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 136 | 0x74, 0x6f, 0x33, 137 | } 138 | 139 | var ( 140 | file_nooption_proto_rawDescOnce sync.Once 141 | file_nooption_proto_rawDescData = file_nooption_proto_rawDesc 142 | ) 143 | 144 | func file_nooption_proto_rawDescGZIP() []byte { 145 | file_nooption_proto_rawDescOnce.Do(func() { 146 | file_nooption_proto_rawDescData = protoimpl.X.CompressGZIP(file_nooption_proto_rawDescData) 147 | }) 148 | return file_nooption_proto_rawDescData 149 | } 150 | 151 | var file_nooption_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 152 | var file_nooption_proto_goTypes = []interface{}{ 153 | (*HelloRequest)(nil), // 0: nooption.HelloRequest 154 | (*HelloReply)(nil), // 1: nooption.HelloReply 155 | } 156 | var file_nooption_proto_depIdxs = []int32{ 157 | 0, // 0: nooption.Greeter.SayHello:input_type -> nooption.HelloRequest 158 | 1, // 1: nooption.Greeter.SayHello:output_type -> nooption.HelloReply 159 | 1, // [1:2] is the sub-list for method output_type 160 | 0, // [0:1] is the sub-list for method input_type 161 | 0, // [0:0] is the sub-list for extension type_name 162 | 0, // [0:0] is the sub-list for extension extendee 163 | 0, // [0:0] is the sub-list for field type_name 164 | } 165 | 166 | func init() { file_nooption_proto_init() } 167 | func file_nooption_proto_init() { 168 | if File_nooption_proto != nil { 169 | return 170 | } 171 | if !protoimpl.UnsafeEnabled { 172 | file_nooption_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 173 | switch v := v.(*HelloRequest); i { 174 | case 0: 175 | return &v.state 176 | case 1: 177 | return &v.sizeCache 178 | case 2: 179 | return &v.unknownFields 180 | default: 181 | return nil 182 | } 183 | } 184 | file_nooption_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 185 | switch v := v.(*HelloReply); i { 186 | case 0: 187 | return &v.state 188 | case 1: 189 | return &v.sizeCache 190 | case 2: 191 | return &v.unknownFields 192 | default: 193 | return nil 194 | } 195 | } 196 | } 197 | type x struct{} 198 | out := protoimpl.TypeBuilder{ 199 | File: protoimpl.DescBuilder{ 200 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 201 | RawDescriptor: file_nooption_proto_rawDesc, 202 | NumEnums: 0, 203 | NumMessages: 2, 204 | NumExtensions: 0, 205 | NumServices: 1, 206 | }, 207 | GoTypes: file_nooption_proto_goTypes, 208 | DependencyIndexes: file_nooption_proto_depIdxs, 209 | MessageInfos: file_nooption_proto_msgTypes, 210 | }.Build() 211 | File_nooption_proto = out.File 212 | file_nooption_proto_rawDesc = nil 213 | file_nooption_proto_goTypes = nil 214 | file_nooption_proto_depIdxs = nil 215 | } 216 | -------------------------------------------------------------------------------- /examples/nooption/nooption.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package nooption; 4 | 5 | option go_package = "github.com/nats-rpc/nrpc/examples/nooption"; 6 | 7 | service Greeter { 8 | // Sends a greeting 9 | rpc SayHello (HelloRequest) returns (HelloReply) {} 10 | } 11 | 12 | // The request message containing the user's name. 13 | message HelloRequest { 14 | string name = 1; 15 | } 16 | 17 | // The response message containing the greetings 18 | message HelloReply { 19 | string message = 1; 20 | } 21 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nats-rpc/nrpc 2 | 3 | go 1.11 4 | 5 | require ( 6 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 7 | github.com/golang/protobuf v1.5.3 // indirect 8 | github.com/nats-io/nats-server/v2 v2.9.23 9 | github.com/nats-io/nats.go v1.28.0 10 | github.com/prometheus/client_golang v1.14.0 11 | github.com/prometheus/common v0.42.0 // indirect 12 | github.com/prometheus/procfs v0.9.0 // indirect 13 | github.com/stretchr/testify v1.8.1 14 | google.golang.org/protobuf v1.33.0 15 | ) 16 | -------------------------------------------------------------------------------- /helloworld_test.go: -------------------------------------------------------------------------------- 1 | package nrpc 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "os/exec" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestHelloWorldExample(t *testing.T) { 12 | // make sure protoc-gen-nrpc is up to date 13 | installGenRPC := exec.Command("go", "install", "./protoc-gen-nrpc") 14 | if out, err := installGenRPC.CombinedOutput(); err != nil { 15 | t.Fatal("Install protoc-gen-nrpc failed", err, ":\n", string(out)) 16 | } 17 | // generate the sources 18 | generate := exec.Command("go", "generate", "./examples/helloworld/helloworld") 19 | if out, err := generate.CombinedOutput(); err != nil { 20 | t.Fatal("Generate failed", err, ":\n", string(out)) 21 | } 22 | // build 23 | buildServer := exec.Command("go", "build", 24 | "-o", "./examples/helloworld/greeter_server/greeter_server", 25 | "./examples/helloworld/greeter_server") 26 | if out, err := buildServer.CombinedOutput(); err != nil { 27 | t.Fatal("Buid server failed", err, string(out)) 28 | } 29 | buildClient := exec.Command("go", "build", 30 | "-o", "./examples/helloworld/greeter_client/greeter_client", 31 | "./examples/helloworld/greeter_client") 32 | if out, err := buildClient.CombinedOutput(); err != nil { 33 | t.Fatal("Buid client failed", err, string(out)) 34 | } 35 | // run the server 36 | server := exec.Command("./examples/helloworld/greeter_server/greeter_server", NatsURL) 37 | var serverStdout bytes.Buffer 38 | server.Stdout = &serverStdout 39 | server.Start() 40 | defer func() { 41 | if server.Process != nil { 42 | server.Process.Signal(os.Interrupt) 43 | } 44 | if err := server.Wait(); err != nil { 45 | t.Error("Server run failed:", err) 46 | t.Error("Server output:", serverStdout.String()) 47 | } 48 | }() 49 | 50 | // Give the server a little time to be ready to handle requests 51 | time.Sleep(250 * time.Millisecond) 52 | 53 | // run the client and check its output 54 | client := exec.Command("./examples/helloworld/greeter_client/greeter_client", NatsURL) 55 | timeout := time.AfterFunc(time.Second, func() { client.Process.Kill() }) 56 | out, err := client.CombinedOutput() 57 | timeout.Stop() 58 | if err != nil { 59 | t.Fatal("Run client failed with:", err, ", output was:\n", string(out)) 60 | } 61 | expectedOuput := "Greeting: Hello world\n" 62 | if string(out) != expectedOuput { 63 | t.Errorf("Wrong client output. Expected '%s', got '%s'", 64 | expectedOuput, string(out)) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /nrpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.29.0 4 | // protoc v4.22.2 5 | // source: nrpc.proto 6 | 7 | package nrpc 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | descriptorpb "google.golang.org/protobuf/types/descriptorpb" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | type SubjectRule int32 25 | 26 | const ( 27 | SubjectRule_COPY SubjectRule = 0 28 | SubjectRule_TOLOWER SubjectRule = 1 29 | ) 30 | 31 | // Enum value maps for SubjectRule. 32 | var ( 33 | SubjectRule_name = map[int32]string{ 34 | 0: "COPY", 35 | 1: "TOLOWER", 36 | } 37 | SubjectRule_value = map[string]int32{ 38 | "COPY": 0, 39 | "TOLOWER": 1, 40 | } 41 | ) 42 | 43 | func (x SubjectRule) Enum() *SubjectRule { 44 | p := new(SubjectRule) 45 | *p = x 46 | return p 47 | } 48 | 49 | func (x SubjectRule) String() string { 50 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 51 | } 52 | 53 | func (SubjectRule) Descriptor() protoreflect.EnumDescriptor { 54 | return file_nrpc_proto_enumTypes[0].Descriptor() 55 | } 56 | 57 | func (SubjectRule) Type() protoreflect.EnumType { 58 | return &file_nrpc_proto_enumTypes[0] 59 | } 60 | 61 | func (x SubjectRule) Number() protoreflect.EnumNumber { 62 | return protoreflect.EnumNumber(x) 63 | } 64 | 65 | // Deprecated: Use SubjectRule.Descriptor instead. 66 | func (SubjectRule) EnumDescriptor() ([]byte, []int) { 67 | return file_nrpc_proto_rawDescGZIP(), []int{0} 68 | } 69 | 70 | type Error_Type int32 71 | 72 | const ( 73 | Error_CLIENT Error_Type = 0 74 | Error_SERVER Error_Type = 1 75 | Error_EOS Error_Type = 3 76 | Error_SERVERTOOBUSY Error_Type = 4 77 | ) 78 | 79 | // Enum value maps for Error_Type. 80 | var ( 81 | Error_Type_name = map[int32]string{ 82 | 0: "CLIENT", 83 | 1: "SERVER", 84 | 3: "EOS", 85 | 4: "SERVERTOOBUSY", 86 | } 87 | Error_Type_value = map[string]int32{ 88 | "CLIENT": 0, 89 | "SERVER": 1, 90 | "EOS": 3, 91 | "SERVERTOOBUSY": 4, 92 | } 93 | ) 94 | 95 | func (x Error_Type) Enum() *Error_Type { 96 | p := new(Error_Type) 97 | *p = x 98 | return p 99 | } 100 | 101 | func (x Error_Type) String() string { 102 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 103 | } 104 | 105 | func (Error_Type) Descriptor() protoreflect.EnumDescriptor { 106 | return file_nrpc_proto_enumTypes[1].Descriptor() 107 | } 108 | 109 | func (Error_Type) Type() protoreflect.EnumType { 110 | return &file_nrpc_proto_enumTypes[1] 111 | } 112 | 113 | func (x Error_Type) Number() protoreflect.EnumNumber { 114 | return protoreflect.EnumNumber(x) 115 | } 116 | 117 | // Deprecated: Use Error_Type.Descriptor instead. 118 | func (Error_Type) EnumDescriptor() ([]byte, []int) { 119 | return file_nrpc_proto_rawDescGZIP(), []int{0, 0} 120 | } 121 | 122 | type Error struct { 123 | state protoimpl.MessageState 124 | sizeCache protoimpl.SizeCache 125 | unknownFields protoimpl.UnknownFields 126 | 127 | Type Error_Type `protobuf:"varint,1,opt,name=type,proto3,enum=nrpc.Error_Type" json:"type,omitempty"` 128 | Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` 129 | MsgCount uint32 `protobuf:"varint,3,opt,name=msgCount,proto3" json:"msgCount,omitempty"` 130 | } 131 | 132 | func (x *Error) Reset() { 133 | *x = Error{} 134 | if protoimpl.UnsafeEnabled { 135 | mi := &file_nrpc_proto_msgTypes[0] 136 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 137 | ms.StoreMessageInfo(mi) 138 | } 139 | } 140 | 141 | func (x *Error) String() string { 142 | return protoimpl.X.MessageStringOf(x) 143 | } 144 | 145 | func (*Error) ProtoMessage() {} 146 | 147 | func (x *Error) ProtoReflect() protoreflect.Message { 148 | mi := &file_nrpc_proto_msgTypes[0] 149 | if protoimpl.UnsafeEnabled && x != nil { 150 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 151 | if ms.LoadMessageInfo() == nil { 152 | ms.StoreMessageInfo(mi) 153 | } 154 | return ms 155 | } 156 | return mi.MessageOf(x) 157 | } 158 | 159 | // Deprecated: Use Error.ProtoReflect.Descriptor instead. 160 | func (*Error) Descriptor() ([]byte, []int) { 161 | return file_nrpc_proto_rawDescGZIP(), []int{0} 162 | } 163 | 164 | func (x *Error) GetType() Error_Type { 165 | if x != nil { 166 | return x.Type 167 | } 168 | return Error_CLIENT 169 | } 170 | 171 | func (x *Error) GetMessage() string { 172 | if x != nil { 173 | return x.Message 174 | } 175 | return "" 176 | } 177 | 178 | func (x *Error) GetMsgCount() uint32 { 179 | if x != nil { 180 | return x.MsgCount 181 | } 182 | return 0 183 | } 184 | 185 | type Void struct { 186 | state protoimpl.MessageState 187 | sizeCache protoimpl.SizeCache 188 | unknownFields protoimpl.UnknownFields 189 | } 190 | 191 | func (x *Void) Reset() { 192 | *x = Void{} 193 | if protoimpl.UnsafeEnabled { 194 | mi := &file_nrpc_proto_msgTypes[1] 195 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 196 | ms.StoreMessageInfo(mi) 197 | } 198 | } 199 | 200 | func (x *Void) String() string { 201 | return protoimpl.X.MessageStringOf(x) 202 | } 203 | 204 | func (*Void) ProtoMessage() {} 205 | 206 | func (x *Void) ProtoReflect() protoreflect.Message { 207 | mi := &file_nrpc_proto_msgTypes[1] 208 | if protoimpl.UnsafeEnabled && x != nil { 209 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 210 | if ms.LoadMessageInfo() == nil { 211 | ms.StoreMessageInfo(mi) 212 | } 213 | return ms 214 | } 215 | return mi.MessageOf(x) 216 | } 217 | 218 | // Deprecated: Use Void.ProtoReflect.Descriptor instead. 219 | func (*Void) Descriptor() ([]byte, []int) { 220 | return file_nrpc_proto_rawDescGZIP(), []int{1} 221 | } 222 | 223 | type NoRequest struct { 224 | state protoimpl.MessageState 225 | sizeCache protoimpl.SizeCache 226 | unknownFields protoimpl.UnknownFields 227 | } 228 | 229 | func (x *NoRequest) Reset() { 230 | *x = NoRequest{} 231 | if protoimpl.UnsafeEnabled { 232 | mi := &file_nrpc_proto_msgTypes[2] 233 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 234 | ms.StoreMessageInfo(mi) 235 | } 236 | } 237 | 238 | func (x *NoRequest) String() string { 239 | return protoimpl.X.MessageStringOf(x) 240 | } 241 | 242 | func (*NoRequest) ProtoMessage() {} 243 | 244 | func (x *NoRequest) ProtoReflect() protoreflect.Message { 245 | mi := &file_nrpc_proto_msgTypes[2] 246 | if protoimpl.UnsafeEnabled && x != nil { 247 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 248 | if ms.LoadMessageInfo() == nil { 249 | ms.StoreMessageInfo(mi) 250 | } 251 | return ms 252 | } 253 | return mi.MessageOf(x) 254 | } 255 | 256 | // Deprecated: Use NoRequest.ProtoReflect.Descriptor instead. 257 | func (*NoRequest) Descriptor() ([]byte, []int) { 258 | return file_nrpc_proto_rawDescGZIP(), []int{2} 259 | } 260 | 261 | type NoReply struct { 262 | state protoimpl.MessageState 263 | sizeCache protoimpl.SizeCache 264 | unknownFields protoimpl.UnknownFields 265 | } 266 | 267 | func (x *NoReply) Reset() { 268 | *x = NoReply{} 269 | if protoimpl.UnsafeEnabled { 270 | mi := &file_nrpc_proto_msgTypes[3] 271 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 272 | ms.StoreMessageInfo(mi) 273 | } 274 | } 275 | 276 | func (x *NoReply) String() string { 277 | return protoimpl.X.MessageStringOf(x) 278 | } 279 | 280 | func (*NoReply) ProtoMessage() {} 281 | 282 | func (x *NoReply) ProtoReflect() protoreflect.Message { 283 | mi := &file_nrpc_proto_msgTypes[3] 284 | if protoimpl.UnsafeEnabled && x != nil { 285 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 286 | if ms.LoadMessageInfo() == nil { 287 | ms.StoreMessageInfo(mi) 288 | } 289 | return ms 290 | } 291 | return mi.MessageOf(x) 292 | } 293 | 294 | // Deprecated: Use NoReply.ProtoReflect.Descriptor instead. 295 | func (*NoReply) Descriptor() ([]byte, []int) { 296 | return file_nrpc_proto_rawDescGZIP(), []int{3} 297 | } 298 | 299 | type HeartBeat struct { 300 | state protoimpl.MessageState 301 | sizeCache protoimpl.SizeCache 302 | unknownFields protoimpl.UnknownFields 303 | 304 | Lastbeat bool `protobuf:"varint,1,opt,name=lastbeat,proto3" json:"lastbeat,omitempty"` 305 | } 306 | 307 | func (x *HeartBeat) Reset() { 308 | *x = HeartBeat{} 309 | if protoimpl.UnsafeEnabled { 310 | mi := &file_nrpc_proto_msgTypes[4] 311 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 312 | ms.StoreMessageInfo(mi) 313 | } 314 | } 315 | 316 | func (x *HeartBeat) String() string { 317 | return protoimpl.X.MessageStringOf(x) 318 | } 319 | 320 | func (*HeartBeat) ProtoMessage() {} 321 | 322 | func (x *HeartBeat) ProtoReflect() protoreflect.Message { 323 | mi := &file_nrpc_proto_msgTypes[4] 324 | if protoimpl.UnsafeEnabled && x != nil { 325 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 326 | if ms.LoadMessageInfo() == nil { 327 | ms.StoreMessageInfo(mi) 328 | } 329 | return ms 330 | } 331 | return mi.MessageOf(x) 332 | } 333 | 334 | // Deprecated: Use HeartBeat.ProtoReflect.Descriptor instead. 335 | func (*HeartBeat) Descriptor() ([]byte, []int) { 336 | return file_nrpc_proto_rawDescGZIP(), []int{4} 337 | } 338 | 339 | func (x *HeartBeat) GetLastbeat() bool { 340 | if x != nil { 341 | return x.Lastbeat 342 | } 343 | return false 344 | } 345 | 346 | var file_nrpc_proto_extTypes = []protoimpl.ExtensionInfo{ 347 | { 348 | ExtendedType: (*descriptorpb.FileOptions)(nil), 349 | ExtensionType: (*string)(nil), 350 | Field: 50000, 351 | Name: "nrpc.packageSubject", 352 | Tag: "bytes,50000,opt,name=packageSubject", 353 | Filename: "nrpc.proto", 354 | }, 355 | { 356 | ExtendedType: (*descriptorpb.FileOptions)(nil), 357 | ExtensionType: ([]string)(nil), 358 | Field: 50001, 359 | Name: "nrpc.packageSubjectParams", 360 | Tag: "bytes,50001,rep,name=packageSubjectParams", 361 | Filename: "nrpc.proto", 362 | }, 363 | { 364 | ExtendedType: (*descriptorpb.FileOptions)(nil), 365 | ExtensionType: (*SubjectRule)(nil), 366 | Field: 50002, 367 | Name: "nrpc.serviceSubjectRule", 368 | Tag: "varint,50002,opt,name=serviceSubjectRule,enum=nrpc.SubjectRule", 369 | Filename: "nrpc.proto", 370 | }, 371 | { 372 | ExtendedType: (*descriptorpb.FileOptions)(nil), 373 | ExtensionType: (*SubjectRule)(nil), 374 | Field: 50003, 375 | Name: "nrpc.methodSubjectRule", 376 | Tag: "varint,50003,opt,name=methodSubjectRule,enum=nrpc.SubjectRule", 377 | Filename: "nrpc.proto", 378 | }, 379 | { 380 | ExtendedType: (*descriptorpb.ServiceOptions)(nil), 381 | ExtensionType: (*string)(nil), 382 | Field: 51000, 383 | Name: "nrpc.serviceSubject", 384 | Tag: "bytes,51000,opt,name=serviceSubject", 385 | Filename: "nrpc.proto", 386 | }, 387 | { 388 | ExtendedType: (*descriptorpb.ServiceOptions)(nil), 389 | ExtensionType: ([]string)(nil), 390 | Field: 51001, 391 | Name: "nrpc.serviceSubjectParams", 392 | Tag: "bytes,51001,rep,name=serviceSubjectParams", 393 | Filename: "nrpc.proto", 394 | }, 395 | { 396 | ExtendedType: (*descriptorpb.MethodOptions)(nil), 397 | ExtensionType: (*string)(nil), 398 | Field: 52000, 399 | Name: "nrpc.methodSubject", 400 | Tag: "bytes,52000,opt,name=methodSubject", 401 | Filename: "nrpc.proto", 402 | }, 403 | { 404 | ExtendedType: (*descriptorpb.MethodOptions)(nil), 405 | ExtensionType: ([]string)(nil), 406 | Field: 52001, 407 | Name: "nrpc.methodSubjectParams", 408 | Tag: "bytes,52001,rep,name=methodSubjectParams", 409 | Filename: "nrpc.proto", 410 | }, 411 | { 412 | ExtendedType: (*descriptorpb.MethodOptions)(nil), 413 | ExtensionType: (*bool)(nil), 414 | Field: 52002, 415 | Name: "nrpc.streamedReply", 416 | Tag: "varint,52002,opt,name=streamedReply", 417 | Filename: "nrpc.proto", 418 | }, 419 | { 420 | ExtendedType: (*descriptorpb.MethodOptions)(nil), 421 | ExtensionType: (*bool)(nil), 422 | Field: 52003, 423 | Name: "nrpc.pollingEnabled", 424 | Tag: "varint,52003,opt,name=pollingEnabled", 425 | Filename: "nrpc.proto", 426 | }, 427 | } 428 | 429 | // Extension fields to descriptorpb.FileOptions. 430 | var ( 431 | // A custom subject prefix to use instead of the package name 432 | // 433 | // optional string packageSubject = 50000; 434 | E_PackageSubject = &file_nrpc_proto_extTypes[0] 435 | // Parameters included in the subject at the package level 436 | // 437 | // repeated string packageSubjectParams = 50001; 438 | E_PackageSubjectParams = &file_nrpc_proto_extTypes[1] 439 | // Default rule to build a service subject from the service name 440 | // 441 | // optional nrpc.SubjectRule serviceSubjectRule = 50002; 442 | E_ServiceSubjectRule = &file_nrpc_proto_extTypes[2] 443 | // Default rule to build a method subject from its name 444 | // 445 | // optional nrpc.SubjectRule methodSubjectRule = 50003; 446 | E_MethodSubjectRule = &file_nrpc_proto_extTypes[3] 447 | ) 448 | 449 | // Extension fields to descriptorpb.ServiceOptions. 450 | var ( 451 | // A custom subject token to use instead of (service name + serviceSubjectRule) 452 | // 453 | // optional string serviceSubject = 51000; 454 | E_ServiceSubject = &file_nrpc_proto_extTypes[4] 455 | // Parameters included in the subject at the service level 456 | // 457 | // repeated string serviceSubjectParams = 51001; 458 | E_ServiceSubjectParams = &file_nrpc_proto_extTypes[5] 459 | ) 460 | 461 | // Extension fields to descriptorpb.MethodOptions. 462 | var ( 463 | // A custom subject to use instead of (methor name + methodSubjectRule) 464 | // 465 | // optional string methodSubject = 52000; 466 | E_MethodSubject = &file_nrpc_proto_extTypes[6] 467 | // Parameters included in the subject at the method level 468 | // 469 | // repeated string methodSubjectParams = 52001; 470 | E_MethodSubjectParams = &file_nrpc_proto_extTypes[7] 471 | // If true, the method returns a stream of reply messages instead of just one 472 | // 473 | // optional bool streamedReply = 52002; 474 | E_StreamedReply = &file_nrpc_proto_extTypes[8] 475 | // If true, a 'Polling' version of the client method is generated 476 | // 477 | // optional bool pollingEnabled = 52003; 478 | E_PollingEnabled = &file_nrpc_proto_extTypes[9] 479 | ) 480 | 481 | var File_nrpc_proto protoreflect.FileDescriptor 482 | 483 | var file_nrpc_proto_rawDesc = []byte{ 484 | 0x0a, 0x0a, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6e, 0x72, 485 | 0x70, 0x63, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 486 | 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 487 | 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9f, 0x01, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x24, 488 | 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6e, 489 | 0x72, 0x70, 0x63, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 490 | 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 491 | 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1a, 492 | 0x0a, 0x08, 0x6d, 0x73, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 493 | 0x52, 0x08, 0x6d, 0x73, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x3a, 0x0a, 0x04, 0x54, 0x79, 494 | 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x0a, 495 | 0x0a, 0x06, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x45, 0x4f, 496 | 0x53, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x54, 0x4f, 0x4f, 497 | 0x42, 0x55, 0x53, 0x59, 0x10, 0x04, 0x22, 0x06, 0x0a, 0x04, 0x56, 0x6f, 0x69, 0x64, 0x22, 0x0b, 498 | 0x0a, 0x09, 0x4e, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x09, 0x0a, 0x07, 0x4e, 499 | 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x27, 0x0a, 0x09, 0x48, 0x65, 0x61, 0x72, 0x74, 0x42, 500 | 0x65, 0x61, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, 501 | 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x62, 0x65, 0x61, 0x74, 0x2a, 502 | 0x24, 0x0a, 0x0b, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x08, 503 | 0x0a, 0x04, 0x43, 0x4f, 0x50, 0x59, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x4f, 0x4c, 0x4f, 504 | 0x57, 0x45, 0x52, 0x10, 0x01, 0x3a, 0x46, 0x0a, 0x0e, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 505 | 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 506 | 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 507 | 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd0, 0x86, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 508 | 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x52, 0x0a, 509 | 0x14, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 510 | 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 511 | 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 512 | 0x6f, 0x6e, 0x73, 0x18, 0xd1, 0x86, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x14, 0x70, 0x61, 0x63, 513 | 0x6b, 0x61, 0x67, 0x65, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 514 | 0x73, 0x3a, 0x61, 0x0a, 0x12, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x6a, 515 | 0x65, 0x63, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 516 | 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 517 | 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd2, 0x86, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 518 | 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x75, 0x6c, 0x65, 519 | 0x52, 0x12, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 520 | 0x52, 0x75, 0x6c, 0x65, 0x3a, 0x5f, 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x53, 0x75, 521 | 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 522 | 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 523 | 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd3, 0x86, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 524 | 0x11, 0x2e, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x75, 525 | 0x6c, 0x65, 0x52, 0x11, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 526 | 0x74, 0x52, 0x75, 0x6c, 0x65, 0x3a, 0x49, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 527 | 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 528 | 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 529 | 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb8, 0x8e, 0x03, 0x20, 0x01, 0x28, 0x09, 530 | 0x52, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 531 | 0x3a, 0x55, 0x0a, 0x14, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x6a, 0x65, 532 | 0x63, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 533 | 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 534 | 0x63, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb9, 0x8e, 0x03, 0x20, 0x03, 0x28, 535 | 0x09, 0x52, 0x14, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 536 | 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x3a, 0x46, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x68, 0x6f, 537 | 0x64, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 538 | 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 539 | 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xa0, 0x96, 0x03, 0x20, 0x01, 0x28, 0x09, 540 | 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 541 | 0x52, 0x0a, 0x13, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 542 | 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 543 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 544 | 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xa1, 0x96, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 545 | 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x61, 0x72, 546 | 0x61, 0x6d, 0x73, 0x3a, 0x46, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x65, 0x64, 0x52, 547 | 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 548 | 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 549 | 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xa2, 0x96, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, 550 | 0x72, 0x65, 0x61, 0x6d, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x3a, 0x48, 0x0a, 0x0e, 0x70, 551 | 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1e, 0x2e, 552 | 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 553 | 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xa3, 0x96, 554 | 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x70, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x45, 0x6e, 555 | 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x1a, 0x5a, 0x18, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 556 | 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x61, 0x74, 0x73, 0x2d, 0x72, 0x70, 0x63, 0x2f, 0x6e, 0x72, 0x70, 557 | 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 558 | } 559 | 560 | var ( 561 | file_nrpc_proto_rawDescOnce sync.Once 562 | file_nrpc_proto_rawDescData = file_nrpc_proto_rawDesc 563 | ) 564 | 565 | func file_nrpc_proto_rawDescGZIP() []byte { 566 | file_nrpc_proto_rawDescOnce.Do(func() { 567 | file_nrpc_proto_rawDescData = protoimpl.X.CompressGZIP(file_nrpc_proto_rawDescData) 568 | }) 569 | return file_nrpc_proto_rawDescData 570 | } 571 | 572 | var file_nrpc_proto_enumTypes = make([]protoimpl.EnumInfo, 2) 573 | var file_nrpc_proto_msgTypes = make([]protoimpl.MessageInfo, 5) 574 | var file_nrpc_proto_goTypes = []interface{}{ 575 | (SubjectRule)(0), // 0: nrpc.SubjectRule 576 | (Error_Type)(0), // 1: nrpc.Error.Type 577 | (*Error)(nil), // 2: nrpc.Error 578 | (*Void)(nil), // 3: nrpc.Void 579 | (*NoRequest)(nil), // 4: nrpc.NoRequest 580 | (*NoReply)(nil), // 5: nrpc.NoReply 581 | (*HeartBeat)(nil), // 6: nrpc.HeartBeat 582 | (*descriptorpb.FileOptions)(nil), // 7: google.protobuf.FileOptions 583 | (*descriptorpb.ServiceOptions)(nil), // 8: google.protobuf.ServiceOptions 584 | (*descriptorpb.MethodOptions)(nil), // 9: google.protobuf.MethodOptions 585 | } 586 | var file_nrpc_proto_depIdxs = []int32{ 587 | 1, // 0: nrpc.Error.type:type_name -> nrpc.Error.Type 588 | 7, // 1: nrpc.packageSubject:extendee -> google.protobuf.FileOptions 589 | 7, // 2: nrpc.packageSubjectParams:extendee -> google.protobuf.FileOptions 590 | 7, // 3: nrpc.serviceSubjectRule:extendee -> google.protobuf.FileOptions 591 | 7, // 4: nrpc.methodSubjectRule:extendee -> google.protobuf.FileOptions 592 | 8, // 5: nrpc.serviceSubject:extendee -> google.protobuf.ServiceOptions 593 | 8, // 6: nrpc.serviceSubjectParams:extendee -> google.protobuf.ServiceOptions 594 | 9, // 7: nrpc.methodSubject:extendee -> google.protobuf.MethodOptions 595 | 9, // 8: nrpc.methodSubjectParams:extendee -> google.protobuf.MethodOptions 596 | 9, // 9: nrpc.streamedReply:extendee -> google.protobuf.MethodOptions 597 | 9, // 10: nrpc.pollingEnabled:extendee -> google.protobuf.MethodOptions 598 | 0, // 11: nrpc.serviceSubjectRule:type_name -> nrpc.SubjectRule 599 | 0, // 12: nrpc.methodSubjectRule:type_name -> nrpc.SubjectRule 600 | 13, // [13:13] is the sub-list for method output_type 601 | 13, // [13:13] is the sub-list for method input_type 602 | 11, // [11:13] is the sub-list for extension type_name 603 | 1, // [1:11] is the sub-list for extension extendee 604 | 0, // [0:1] is the sub-list for field type_name 605 | } 606 | 607 | func init() { file_nrpc_proto_init() } 608 | func file_nrpc_proto_init() { 609 | if File_nrpc_proto != nil { 610 | return 611 | } 612 | if !protoimpl.UnsafeEnabled { 613 | file_nrpc_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 614 | switch v := v.(*Error); i { 615 | case 0: 616 | return &v.state 617 | case 1: 618 | return &v.sizeCache 619 | case 2: 620 | return &v.unknownFields 621 | default: 622 | return nil 623 | } 624 | } 625 | file_nrpc_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 626 | switch v := v.(*Void); i { 627 | case 0: 628 | return &v.state 629 | case 1: 630 | return &v.sizeCache 631 | case 2: 632 | return &v.unknownFields 633 | default: 634 | return nil 635 | } 636 | } 637 | file_nrpc_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 638 | switch v := v.(*NoRequest); i { 639 | case 0: 640 | return &v.state 641 | case 1: 642 | return &v.sizeCache 643 | case 2: 644 | return &v.unknownFields 645 | default: 646 | return nil 647 | } 648 | } 649 | file_nrpc_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { 650 | switch v := v.(*NoReply); i { 651 | case 0: 652 | return &v.state 653 | case 1: 654 | return &v.sizeCache 655 | case 2: 656 | return &v.unknownFields 657 | default: 658 | return nil 659 | } 660 | } 661 | file_nrpc_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { 662 | switch v := v.(*HeartBeat); i { 663 | case 0: 664 | return &v.state 665 | case 1: 666 | return &v.sizeCache 667 | case 2: 668 | return &v.unknownFields 669 | default: 670 | return nil 671 | } 672 | } 673 | } 674 | type x struct{} 675 | out := protoimpl.TypeBuilder{ 676 | File: protoimpl.DescBuilder{ 677 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 678 | RawDescriptor: file_nrpc_proto_rawDesc, 679 | NumEnums: 2, 680 | NumMessages: 5, 681 | NumExtensions: 10, 682 | NumServices: 0, 683 | }, 684 | GoTypes: file_nrpc_proto_goTypes, 685 | DependencyIndexes: file_nrpc_proto_depIdxs, 686 | EnumInfos: file_nrpc_proto_enumTypes, 687 | MessageInfos: file_nrpc_proto_msgTypes, 688 | ExtensionInfos: file_nrpc_proto_extTypes, 689 | }.Build() 690 | File_nrpc_proto = out.File 691 | file_nrpc_proto_rawDesc = nil 692 | file_nrpc_proto_goTypes = nil 693 | file_nrpc_proto_depIdxs = nil 694 | } 695 | -------------------------------------------------------------------------------- /nrpc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package nrpc; 4 | 5 | option go_package = "github.com/nats-rpc/nrpc"; 6 | 7 | import "google/protobuf/descriptor.proto"; 8 | 9 | enum SubjectRule { 10 | COPY = 0; 11 | TOLOWER = 1; 12 | } 13 | 14 | extend google.protobuf.FileOptions { 15 | // A custom subject prefix to use instead of the package name 16 | string packageSubject = 50000; 17 | // Parameters included in the subject at the package level 18 | repeated string packageSubjectParams = 50001; 19 | // Default rule to build a service subject from the service name 20 | SubjectRule serviceSubjectRule = 50002; 21 | // Default rule to build a method subject from its name 22 | SubjectRule methodSubjectRule = 50003; 23 | } 24 | 25 | extend google.protobuf.ServiceOptions { 26 | // A custom subject token to use instead of (service name + serviceSubjectRule) 27 | string serviceSubject = 51000; 28 | // Parameters included in the subject at the service level 29 | repeated string serviceSubjectParams = 51001; 30 | } 31 | 32 | extend google.protobuf.MethodOptions { 33 | // A custom subject to use instead of (methor name + methodSubjectRule) 34 | string methodSubject = 52000; 35 | // Parameters included in the subject at the method level 36 | repeated string methodSubjectParams = 52001; 37 | // If true, the method returns a stream of reply messages instead of just one 38 | bool streamedReply = 52002; 39 | // If true, a 'Polling' version of the client method is generated 40 | bool pollingEnabled = 52003; 41 | } 42 | 43 | message Error { 44 | enum Type { 45 | CLIENT = 0; 46 | SERVER = 1; 47 | EOS = 3; 48 | SERVERTOOBUSY = 4; 49 | } 50 | Type type = 1; 51 | string message = 2; 52 | uint32 msgCount = 3; 53 | } 54 | 55 | message Void {} 56 | 57 | message NoRequest {} 58 | message NoReply {} 59 | 60 | message HeartBeat { 61 | bool lastbeat = 1; 62 | } 63 | -------------------------------------------------------------------------------- /nrpc_test.go: -------------------------------------------------------------------------------- 1 | package nrpc_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "log" 8 | "strings" 9 | "testing" 10 | "time" 11 | 12 | "github.com/nats-io/nats.go" 13 | "google.golang.org/protobuf/proto" 14 | 15 | "github.com/nats-rpc/nrpc" 16 | ) 17 | 18 | //go:generate protoc --go_out=. --go_opt=paths=source_relative nrpc_test.proto 19 | //go:generate mv nrpc_test.pb.go nrpcpb_test.go 20 | 21 | func TestBasic(t *testing.T) { 22 | nc, err := nats.Connect(nrpc.NatsURL, nats.Timeout(5*time.Second)) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | defer nc.Close() 27 | 28 | subn, err := nc.Subscribe("foo.*", func(m *nats.Msg) { 29 | if err := nrpc.Publish(&DummyMessage{Foobar: "world"}, nil, nc, m.Reply, "protobuf"); err != nil { 30 | t.Fatal(err) 31 | } 32 | }) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | defer subn.Unsubscribe() 37 | 38 | var dm DummyMessage 39 | if err := nrpc.Call( 40 | &DummyMessage{Foobar: "hello"}, &dm, nc, "foo.bar", "protobuf", 5*time.Second, 41 | ); err != nil { 42 | t.Fatal(err) 43 | } 44 | if dm.Foobar != "world" { 45 | t.Fatal("wrong response: ", string(dm.Foobar)) 46 | } 47 | } 48 | 49 | func TestDecode(t *testing.T) { 50 | nc, err := nats.Connect(nrpc.NatsURL, nats.Timeout(5*time.Second)) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | defer nc.Close() 55 | 56 | var name string 57 | subn, err := nc.Subscribe("foo.*", func(m *nats.Msg) { 58 | rname := strings.Split(m.Subject, ".")[1] 59 | var dm DummyMessage 60 | if rname != name { 61 | t.Fatal("unexpected name: " + rname) 62 | } else if err := proto.Unmarshal(m.Data, &dm); err != nil { 63 | t.Fatal(err) 64 | } else if dm.Foobar != "hello" { 65 | t.Fatal("unexpected inner request: " + dm.Foobar) 66 | } else if err := nrpc.Publish(&DummyMessage{Foobar: "world"}, nil, nc, m.Reply, "protobuf"); err != nil { 67 | t.Fatal(err) 68 | } 69 | }) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | defer subn.Unsubscribe() 74 | 75 | var names = []string{"lorem", "ipsum", "dolor"} 76 | for _, n := range names { 77 | name = n 78 | var dm DummyMessage 79 | if err := nrpc.Call( 80 | &DummyMessage{Foobar: "hello"}, &dm, nc, "foo."+name, "protobuf", 5*time.Second, 81 | ); err != nil { 82 | t.Fatal(err) 83 | } 84 | if dm.Foobar != "world" { 85 | t.Fatal("wrong response: ", string(dm.Foobar)) 86 | } 87 | } 88 | } 89 | 90 | func TestStreamCall(t *testing.T) { 91 | nc, err := nats.Connect(nrpc.NatsURL, nats.Timeout(5*time.Second)) 92 | if err != nil { 93 | t.Fatal(err) 94 | } 95 | defer nc.Close() 96 | 97 | t.Run("Simple stream", func(t *testing.T) { 98 | subn, err := nc.Subscribe("foo.*", func(m *nats.Msg) { 99 | time.Sleep(60 * time.Millisecond) 100 | // Send an empty message 101 | if err := nc.Publish(m.Reply, []byte{0}); err != nil { 102 | t.Fatal(err) 103 | } 104 | time.Sleep(60 * time.Millisecond) 105 | // Send a first message 106 | if err := nrpc.Publish( 107 | &DummyMessage{Foobar: "hello"}, 108 | nil, 109 | nc, m.Reply, "protobuf", 110 | ); err != nil { 111 | t.Fatal(err) 112 | } 113 | time.Sleep(60 * time.Millisecond) 114 | log.Print("Sending 'world'") 115 | // Send a second message 116 | if err := nrpc.Publish( 117 | &DummyMessage{Foobar: "world"}, 118 | nil, 119 | nc, m.Reply, "protobuf", 120 | ); err != nil { 121 | t.Fatal(err) 122 | } 123 | log.Print("Sending EOF") 124 | // Send the EOS marker 125 | if err := nrpc.Publish( 126 | nil, 127 | &nrpc.Error{Type: nrpc.Error_EOS, MsgCount: 2}, 128 | nc, m.Reply, "protobuf", 129 | ); err != nil { 130 | t.Fatal(err) 131 | } 132 | }) 133 | if err != nil { 134 | t.Fatal(err) 135 | } 136 | defer subn.Unsubscribe() 137 | 138 | sub, err := nrpc.StreamCall( 139 | context.TODO(), nc, "foo.*", &DummyMessage{}, "protobuf", 100*time.Millisecond) 140 | if err != nil { 141 | t.Fatal(err) 142 | } 143 | 144 | var dm DummyMessage 145 | var cont bool 146 | 147 | err = sub.Next(&dm) 148 | if err != nil { 149 | t.Fatal(err) 150 | } 151 | 152 | err = sub.Next(&dm) 153 | if err != nil { 154 | t.Fatal(err) 155 | } 156 | 157 | err = sub.Next(&dm) 158 | if err != nrpc.ErrEOS { 159 | t.Fatalf("Expected EOS, got %s", err) 160 | } 161 | if cont { 162 | t.Errorf("Expects cont=false") 163 | } 164 | err = sub.Next(&dm) 165 | if err != nats.ErrBadSubscription { 166 | t.Errorf("Expected a ErrBadSubscription, got %s", err) 167 | } 168 | }) 169 | 170 | t.Run("Error", func(t *testing.T) { 171 | subn, err := nc.Subscribe("foo.*", func(m *nats.Msg) { 172 | if err := nrpc.Publish( 173 | nil, 174 | &nrpc.Error{Type: nrpc.Error_CLIENT, Message: "error"}, 175 | nc, m.Reply, "protobuf", 176 | ); err != nil { 177 | t.Fatal(err) 178 | } 179 | }) 180 | if err != nil { 181 | t.Fatal(err) 182 | } 183 | defer subn.Unsubscribe() 184 | 185 | sub, err := nrpc.StreamCall( 186 | context.TODO(), nc, "foo.*", &DummyMessage{}, "protobuf", 100*time.Millisecond) 187 | if err != nil { 188 | t.Fatal(err) 189 | } 190 | 191 | err = sub.Next(&DummyMessage{}) 192 | if err == nil { 193 | t.Fatalf("Expected an error, got %s", err) 194 | } 195 | if nErr, ok := err.(*nrpc.Error); ok { 196 | if nErr.Message != "error" { 197 | t.Errorf("Expected message='error', got %s", nErr.Message) 198 | } 199 | } else { 200 | t.Errorf("Expected a nrpc.Error, got %s", err) 201 | } 202 | err = sub.Next(&DummyMessage{}) 203 | if err != nats.ErrBadSubscription { 204 | t.Errorf("Expected a ErrBadSubscription, got %s", err) 205 | } 206 | }) 207 | } 208 | 209 | func TestError(t *testing.T) { 210 | nc, err := nats.Connect(nrpc.NatsURL, nats.Timeout(5*time.Second)) 211 | if err != nil { 212 | t.Fatal(err) 213 | } 214 | defer nc.Close() 215 | 216 | subn, err := nc.Subscribe("foo.*", func(m *nats.Msg) { 217 | if err := nrpc.Publish( 218 | &DummyMessage{Foobar: "world"}, 219 | &nrpc.Error{Type: nrpc.Error_CLIENT, Message: "anerror"}, 220 | nc, m.Reply, "protobuf", 221 | ); err != nil { 222 | t.Fatal(err) 223 | } 224 | }) 225 | if err != nil { 226 | t.Fatal(err) 227 | } 228 | defer subn.Unsubscribe() 229 | 230 | err = nrpc.Call(&DummyMessage{Foobar: "hello"}, &DummyMessage{}, nc, "foo.bar", "protobuf", 5*time.Second) 231 | if err == nil { 232 | t.Fatal("error expected") 233 | } 234 | if err.Error() != "CLIENT error: anerror" { 235 | t.Fatal("wrong error: ", err.Error()) 236 | } 237 | } 238 | 239 | func TestTimeout(t *testing.T) { 240 | nc, err := nats.Connect(nrpc.NatsURL, nats.Timeout(5*time.Second)) 241 | if err != nil { 242 | t.Fatal(err) 243 | } 244 | defer nc.Close() 245 | 246 | subn, err := nc.Subscribe("foo.*", func(m *nats.Msg) { 247 | time.Sleep(time.Second) 248 | if err := nrpc.Publish(&DummyMessage{Foobar: "world"}, nil, nc, m.Reply, "protobuf"); err != nil { 249 | t.Fatal(err) 250 | } 251 | }) 252 | if err != nil { 253 | t.Fatal(err) 254 | } 255 | defer subn.Unsubscribe() 256 | 257 | err = nrpc.Call(&DummyMessage{Foobar: "hello"}, &DummyMessage{}, nc, "foo.bar", "protobuf", 500*time.Millisecond) 258 | if err == nil { 259 | t.Fatal("error expected") 260 | } else if err.Error() != "nats: timeout" { 261 | t.Fatal("unexpected error: " + err.Error()) 262 | } 263 | } 264 | 265 | var ( 266 | encodingTestMsg = DummyMessage{Foobar: "hello"} 267 | encodingTests = []struct { 268 | encoding string 269 | data []byte 270 | }{ 271 | {"protobuf", []byte{10, 5, 104, 101, 108, 108, 111}}, 272 | {"json", []byte(`{"foobar":"hello"}`)}, 273 | } 274 | ) 275 | 276 | func TestMarshal(t *testing.T) { 277 | for _, tt := range encodingTests { 278 | t.Run("Marshal"+tt.encoding, func(t *testing.T) { 279 | b, err := nrpc.Marshal(tt.encoding, &encodingTestMsg) 280 | if err != nil { 281 | t.Fatal(err) 282 | } 283 | if !bytes.Equal(b, tt.data) { 284 | t.Errorf("Marshal %s failed", tt.encoding) 285 | } 286 | }) 287 | } 288 | } 289 | 290 | func TestUnmarshal(t *testing.T) { 291 | for _, tt := range encodingTests { 292 | t.Run("Unmarshal"+tt.encoding, func(t *testing.T) { 293 | var msg DummyMessage 294 | err := nrpc.Unmarshal(tt.encoding, tt.data, &msg) 295 | if err != nil { 296 | t.Fatal(err) 297 | } 298 | if msg.Foobar != encodingTestMsg.Foobar { 299 | t.Errorf( 300 | "%s decode failed. Expected %#v, got %#v", 301 | tt.encoding, encodingTestMsg, msg) 302 | } 303 | }) 304 | } 305 | } 306 | 307 | func TestMarshalUnmarshalResponse(t *testing.T) { 308 | for _, tt := range encodingTests { 309 | t.Run("UnmarshalResponse"+tt.encoding, func(t *testing.T) { 310 | var msg DummyMessage 311 | err := nrpc.UnmarshalResponse(tt.encoding, tt.data, &msg) 312 | if err != nil { 313 | t.Fatal(err) 314 | } 315 | if msg.Foobar != encodingTestMsg.Foobar { 316 | t.Errorf( 317 | "%s decode failed. Expected %#v, got %#v", 318 | tt.encoding, encodingTestMsg, msg) 319 | } 320 | }) 321 | t.Run("MarshalErrorResponse"+tt.encoding, func(t *testing.T) { 322 | data, err := nrpc.MarshalErrorResponse(tt.encoding, &nrpc.Error{ 323 | Type: nrpc.Error_SERVER, Message: "Some error", 324 | }) 325 | if err != nil { 326 | t.Fatal("Unexpected error:", err) 327 | } 328 | switch tt.encoding { 329 | case "protobuf": 330 | if data[0] != 0 { 331 | t.Error("Expects payload to start with a '0', got", data[0]) 332 | } 333 | case "json": 334 | var expected = `{"__error__":{"type":"SERVER","message":"Some error"}}` 335 | if string(data) != expected { 336 | t.Errorf("Invalid json-encoded error. Expects %s, got %s", expected, string(data)) 337 | } 338 | } 339 | var msg DummyMessage 340 | err = nrpc.UnmarshalResponse(tt.encoding, data, &msg) 341 | if err == nil { 342 | t.Errorf("Expected an error, got nil") 343 | return 344 | } 345 | repErr, ok := err.(*nrpc.Error) 346 | if !ok { 347 | t.Errorf("Expected a nrpc.Error, got %#v", err) 348 | return 349 | } 350 | if repErr.Type != nrpc.Error_SERVER || repErr.Message != "Some error" { 351 | t.Errorf("Unexpected err: %#v", *repErr) 352 | } 353 | }) 354 | } 355 | } 356 | 357 | // MSG Greeter.SayHello-json 1 _INBOX.test 16\r\n{"foobar":"hello"}\r\n 358 | 359 | func compareStringSlices(t *testing.T, expected, actual []string) { 360 | if len(expected) != len(expected) { 361 | t.Errorf("String slices are different. Expected [%s], got [%s]", 362 | strings.Join(expected, ","), strings.Join(actual, ",")) 363 | return 364 | } 365 | for i, expectedValue := range expected { 366 | if actual[i] != expectedValue { 367 | t.Errorf("String slices are different. Expected [%s], got [%s]", 368 | strings.Join(expected, ","), strings.Join(actual, ",")) 369 | return 370 | } 371 | } 372 | } 373 | 374 | func TestParseSubject(t *testing.T) { 375 | for i, tt := range []struct { 376 | pkgSubject string 377 | pkgParamsCount int 378 | svcSubject string 379 | svcParamsCount int 380 | mtParamsCount int 381 | subject string 382 | pkgParams []string 383 | svcParams []string 384 | mtParams []string 385 | name string 386 | encoding string 387 | err string 388 | }{ 389 | {"", 0, "foo", 0, 0, "foo.bar", nil, nil, nil, "bar", "protobuf", ""}, 390 | {"", 0, "foo", 0, 0, "foo.bar.protobuf", nil, nil, nil, "bar", "protobuf", ""}, 391 | {"", 0, "foo", 0, 0, "foo.bar.json", nil, nil, nil, "bar", "json", ""}, 392 | {"", 0, "foo", 0, 0, "foo.bar.json.protobuf", nil, nil, nil, "bar", "", 393 | "Invalid subject tail length. Expects 0 or 1 parts, got 2"}, 394 | {"demo", 0, "foo", 0, 0, "demo.foo.bar", nil, nil, nil, "bar", "protobuf", ""}, 395 | {"demo", 0, "foo", 0, 0, "demo.foo.bar.json", nil, nil, nil, "bar", "json", ""}, 396 | {"demo", 0, "foo", 0, 0, "foo.bar.json", nil, nil, nil, "", "", 397 | "Invalid subject prefix. Expected 'demo', got 'foo'"}, 398 | {"demo", 2, "foo", 0, 0, "demo.p1.p2.foo.bar.json", []string{"p1", "p2"}, nil, nil, "bar", "json", ""}, 399 | {"demo", 2, "foo", 1, 0, "demo.p1.p2.foo.sp1.bar.json", []string{"p1", "p2"}, []string{"sp1"}, nil, "bar", "json", ""}, 400 | {"demo.pkg", 1, "nested.svc", 1, 0, "demo.pkg.p1.nested.svc.sp1.bar", 401 | []string{"p1"}, []string{"sp1"}, nil, "bar", "protobuf", ""}, 402 | } { 403 | pkgParams, svcParams, name, tail, err := nrpc.ParseSubject( 404 | tt.pkgSubject, tt.pkgParamsCount, 405 | tt.svcSubject, tt.svcParamsCount, 406 | tt.subject) 407 | var mtParams []string 408 | var encoding string 409 | if err == nil { 410 | mtParams, encoding, err = nrpc.ParseSubjectTail(tt.mtParamsCount, tail) 411 | } 412 | compareStringSlices(t, tt.pkgParams, pkgParams) 413 | compareStringSlices(t, tt.svcParams, svcParams) 414 | compareStringSlices(t, tt.mtParams, mtParams) 415 | if name != tt.name { 416 | t.Errorf("test %d: Expected name=%s, got %s", i, tt.name, name) 417 | } 418 | if encoding != tt.encoding { 419 | t.Errorf("text %d: Expected encoding=%s, got %s", i, tt.encoding, encoding) 420 | } 421 | if tt.err == "" && err != nil { 422 | t.Errorf("text %d: Unexpected error %s", i, err) 423 | } else if tt.err != "" && err == nil { 424 | t.Errorf("text %d: Expected error, got nothing", i) 425 | } else if tt.err != "" && tt.err != err.Error() { 426 | t.Errorf("text %d: Expected error '%s', got '%s'", i, tt.err, err) 427 | } 428 | } 429 | } 430 | 431 | func TestCaptureErrors(t *testing.T) { 432 | t.Run("NoError", func(t *testing.T) { 433 | msg, err := nrpc.CaptureErrors(func() (proto.Message, error) { 434 | return &DummyMessage{Foobar: "Hi"}, nil 435 | }) 436 | if err != nil { 437 | t.Error("Unexpected error:", err) 438 | } 439 | dm, ok := msg.(*DummyMessage) 440 | if !ok { 441 | t.Error("Expected a DummyMessage, got", msg) 442 | } 443 | if dm.Foobar != "Hi" { 444 | t.Error("Message was not passed properly") 445 | } 446 | }) 447 | t.Run("ClientError", func(t *testing.T) { 448 | msg, err := nrpc.CaptureErrors(func() (proto.Message, error) { 449 | return nil, fmt.Errorf("anerror") 450 | }) 451 | if err == nil { 452 | t.Fatal("Expected an error, got nothing") 453 | } 454 | if err.Type != nrpc.Error_CLIENT { 455 | t.Errorf("Invalid error type. Expected 'CLIENT' (0), got %s", err.Type) 456 | } 457 | if err.Message != "anerror" { 458 | t.Error("Unexpected error message. Expected 'anerror', got", err.Message) 459 | } 460 | if msg != nil { 461 | t.Error("Expected a nil msg, got", msg) 462 | } 463 | }) 464 | t.Run("DirectError", func(t *testing.T) { 465 | msg, err := nrpc.CaptureErrors(func() (proto.Message, error) { 466 | return nil, &nrpc.Error{Type: nrpc.Error_SERVER, Message: "anerror"} 467 | }) 468 | if err == nil { 469 | t.Fatal("Expected an error, got nothing") 470 | } 471 | if err.Type != nrpc.Error_SERVER { 472 | t.Errorf("Invalid error type. Expected 'SERVER' (1), got %s", err.Type) 473 | } 474 | if err.Message != "anerror" { 475 | t.Error("Unexpected error message. Expected 'anerror', got", err.Message) 476 | } 477 | if msg != nil { 478 | t.Error("Expected a nil msg, got", msg) 479 | } 480 | }) 481 | t.Run("ServerError", func(t *testing.T) { 482 | msg, err := nrpc.CaptureErrors(func() (proto.Message, error) { 483 | panic("panicking") 484 | }) 485 | if err == nil { 486 | t.Fatal("Expected an error, got nothing") 487 | } 488 | if err.Type != nrpc.Error_SERVER { 489 | t.Errorf("Invalid error type. Expected 'SERVER' (1), got %s", err.Type) 490 | } 491 | if err.Message != "panicking" { 492 | t.Error("Unexpected error message. Expected 'panicking', got", err.Message) 493 | } 494 | if msg != nil { 495 | t.Error("Expected a nil msg, got", msg) 496 | } 497 | }) 498 | } 499 | -------------------------------------------------------------------------------- /nrpc_test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/nats-rpc/nrpc_test"; 4 | 5 | message DummyMessage { 6 | string foobar = 1; 7 | } 8 | -------------------------------------------------------------------------------- /nrpcpb_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.29.0 4 | // protoc v4.22.2 5 | // source: nrpc_test.proto 6 | 7 | package nrpc_test 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type DummyMessage struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | Foobar string `protobuf:"bytes,1,opt,name=foobar,proto3" json:"foobar,omitempty"` 29 | } 30 | 31 | func (x *DummyMessage) Reset() { 32 | *x = DummyMessage{} 33 | if protoimpl.UnsafeEnabled { 34 | mi := &file_nrpc_test_proto_msgTypes[0] 35 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 36 | ms.StoreMessageInfo(mi) 37 | } 38 | } 39 | 40 | func (x *DummyMessage) String() string { 41 | return protoimpl.X.MessageStringOf(x) 42 | } 43 | 44 | func (*DummyMessage) ProtoMessage() {} 45 | 46 | func (x *DummyMessage) ProtoReflect() protoreflect.Message { 47 | mi := &file_nrpc_test_proto_msgTypes[0] 48 | if protoimpl.UnsafeEnabled && x != nil { 49 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 50 | if ms.LoadMessageInfo() == nil { 51 | ms.StoreMessageInfo(mi) 52 | } 53 | return ms 54 | } 55 | return mi.MessageOf(x) 56 | } 57 | 58 | // Deprecated: Use DummyMessage.ProtoReflect.Descriptor instead. 59 | func (*DummyMessage) Descriptor() ([]byte, []int) { 60 | return file_nrpc_test_proto_rawDescGZIP(), []int{0} 61 | } 62 | 63 | func (x *DummyMessage) GetFoobar() string { 64 | if x != nil { 65 | return x.Foobar 66 | } 67 | return "" 68 | } 69 | 70 | var File_nrpc_test_proto protoreflect.FileDescriptor 71 | 72 | var file_nrpc_test_proto_rawDesc = []byte{ 73 | 0x0a, 0x0f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 74 | 0x6f, 0x22, 0x26, 0x0a, 0x0c, 0x44, 0x75, 0x6d, 0x6d, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 75 | 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 76 | 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x42, 0x1f, 0x5a, 0x1d, 0x67, 0x69, 0x74, 77 | 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x61, 0x74, 0x73, 0x2d, 0x72, 0x70, 0x63, 78 | 0x2f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 79 | 0x6f, 0x33, 80 | } 81 | 82 | var ( 83 | file_nrpc_test_proto_rawDescOnce sync.Once 84 | file_nrpc_test_proto_rawDescData = file_nrpc_test_proto_rawDesc 85 | ) 86 | 87 | func file_nrpc_test_proto_rawDescGZIP() []byte { 88 | file_nrpc_test_proto_rawDescOnce.Do(func() { 89 | file_nrpc_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_nrpc_test_proto_rawDescData) 90 | }) 91 | return file_nrpc_test_proto_rawDescData 92 | } 93 | 94 | var file_nrpc_test_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 95 | var file_nrpc_test_proto_goTypes = []interface{}{ 96 | (*DummyMessage)(nil), // 0: DummyMessage 97 | } 98 | var file_nrpc_test_proto_depIdxs = []int32{ 99 | 0, // [0:0] is the sub-list for method output_type 100 | 0, // [0:0] is the sub-list for method input_type 101 | 0, // [0:0] is the sub-list for extension type_name 102 | 0, // [0:0] is the sub-list for extension extendee 103 | 0, // [0:0] is the sub-list for field type_name 104 | } 105 | 106 | func init() { file_nrpc_test_proto_init() } 107 | func file_nrpc_test_proto_init() { 108 | if File_nrpc_test_proto != nil { 109 | return 110 | } 111 | if !protoimpl.UnsafeEnabled { 112 | file_nrpc_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 113 | switch v := v.(*DummyMessage); i { 114 | case 0: 115 | return &v.state 116 | case 1: 117 | return &v.sizeCache 118 | case 2: 119 | return &v.unknownFields 120 | default: 121 | return nil 122 | } 123 | } 124 | } 125 | type x struct{} 126 | out := protoimpl.TypeBuilder{ 127 | File: protoimpl.DescBuilder{ 128 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 129 | RawDescriptor: file_nrpc_test_proto_rawDesc, 130 | NumEnums: 0, 131 | NumMessages: 1, 132 | NumExtensions: 0, 133 | NumServices: 0, 134 | }, 135 | GoTypes: file_nrpc_test_proto_goTypes, 136 | DependencyIndexes: file_nrpc_test_proto_depIdxs, 137 | MessageInfos: file_nrpc_test_proto_msgTypes, 138 | }.Build() 139 | File_nrpc_test_proto = out.File 140 | file_nrpc_test_proto_rawDesc = nil 141 | file_nrpc_test_proto_goTypes = nil 142 | file_nrpc_test_proto_depIdxs = nil 143 | } 144 | -------------------------------------------------------------------------------- /protoc-gen-nrpc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "path" 10 | "strings" 11 | "text/template" 12 | 13 | "github.com/nats-rpc/nrpc" 14 | 15 | "google.golang.org/protobuf/proto" 16 | descriptor "google.golang.org/protobuf/types/descriptorpb" 17 | plugin "google.golang.org/protobuf/types/pluginpb" 18 | ) 19 | 20 | // baseName returns the last path element of the name, with the last dotted suffix removed. 21 | func baseName(name string) string { 22 | // First, find the last element 23 | if i := strings.LastIndex(name, "/"); i >= 0 { 24 | name = name[i+1:] 25 | } 26 | // Now drop the suffix 27 | if i := strings.LastIndex(name, "."); i >= 0 { 28 | name = name[0:i] 29 | } 30 | return name 31 | } 32 | 33 | // getGoPackage returns the file's go_package option. 34 | // If it containts a semicolon, only the part before it is returned. 35 | func getGoPackage(fd *descriptor.FileDescriptorProto) string { 36 | pkg := fd.GetOptions().GetGoPackage() 37 | if strings.Contains(pkg, ";") { 38 | parts := strings.Split(pkg, ";") 39 | if len(parts) > 2 { 40 | log.Fatalf( 41 | "protoc-gen-nrpc: go_package '%s' contains more than 1 ';'", 42 | pkg) 43 | } 44 | pkg = parts[1] 45 | } 46 | 47 | return pkg 48 | } 49 | 50 | // goPackageOption interprets the file's go_package option. 51 | // If there is no go_package, it returns ("", "", false). 52 | // If there's a simple name, it returns ("", pkg, true). 53 | // If the option implies an import path, it returns (impPath, pkg, true). 54 | func goPackageOption(d *descriptor.FileDescriptorProto) (impPath, pkg string, ok bool) { 55 | pkg = getGoPackage(d) 56 | if pkg == "" { 57 | return 58 | } 59 | ok = true 60 | // The presence of a slash implies there's an import path. 61 | slash := strings.LastIndex(pkg, "/") 62 | if slash < 0 { 63 | return 64 | } 65 | impPath, pkg = pkg, pkg[slash+1:] 66 | // A semicolon-delimited suffix overrides the package name. 67 | sc := strings.IndexByte(impPath, ';') 68 | if sc < 0 { 69 | return 70 | } 71 | impPath, pkg = impPath[:sc], impPath[sc+1:] 72 | return 73 | } 74 | 75 | // goPackageName returns the Go package name to use in the 76 | // generated Go file. The result explicit reports whether the name 77 | // came from an option go_package statement. If explicit is false, 78 | // the name was derived from the protocol buffer's package statement 79 | // or the input file name. 80 | func goPackageName(d *descriptor.FileDescriptorProto) (name string, explicit bool) { 81 | // Does the file have a "go_package" option? 82 | if _, pkg, ok := goPackageOption(d); ok { 83 | return pkg, true 84 | } 85 | 86 | // Does the file have a package clause? 87 | if pkg := d.GetPackage(); pkg != "" { 88 | return pkg, false 89 | } 90 | // Use the file base name. 91 | return baseName(d.GetName()), false 92 | } 93 | 94 | // goFileName returns the output name for the generated Go file. 95 | func goFileName(d *descriptor.FileDescriptorProto) string { 96 | name := *d.Name 97 | if ext := path.Ext(name); ext == ".proto" || ext == ".protodevel" { 98 | name = name[:len(name)-len(ext)] 99 | } 100 | name += ".nrpc.go" 101 | 102 | if pathsSourceRelative { 103 | return name 104 | } 105 | 106 | // Does the file have a "go_package" option? 107 | // If it does, it may override the filename. 108 | if impPath, _, ok := goPackageOption(d); ok && impPath != "" { 109 | // Replace the existing dirname with the declared import path. 110 | _, name = path.Split(name) 111 | name = path.Join(impPath, name) 112 | return name 113 | } 114 | 115 | return name 116 | } 117 | 118 | // splitMessageTypeName split a message type into (package name, type name) 119 | func splitMessageTypeName(name string) (string, string) { 120 | if len(name) == 0 { 121 | log.Fatal("Empty message type") 122 | } 123 | if name[0] != '.' { 124 | log.Fatalf("Expect message type name to start with '.', but it is '%s'", name) 125 | } 126 | lastDot := strings.LastIndex(name, ".") 127 | return name[1:lastDot], name[lastDot+1:] 128 | } 129 | 130 | func splitTypePath(name string) []string { 131 | if len(name) == 0 { 132 | log.Fatal("Empty message type") 133 | } 134 | if name[0] != '.' { 135 | log.Fatalf("Expect message type name to start with '.', but it is '%s'", name) 136 | } 137 | return strings.Split(name[1:], ".") 138 | } 139 | 140 | func lookupFileDescriptor(name string) *descriptor.FileDescriptorProto { 141 | for _, fd := range request.GetProtoFile() { 142 | if fd.GetPackage() == name { 143 | return fd 144 | } 145 | } 146 | return nil 147 | } 148 | 149 | func lookupMessageType(name string) (*descriptor.FileDescriptorProto, *descriptor.DescriptorProto) { 150 | path := splitTypePath(name) 151 | 152 | pkgpath := path[:len(path)-1] 153 | 154 | var fd *descriptor.FileDescriptorProto 155 | for { 156 | pkgname := strings.Join(pkgpath, ".") 157 | fd = lookupFileDescriptor(pkgname) 158 | if fd != nil { 159 | break 160 | } 161 | if len(pkgpath) == 1 { 162 | log.Fatalf("Could not find the .proto file for package '%s' (from message %s)", pkgname, name) 163 | } 164 | pkgpath = pkgpath[:len(pkgpath)-1] 165 | } 166 | 167 | path = path[len(pkgpath):] 168 | 169 | var d *descriptor.DescriptorProto 170 | for _, mt := range fd.GetMessageType() { 171 | if mt.GetName() == path[0] { 172 | d = mt 173 | break 174 | } 175 | } 176 | if d == nil { 177 | log.Fatalf("No such type '%s' in package '%s'", path[0], strings.Join(pkgpath, ".")) 178 | } 179 | for i, token := range path[1:] { 180 | var found bool 181 | for _, nd := range d.GetNestedType() { 182 | if nd.GetName() == token { 183 | d = nd 184 | found = true 185 | break 186 | } 187 | } 188 | if !found { 189 | log.Fatalf("No such nested type '%s' in '%s.%s'", 190 | token, strings.Join(pkgpath, "."), strings.Join(path[:i+1], ".")) 191 | } 192 | } 193 | return fd, d 194 | } 195 | 196 | func getField(d *descriptor.DescriptorProto, name string) *descriptor.FieldDescriptorProto { 197 | for _, f := range d.GetField() { 198 | if f.GetName() == name { 199 | return f 200 | } 201 | } 202 | return nil 203 | } 204 | 205 | func getOneofDecl(d *descriptor.DescriptorProto, name string) *descriptor.OneofDescriptorProto { 206 | for _, of := range d.GetOneofDecl() { 207 | if of.GetName() == name { 208 | return of 209 | } 210 | } 211 | return nil 212 | } 213 | 214 | func pkgSubject(fd *descriptor.FileDescriptorProto) string { 215 | if options := fd.GetOptions(); options != nil { 216 | e := proto.GetExtension(options, nrpc.E_PackageSubject) 217 | if value, ok := e.(string); ok { 218 | return value 219 | } 220 | } 221 | return fd.GetPackage() 222 | } 223 | 224 | func getResultType( 225 | md *descriptor.MethodDescriptorProto, 226 | ) string { 227 | return md.GetOutputType() 228 | } 229 | 230 | func getGoType(pbType string) (string, string) { 231 | if !strings.Contains(pbType, ".") { 232 | return "", pbType 233 | } 234 | fd, _ := lookupMessageType(pbType) 235 | name := strings.TrimPrefix(pbType, "."+fd.GetPackage()+".") 236 | name = strings.Replace(name, ".", "_", -1) 237 | return getGoPackage(fd), name 238 | } 239 | 240 | func getPkgImportName(goPkg string) string { 241 | if goPkg == getGoPackage(currentFile) { 242 | return "" 243 | } 244 | replacer := strings.NewReplacer(".", "_", "/", "_", "-", "_") 245 | return replacer.Replace(goPkg) 246 | } 247 | 248 | var pluginPrometheus bool 249 | var pathsSourceRelative bool 250 | 251 | var funcMap = template.FuncMap{ 252 | "GoPackageName": func(fd *descriptor.FileDescriptorProto) string { 253 | p, _ := goPackageName(fd) 254 | return p 255 | }, 256 | "GetPkg": func(pkg, s string) string { 257 | s = strings.TrimPrefix(s, ".") 258 | s = strings.TrimPrefix(s, pkg) 259 | s = strings.TrimPrefix(s, ".") 260 | return s 261 | }, 262 | "GetExtraImports": func(fd *descriptor.FileDescriptorProto) []string { 263 | // check all the types used and imports packages from where they come 264 | var imports = make(map[string]string) 265 | for _, sd := range fd.GetService() { 266 | for _, md := range sd.GetMethod() { 267 | goPkg, _ := getGoType(md.GetInputType()) 268 | pkgImportName := getPkgImportName(goPkg) 269 | if pkgImportName != "" { 270 | imports[pkgImportName] = goPkg 271 | } 272 | goPkg, _ = getGoType(getResultType(md)) 273 | pkgImportName = getPkgImportName(goPkg) 274 | if pkgImportName != "" { 275 | imports[pkgImportName] = goPkg 276 | } 277 | } 278 | } 279 | var result []string 280 | for importName, goPkg := range imports { 281 | result = append(result, 282 | fmt.Sprintf("%s \"%s\"", 283 | importName, 284 | goPkg, 285 | ), 286 | ) 287 | } 288 | return result 289 | }, 290 | "GetPkgSubjectPrefix": func(fd *descriptor.FileDescriptorProto) string { 291 | if s := pkgSubject(fd); s != "" { 292 | return s + "." 293 | } 294 | return "" 295 | }, 296 | "GetPkgSubject": pkgSubject, 297 | "GetPkgSubjectParams": func(fd *descriptor.FileDescriptorProto) []string { 298 | if opts := fd.GetOptions(); opts != nil { 299 | e := proto.GetExtension(opts, nrpc.E_PackageSubjectParams) 300 | value := e.([]string) 301 | return value 302 | } 303 | return nil 304 | }, 305 | "GetServiceSubject": func(sd *descriptor.ServiceDescriptorProto) string { 306 | if opts := sd.GetOptions(); opts != nil { 307 | s := proto.GetExtension(opts, nrpc.E_ServiceSubject) 308 | if value, ok := s.(string); ok && s != "" { 309 | return value 310 | } 311 | } 312 | if opts := currentFile.GetOptions(); opts != nil { 313 | s := proto.GetExtension(opts, nrpc.E_ServiceSubjectRule) 314 | switch s.(nrpc.SubjectRule) { 315 | case nrpc.SubjectRule_COPY: 316 | return sd.GetName() 317 | case nrpc.SubjectRule_TOLOWER: 318 | return strings.ToLower(sd.GetName()) 319 | } 320 | } 321 | return sd.GetName() 322 | }, 323 | "ServiceNeedsHandler": func(sd *descriptor.ServiceDescriptorProto) bool { 324 | for _, md := range sd.GetMethod() { 325 | if md.GetInputType() != ".nrpc.NoRequest" { 326 | return true 327 | } 328 | } 329 | return false 330 | }, 331 | "GetMethodSubject": func(md *descriptor.MethodDescriptorProto) string { 332 | if opts := md.GetOptions(); opts != nil { 333 | s := proto.GetExtension(opts, nrpc.E_MethodSubject) 334 | if value, ok := s.(string); ok && value != "" { 335 | return value 336 | } 337 | } 338 | if opts := currentFile.GetOptions(); opts != nil { 339 | s := proto.GetExtension(opts, nrpc.E_MethodSubjectRule) 340 | switch s.(nrpc.SubjectRule) { 341 | case nrpc.SubjectRule_COPY: 342 | return md.GetName() 343 | case nrpc.SubjectRule_TOLOWER: 344 | return strings.ToLower(md.GetName()) 345 | } 346 | } 347 | return md.GetName() 348 | }, 349 | "GetMethodSubjectParams": func(md *descriptor.MethodDescriptorProto) []string { 350 | if opts := md.GetOptions(); opts != nil { 351 | e := proto.GetExtension(opts, nrpc.E_MethodSubjectParams) 352 | if s, ok := e.([]string); ok { 353 | return s 354 | } 355 | } 356 | return []string{} 357 | }, 358 | "GetServiceSubjectParams": func(sd *descriptor.ServiceDescriptorProto) []string { 359 | if opts := sd.GetOptions(); opts != nil { 360 | e := proto.GetExtension(opts, nrpc.E_ServiceSubjectParams) 361 | if s, ok := e.([]string); ok { 362 | return s 363 | } 364 | } 365 | return []string{} 366 | }, 367 | "HasStreamedReply": func(md *descriptor.MethodDescriptorProto) bool { 368 | if opts := md.GetOptions(); opts != nil { 369 | e := proto.GetExtension(opts, nrpc.E_StreamedReply) 370 | if s, ok := e.(bool); ok { 371 | return s 372 | } 373 | } 374 | return false 375 | }, 376 | "HasPollEnabled": func(md *descriptor.MethodDescriptorProto) bool { 377 | if opts := md.GetOptions(); opts != nil { 378 | e := proto.GetExtension(opts, nrpc.E_PollingEnabled) 379 | if s, ok := e.(bool); ok { 380 | return s 381 | } 382 | } 383 | return false 384 | }, 385 | "Prometheus": func() bool { 386 | return pluginPrometheus 387 | }, 388 | "GetResultType": getResultType, 389 | "GoType": func(pbType string) string { 390 | goPkg, goType := getGoType(pbType) 391 | if goPkg != "" { 392 | importName := getPkgImportName(goPkg) 393 | if importName != "" { 394 | goType = importName + "." + goType 395 | } 396 | } 397 | return goType 398 | }, 399 | } 400 | 401 | var request plugin.CodeGeneratorRequest 402 | var currentFile *descriptor.FileDescriptorProto 403 | 404 | func main() { 405 | 406 | log.SetPrefix("protoc-gen-nrpc: ") 407 | data, err := ioutil.ReadAll(os.Stdin) 408 | if err != nil { 409 | log.Fatalf("error: reading input: %v", err) 410 | } 411 | 412 | var response plugin.CodeGeneratorResponse 413 | if err := proto.Unmarshal(data, &request); err != nil { 414 | log.Fatalf("error: parsing input proto: %v", err) 415 | } 416 | 417 | if len(request.GetFileToGenerate()) == 0 { 418 | log.Fatal("error: no files to generate") 419 | } 420 | 421 | for _, param := range strings.Split(request.GetParameter(), ",") { 422 | var value string 423 | if i := strings.Index(param, "="); i >= 0 { 424 | value = param[i+1:] 425 | param = param[0:i] 426 | } 427 | switch param { 428 | case "": 429 | // Ignore. 430 | case "plugins": 431 | for _, plugin := range strings.Split(value, ";") { 432 | switch plugin { 433 | case "prometheus": 434 | pluginPrometheus = true 435 | default: 436 | log.Fatalf("invalid plugin: %s", plugin) 437 | } 438 | } 439 | case "paths": 440 | if value == "source_relative" { 441 | pathsSourceRelative = true 442 | } else if value == "import" { 443 | pathsSourceRelative = false 444 | } else { 445 | log.Fatalf(`unknown path type %q: want "import" or "source_relative"`, value) 446 | } 447 | } 448 | } 449 | 450 | tmpl, err := template.New(".").Funcs(funcMap).Parse(tFile) 451 | if err != nil { 452 | log.Fatal(err) 453 | } 454 | 455 | for _, name := range request.GetFileToGenerate() { 456 | var fd *descriptor.FileDescriptorProto 457 | for _, fd = range request.GetProtoFile() { 458 | if name == fd.GetName() { 459 | break 460 | } 461 | } 462 | if fd == nil { 463 | log.Fatalf("could not find the .proto file for %s", name) 464 | } 465 | 466 | currentFile = fd 467 | 468 | var buf bytes.Buffer 469 | if err := tmpl.Execute(&buf, fd); err != nil { 470 | log.Fatal(err) 471 | } 472 | 473 | currentFile = nil 474 | 475 | response.File = append(response.File, &plugin.CodeGeneratorResponse_File{ 476 | Name: proto.String(goFileName(fd)), 477 | Content: proto.String(buf.String()), 478 | }) 479 | } 480 | 481 | if data, err = proto.Marshal(&response); err != nil { 482 | log.Fatalf("error: failed to marshal output proto: %v", err) 483 | } 484 | if _, err := os.Stdout.Write(data); err != nil { 485 | log.Fatalf("error: failed to write output proto: %v", err) 486 | } 487 | } 488 | -------------------------------------------------------------------------------- /protoc-gen-nrpc/tmpl.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const tFile = `// This code was autogenerated from {{.GetName}}, do not edit. 4 | 5 | {{- $pkgName := GoPackageName .}} 6 | {{- $pkgSubject := GetPkgSubject .}} 7 | {{- $pkgSubjectPrefix := GetPkgSubjectPrefix .}} 8 | {{- $pkgSubjectParams := GetPkgSubjectParams .}} 9 | package {{$pkgName}} 10 | 11 | import ( 12 | "context" 13 | "log" 14 | "time" 15 | 16 | "google.golang.org/protobuf/proto" 17 | "github.com/nats-io/nats.go" 18 | {{- range GetExtraImports .}} 19 | {{.}} 20 | {{- end}} 21 | {{- if Prometheus}} 22 | "github.com/prometheus/client_golang/prometheus" 23 | {{- end}} 24 | "github.com/nats-rpc/nrpc" 25 | ) 26 | 27 | {{- range .Service}} 28 | 29 | // {{.GetName}}Server is the interface that providers of the service 30 | // {{.GetName}} should implement. 31 | type {{.GetName}}Server interface { 32 | {{- range .Method}} 33 | {{- if ne .GetInputType ".nrpc.NoRequest"}} 34 | {{- $resultType := GetResultType .}} 35 | {{.GetName}}(ctx context.Context 36 | {{- range GetMethodSubjectParams . -}} 37 | , {{ . }} string 38 | {{- end -}} 39 | {{- if ne .GetInputType ".nrpc.Void" -}} 40 | , req *{{GoType .GetInputType}} 41 | {{- end -}} 42 | {{- if HasStreamedReply . -}} 43 | , pushRep func(*{{GoType .GetOutputType}}) 44 | {{- end -}} 45 | ) 46 | {{- if ne $resultType ".nrpc.NoReply" }} ( 47 | {{- if and (ne $resultType ".nrpc.Void") (not (HasStreamedReply .)) -}} 48 | *{{GoType $resultType}}, {{end -}} 49 | error) 50 | {{- end -}} 51 | {{- end}} 52 | {{- end}} 53 | } 54 | 55 | {{- if Prometheus}} 56 | 57 | var ( 58 | // The request completion time, measured at client-side. 59 | clientRCTFor{{.GetName}} = prometheus.NewSummaryVec( 60 | prometheus.SummaryOpts{ 61 | Name: "nrpc_client_request_completion_time_seconds", 62 | Help: "The request completion time for calls, measured client-side.", 63 | Objectives: map[float64]float64{0.9: 0.01, 0.95: 0.01, 0.99: 0.001}, 64 | ConstLabels: map[string]string{ 65 | "service": "{{.GetName}}", 66 | }, 67 | }, 68 | []string{"method"}) 69 | 70 | // The handler execution time, measured at server-side. 71 | serverHETFor{{.GetName}} = prometheus.NewSummaryVec( 72 | prometheus.SummaryOpts{ 73 | Name: "nrpc_server_handler_execution_time_seconds", 74 | Help: "The handler execution time for calls, measured server-side.", 75 | Objectives: map[float64]float64{0.9: 0.01, 0.95: 0.01, 0.99: 0.001}, 76 | ConstLabels: map[string]string{ 77 | "service": "{{.GetName}}", 78 | }, 79 | }, 80 | []string{"method"}) 81 | 82 | // The counts of calls made by the client, classified by result type. 83 | clientCallsFor{{.GetName}} = prometheus.NewCounterVec( 84 | prometheus.CounterOpts{ 85 | Name: "nrpc_client_calls_count", 86 | Help: "The count of calls made by the client.", 87 | ConstLabels: map[string]string{ 88 | "service": "{{.GetName}}", 89 | }, 90 | }, 91 | []string{"method", "encoding", "result_type"}) 92 | 93 | // The counts of requests handled by the server, classified by result type. 94 | serverRequestsFor{{.GetName}} = prometheus.NewCounterVec( 95 | prometheus.CounterOpts{ 96 | Name: "nrpc_server_requests_count", 97 | Help: "The count of requests handled by the server.", 98 | ConstLabels: map[string]string{ 99 | "service": "{{.GetName}}", 100 | }, 101 | }, 102 | []string{"method", "encoding", "result_type"}) 103 | ) 104 | {{- end}} 105 | 106 | // {{.GetName}}Handler provides a NATS subscription handler that can serve a 107 | // subscription using a given {{.GetName}}Server implementation. 108 | type {{.GetName}}Handler struct { 109 | ctx context.Context 110 | workers *nrpc.WorkerPool 111 | nc nrpc.NatsConn 112 | server {{.GetName}}Server 113 | 114 | encodings []string 115 | } 116 | 117 | func New{{.GetName}}Handler(ctx context.Context, nc nrpc.NatsConn, s {{.GetName}}Server) *{{.GetName}}Handler { 118 | return &{{.GetName}}Handler{ 119 | ctx: ctx, 120 | nc: nc, 121 | server: s, 122 | 123 | encodings: []string{"protobuf"}, 124 | } 125 | } 126 | 127 | func New{{.GetName}}ConcurrentHandler(workers *nrpc.WorkerPool, nc nrpc.NatsConn, s {{.GetName}}Server) *{{.GetName}}Handler { 128 | return &{{.GetName}}Handler{ 129 | workers: workers, 130 | nc: nc, 131 | server: s, 132 | } 133 | } 134 | 135 | // SetEncodings sets the output encodings when using a '*Publish' function 136 | func (h *{{.GetName}}Handler) SetEncodings(encodings []string) { 137 | h.encodings = encodings 138 | } 139 | 140 | func (h *{{.GetName}}Handler) Subject() string { 141 | return "{{$pkgSubjectPrefix}} 142 | {{- range $pkgSubjectParams -}} 143 | *. 144 | {{- end -}} 145 | {{GetServiceSubject .}} 146 | {{- range GetServiceSubjectParams . -}} 147 | .* 148 | {{- end -}} 149 | .>" 150 | } 151 | {{- $serviceName := .GetName}} 152 | {{- $serviceSubject := GetServiceSubject .}} 153 | {{- $serviceSubjectParams := GetServiceSubjectParams .}} 154 | {{- range .Method}} 155 | {{- if eq .GetInputType ".nrpc.NoRequest"}} 156 | 157 | func (h *{{$serviceName}}Handler) {{.GetName}}Publish( 158 | {{- range $pkgSubjectParams}}pkg{{.}} string, {{end -}} 159 | {{- range $serviceSubjectParams}}svc{{.}} string, {{end -}} 160 | {{- range GetMethodSubjectParams .}}mt{{.}} string, {{end -}} 161 | msg *{{GoType .GetOutputType}}) error { 162 | for _, encoding := range h.encodings { 163 | rawMsg, err := nrpc.Marshal(encoding, msg) 164 | if err != nil { 165 | log.Printf("{{$serviceName}}Handler.{{.GetName}}Publish: error marshaling the message: %s", err) 166 | return err 167 | } 168 | subject := "{{$pkgSubject}}." 169 | {{- range $pkgSubjectParams}} + pkg{{.}} + "."{{end -}} 170 | + "{{$serviceSubject}}." 171 | {{- range $serviceSubjectParams}} + svc{{.}} + "."{{end -}} 172 | + "{{GetMethodSubject .}}" 173 | {{- range GetMethodSubjectParams .}} + "." + mt{{.}}{{end}} 174 | if encoding != "protobuf" { 175 | subject += "." + encoding 176 | } 177 | if err := h.nc.Publish(subject, rawMsg); err != nil { 178 | return err 179 | } 180 | } 181 | return nil 182 | } 183 | {{- end}} 184 | {{- end}} 185 | 186 | {{- if ServiceNeedsHandler .}} 187 | 188 | func (h *{{.GetName}}Handler) Handler(msg *nats.Msg) { 189 | var ctx context.Context 190 | if h.workers != nil { 191 | ctx = h.workers.Context 192 | } else { 193 | ctx = h.ctx 194 | } 195 | request := nrpc.NewRequest(ctx, h.nc, msg.Subject, msg.Reply) 196 | // extract method name & encoding from subject 197 | {{ if ne 0 (len $pkgSubjectParams)}}pkgParams{{else}}_{{end -}}, 198 | {{- if ne 0 (len (GetServiceSubjectParams .))}} svcParams{{else}} _{{end -}} 199 | , name, tail, err := nrpc.ParseSubject( 200 | "{{$pkgSubject}}", {{len $pkgSubjectParams}}, "{{GetServiceSubject .}}", {{len (GetServiceSubjectParams .)}}, msg.Subject) 201 | if err != nil { 202 | log.Printf("{{.GetName}}Hanlder: {{.GetName}} subject parsing failed: %v", err) 203 | return 204 | } 205 | 206 | request.MethodName = name 207 | request.SubjectTail = tail 208 | 209 | {{- range $i, $name := $pkgSubjectParams }} 210 | request.SetPackageParam("{{$name}}", pkgParams[{{$i}}]) 211 | {{- end }} 212 | {{- range $i, $name := GetServiceSubjectParams . }} 213 | request.SetServiceParam("{{$name}}", svcParams[{{$i}}]) 214 | {{- end }} 215 | 216 | // call handler and form response 217 | var immediateError *nrpc.Error 218 | switch name { 219 | {{- range .Method}} 220 | case "{{GetMethodSubject .}}": 221 | {{- if eq .GetInputType ".nrpc.NoRequest"}} 222 | // {{.GetName}} is a no-request method. Ignore it. 223 | return 224 | {{- else}}{{/* !NoRequest */}} 225 | {{- if ne 0 (len (GetMethodSubjectParams .))}} 226 | var mtParams []string 227 | {{- end}} 228 | {{- if eq .GetOutputType ".nrpc.NoReply"}} 229 | request.NoReply = true 230 | {{- end}} 231 | {{if eq 0 (len (GetMethodSubjectParams .))}}_{{else}}mtParams{{end}}, request.Encoding, err = nrpc.ParseSubjectTail({{len (GetMethodSubjectParams .)}}, request.SubjectTail) 232 | if err != nil { 233 | log.Printf("{{.GetName}}Hanlder: {{.GetName}} subject parsing failed: %v", err) 234 | break 235 | } 236 | var req {{GoType .GetInputType}} 237 | if err := nrpc.Unmarshal(request.Encoding, msg.Data, &req); err != nil { 238 | log.Printf("{{.GetName}}Handler: {{.GetName}} request unmarshal failed: %v", err) 239 | immediateError = &nrpc.Error{ 240 | Type: nrpc.Error_CLIENT, 241 | Message: "bad request received: " + err.Error(), 242 | } 243 | {{- if Prometheus}} 244 | serverRequestsFor{{$serviceName}}.WithLabelValues( 245 | "{{.GetName}}", request.Encoding, "unmarshal_fail").Inc() 246 | {{- end}} 247 | } else { 248 | {{- if HasStreamedReply .}} 249 | request.EnableStreamedReply() 250 | request.Handler = func(ctx context.Context)(proto.Message, error){ 251 | err := h.server.{{.GetName}}(ctx 252 | {{- range $i, $p := GetMethodSubjectParams . -}} 253 | , mtParams[{{ $i }}] 254 | {{- end -}} 255 | {{- if ne .GetInputType ".nrpc.Void" -}} 256 | , &req 257 | {{- end -}} 258 | , func(rep *{{GoType .GetOutputType}}){ 259 | request.SendStreamReply(rep) 260 | }) 261 | return nil, err 262 | } 263 | {{- else }} 264 | request.Handler = func(ctx context.Context)(proto.Message, error){ 265 | {{- if eq .GetOutputType ".nrpc.NoReply" -}} 266 | var innerResp = &nrpc.NoReply{} 267 | {{else}} 268 | {{if eq .GetOutputType ".nrpc.Void" -}} 269 | var innerResp = &nrpc.Void{} 270 | {{else}}innerResp, {{end -}} 271 | err := {{end -}} 272 | h.server.{{.GetName}}(ctx 273 | {{- range $i, $p := GetMethodSubjectParams . -}} 274 | , mtParams[{{ $i }}] 275 | {{- end -}} 276 | {{- if ne .GetInputType ".nrpc.Void" -}} 277 | , &req 278 | {{- end -}} 279 | ) 280 | if err != nil { 281 | return nil, err 282 | } 283 | return innerResp, err 284 | } 285 | {{- end }} 286 | } 287 | {{- end}}{{/* not HasStreamedReply */}} 288 | {{- end}}{{/* range .Method */}} 289 | default: 290 | log.Printf("{{.GetName}}Handler: unknown name %q", name) 291 | immediateError = &nrpc.Error{ 292 | Type: nrpc.Error_CLIENT, 293 | Message: "unknown name: " + name, 294 | } 295 | {{- if Prometheus}} 296 | serverRequestsFor{{.GetName}}.WithLabelValues( 297 | "{{.GetName}}", request.Encoding, "name_fail").Inc() 298 | {{- end}} 299 | } 300 | 301 | {{- if Prometheus}} 302 | request.AfterReply = func(request *nrpc.Request, success, replySuccess bool) { 303 | if !replySuccess { 304 | serverRequestsFor{{$serviceName}}.WithLabelValues( 305 | request.MethodName, request.Encoding, "sendreply_fail").Inc() 306 | } 307 | if success { 308 | serverRequestsFor{{$serviceName}}.WithLabelValues( 309 | request.MethodName, request.Encoding, "success").Inc() 310 | } else { 311 | serverRequestsFor{{$serviceName}}.WithLabelValues( 312 | request.MethodName, request.Encoding, "handler_fail").Inc() 313 | } 314 | // report metric to Prometheus 315 | serverHETFor{{$serviceName}}.WithLabelValues(request.MethodName).Observe( 316 | request.Elapsed().Seconds()) 317 | } 318 | 319 | {{- end}} 320 | if immediateError == nil { 321 | if h.workers != nil { 322 | // Try queuing the request 323 | if err := h.workers.QueueRequest(request); err != nil { 324 | log.Printf("nrpc: Error queuing the request: %s", err) 325 | } 326 | } else { 327 | // Run the handler synchronously 328 | request.RunAndReply() 329 | } 330 | } 331 | 332 | if immediateError != nil { 333 | if err := request.SendReply(nil, immediateError); err != nil { 334 | log.Printf("{{.GetName}}Handler: {{.GetName}} handler failed to publish the response: %s", err) 335 | {{- if Prometheus}} 336 | serverRequestsFor{{$serviceName}}.WithLabelValues( 337 | request.MethodName, request.Encoding, "handler_fail").Inc() 338 | {{- end}} 339 | } 340 | {{- if Prometheus}} 341 | serverHETFor{{$serviceName}}.WithLabelValues(request.MethodName).Observe( 342 | request.Elapsed().Seconds()) 343 | {{- end}} 344 | } else { 345 | } 346 | } 347 | {{- end}} 348 | 349 | type {{.GetName}}Client struct { 350 | nc nrpc.NatsConn 351 | {{- if ne 0 (len $pkgSubject)}} 352 | PkgSubject string 353 | {{- end}} 354 | {{- range $pkgSubjectParams}} 355 | PkgParam{{ . }} string 356 | {{- end}} 357 | Subject string 358 | {{- range GetServiceSubjectParams .}} 359 | SvcParam{{ . }} string 360 | {{- end}} 361 | Encoding string 362 | Timeout time.Duration 363 | } 364 | 365 | func New{{.GetName}}Client(nc nrpc.NatsConn 366 | {{- range $pkgSubjectParams -}} 367 | , pkgParam{{.}} string 368 | {{- end -}} 369 | {{- range GetServiceSubjectParams . -}} 370 | , svcParam{{ . }} string 371 | {{- end -}} 372 | ) *{{.GetName}}Client { 373 | return &{{.GetName}}Client{ 374 | nc: nc, 375 | {{- if ne 0 (len $pkgSubject)}} 376 | PkgSubject: "{{$pkgSubject}}", 377 | {{- end}} 378 | {{- range $pkgSubjectParams}} 379 | PkgParam{{.}}: pkgParam{{.}}, 380 | {{- end}} 381 | Subject: "{{GetServiceSubject .}}", 382 | {{- range GetServiceSubjectParams .}} 383 | SvcParam{{.}}: svcParam{{.}}, 384 | {{- end}} 385 | Encoding: "protobuf", 386 | Timeout: 5 * time.Second, 387 | } 388 | } 389 | {{- $serviceName := .GetName}} 390 | {{- $serviceSubjectParams := GetServiceSubjectParams .}} 391 | {{- range .Method}} 392 | {{- $resultType := GetResultType .}} 393 | {{- if eq .GetInputType ".nrpc.NoRequest"}} 394 | 395 | func (c *{{$serviceName}}Client) {{.GetName}}Subject( 396 | {{range GetMethodSubjectParams .}}mt{{.}} string,{{end}} 397 | ) string { 398 | subject := {{ if ne 0 (len $pkgSubject) -}} 399 | c.PkgSubject + "." + {{end}} 400 | {{- range $pkgSubjectParams -}} 401 | c.PkgParam{{.}} + "." + {{end -}} 402 | c.Subject + "." + {{range $serviceSubjectParams -}} 403 | c.SvcParam{{.}} + "." + {{end -}} 404 | "{{GetMethodSubject .}}" 405 | {{- range GetMethodSubjectParams .}} + "." + mt{{.}}{{end}} 406 | if c.Encoding != "protobuf" { 407 | subject += "." + c.Encoding 408 | } 409 | return subject 410 | } 411 | 412 | type {{$serviceName}}{{.GetName}}Subscription struct { 413 | *nats.Subscription 414 | 415 | encoding string 416 | } 417 | 418 | func (s *{{$serviceName}}{{.GetName}}Subscription) Next(timeout time.Duration) (next {{GoType .GetOutputType}}, err error) { 419 | msg, err := s.Subscription.NextMsg(timeout) 420 | if err != nil { 421 | return 422 | } 423 | err = nrpc.Unmarshal(s.encoding, msg.Data, &next) 424 | return 425 | } 426 | 427 | func (c *{{$serviceName}}Client) {{.GetName}}SubscribeSync( 428 | {{range GetMethodSubjectParams .}}mt{{.}} string,{{end}} 429 | ) (sub *{{$serviceName}}{{.GetName}}Subscription, err error) { 430 | subject := c.{{.GetName}}Subject( 431 | {{range GetMethodSubjectParams .}}mt{{.}},{{end}} 432 | ) 433 | natsSub, err := c.nc.SubscribeSync(subject) 434 | if err != nil { 435 | return 436 | } 437 | sub = &{{$serviceName}}{{.GetName}}Subscription{natsSub, c.Encoding} 438 | return 439 | } 440 | 441 | func (c *{{$serviceName}}Client) {{.GetName}}Subscribe( 442 | {{range GetMethodSubjectParams .}}mt{{.}} string,{{end}} 443 | handler func (*{{GoType .GetOutputType}}), 444 | ) (sub *nats.Subscription, err error) { 445 | subject := c.{{.GetName}}Subject( 446 | {{range GetMethodSubjectParams .}}mt{{.}},{{end}} 447 | ) 448 | sub, err = c.nc.Subscribe(subject, func(msg *nats.Msg){ 449 | var pmsg {{GoType .GetOutputType}} 450 | err := nrpc.Unmarshal(c.Encoding, msg.Data, &pmsg) 451 | if err != nil { 452 | log.Printf("{{$serviceName}}Client.{{.GetName}}Subscribe: Error decoding, %s", err) 453 | return 454 | } 455 | handler(&pmsg) 456 | }) 457 | return 458 | } 459 | 460 | func (c *{{$serviceName}}Client) {{.GetName}}SubscribeChan( 461 | {{range GetMethodSubjectParams .}}mt{{.}} string,{{end}} 462 | ) (<-chan *{{GoType .GetOutputType}}, *nats.Subscription, error) { 463 | ch := make(chan *{{GoType .GetOutputType}}) 464 | sub, err := c.{{.GetName}}Subscribe( 465 | {{- range GetMethodSubjectParams .}}mt{{.}}, {{end -}} 466 | func (msg *{{GoType .GetOutputType}}) { 467 | ch <- msg 468 | }) 469 | return ch, sub, err 470 | } 471 | 472 | {{- else if HasStreamedReply .}} 473 | 474 | func (c *{{$serviceName}}Client) {{.GetName}}( 475 | ctx context.Context, 476 | {{- range GetMethodSubjectParams . -}} 477 | {{ . }} string, 478 | {{- end}} 479 | {{- if ne .GetInputType ".nrpc.Void"}} 480 | req *{{GoType .GetInputType}}, 481 | {{- end}} 482 | cb func (context.Context, *{{GoType .GetOutputType}}), 483 | ) error { 484 | {{- if Prometheus}} 485 | start := time.Now() 486 | {{- end}} 487 | subject := {{ if ne 0 (len $pkgSubject) -}} 488 | c.PkgSubject + "." + {{end}} 489 | {{- range $pkgSubjectParams -}} 490 | c.PkgParam{{.}} + "." + {{end -}} 491 | c.Subject + "." + {{range $serviceSubjectParams -}} 492 | c.SvcParam{{.}} + "." + {{end -}} 493 | "{{GetMethodSubject .}}" 494 | {{- range GetMethodSubjectParams . }} + "." + {{ . }}{{ end }} 495 | 496 | sub, err := nrpc.StreamCall(ctx, c.nc, subject 497 | {{- if ne .GetInputType ".nrpc.Void" -}} 498 | , req 499 | {{- else -}} 500 | , &nrpc.Void{} 501 | {{- end -}} 502 | , c.Encoding, c.Timeout) 503 | if err != nil { 504 | {{- if Prometheus}} 505 | clientCallsFor{{$serviceName}}.WithLabelValues( 506 | "{{.GetName}}", c.Encoding, "error").Inc() 507 | {{- end}} 508 | return err 509 | } 510 | 511 | var res {{GoType .GetOutputType}} 512 | for { 513 | err = sub.Next(&res) 514 | if err != nil { 515 | break 516 | } 517 | cb(ctx, &res) 518 | } 519 | if err == nrpc.ErrEOS { 520 | err = nil 521 | } 522 | {{- if Prometheus}} 523 | // report total time taken to Prometheus 524 | elapsed := time.Since(start).Seconds() 525 | clientRCTFor{{$serviceName}}.WithLabelValues("{{.GetName}}").Observe(elapsed) 526 | clientCallsFor{{$serviceName}}.WithLabelValues( 527 | "{{.GetName}}", c.Encoding, "success").Inc() 528 | {{- end}} 529 | return err 530 | } 531 | {{- else}} 532 | 533 | func (c *{{$serviceName}}Client) {{.GetName}}( 534 | {{- range GetMethodSubjectParams . -}} 535 | {{ . }} string, {{ end -}} 536 | {{- if ne .GetInputType ".nrpc.Void" -}} 537 | req *{{GoType .GetInputType}} 538 | {{- end -}}) ( 539 | {{- if not (eq $resultType ".nrpc.Void" ".nrpc.NoReply") -}} 540 | *{{GoType $resultType}}, {{end -}} 541 | error) { 542 | {{- if Prometheus}} 543 | start := time.Now() 544 | {{- end}} 545 | 546 | subject := {{ if ne 0 (len $pkgSubject) -}} 547 | c.PkgSubject + "." + {{end}} 548 | {{- range $pkgSubjectParams -}} 549 | c.PkgParam{{.}} + "." + {{end -}} 550 | c.Subject + "." + {{range $serviceSubjectParams -}} 551 | c.SvcParam{{.}} + "." + {{end -}} 552 | "{{GetMethodSubject .}}" 553 | {{- range GetMethodSubjectParams . }} + "." + {{ . }}{{ end }} 554 | 555 | // call 556 | {{- if eq .GetInputType ".nrpc.Void"}} 557 | var req = &{{GoType .GetInputType}}{} 558 | {{- end}} 559 | var resp = {{GoType $resultType}}{} 560 | if err := nrpc.Call(req, &resp, c.nc, subject, c.Encoding, c.Timeout); err != nil { 561 | {{- if Prometheus}} 562 | clientCallsFor{{$serviceName}}.WithLabelValues( 563 | "{{.GetName}}", c.Encoding, "call_fail").Inc() 564 | {{- end}} 565 | return {{ if not (eq $resultType ".nrpc.Void" ".nrpc.NoReply") }}nil, {{ end }}err 566 | } 567 | 568 | {{- if Prometheus}} 569 | 570 | // report total time taken to Prometheus 571 | elapsed := time.Since(start).Seconds() 572 | clientRCTFor{{$serviceName}}.WithLabelValues("{{.GetName}}").Observe(elapsed) 573 | clientCallsFor{{$serviceName}}.WithLabelValues( 574 | "{{.GetName}}", c.Encoding, "success").Inc() 575 | {{- end}} 576 | 577 | return {{ if not (eq $resultType ".nrpc.Void" ".nrpc.NoReply") }}&resp, {{ end }}nil 578 | } 579 | 580 | {{- if HasPollEnabled .}} 581 | 582 | func (c *{{$serviceName}}Client) {{.GetName}}Poll( 583 | {{- range GetMethodSubjectParams . -}} 584 | {{ . }} string, {{ end -}} 585 | {{- if ne .GetInputType ".nrpc.Void" -}} 586 | req *{{GoType .GetInputType}}, 587 | {{- end -}} 588 | maxreplies int, cb func (*{{GoType .GetOutputType}}) error, 589 | ) (error) { 590 | {{- if Prometheus}} 591 | start := time.Now() 592 | {{- end}} 593 | 594 | subject := {{ if ne 0 (len $pkgSubject) -}} 595 | c.PkgSubject + "." + {{end}} 596 | {{- range $pkgSubjectParams -}} 597 | c.PkgParam{{.}} + "." + {{end -}} 598 | c.Subject + "." + {{range $serviceSubjectParams -}} 599 | c.SvcParam{{.}} + "." + {{end -}} 600 | "{{GetMethodSubject .}}" 601 | {{- range GetMethodSubjectParams . }} + "." + {{ . }}{{ end }} 602 | 603 | {{- if eq .GetInputType ".nrpc.Void"}} 604 | var req = &{{GoType .GetInputType}}{} 605 | {{- end}} 606 | 607 | var resp {{GoType .GetOutputType}} 608 | 609 | err := nrpc.Poll(req, &resp, c.nc, subject, c.Encoding, c.Timeout, maxreplies, 610 | func() error { 611 | return cb(&resp) 612 | }, 613 | ) 614 | if err != nil { 615 | {{- if Prometheus}} 616 | clientCallsFor{{$serviceName}}.WithLabelValues( 617 | "{{.GetName}}", c.Encoding, "poll_fail").Inc() 618 | {{- end}} 619 | return err 620 | } 621 | 622 | {{- if Prometheus}} 623 | 624 | // report total time taken to Prometheus 625 | elapsed := time.Since(start).Seconds() 626 | clientRCTFor{{$serviceName}}.WithLabelValues("{{.GetName}}").Observe(elapsed) 627 | clientCallsFor{{$serviceName}}.WithLabelValues( 628 | "{{.GetName}}", c.Encoding, "poll_success").Inc() 629 | {{- end}} 630 | 631 | return nil 632 | } 633 | {{- end}} 634 | 635 | {{- end}} 636 | {{- end}} 637 | {{- end}} 638 | 639 | type Client struct { 640 | nc nrpc.NatsConn 641 | defaultEncoding string 642 | defaultTimeout time.Duration 643 | {{- if ne 0 (len $pkgSubject)}} 644 | pkgSubject string 645 | {{- end}} 646 | {{- range $pkgSubjectParams}} 647 | pkgParam{{ . }} string 648 | {{- end}} 649 | 650 | {{- range .Service}} 651 | {{.GetName}} *{{.GetName}}Client 652 | {{- end}} 653 | } 654 | 655 | func NewClient(nc nrpc.NatsConn 656 | {{- range $pkgSubjectParams -}} 657 | , pkgParam{{.}} string 658 | {{- end -}}) *Client { 659 | c := Client{ 660 | nc: nc, 661 | defaultEncoding: "protobuf", 662 | defaultTimeout: 5*time.Second, 663 | {{- if ne 0 (len $pkgSubject)}} 664 | pkgSubject: "{{$pkgSubject}}", 665 | {{- end}} 666 | {{- range $pkgSubjectParams}} 667 | pkgParam{{.}}: pkgParam{{.}}, 668 | {{- end}} 669 | } 670 | {{- range .Service}} 671 | {{- if eq 0 (len (GetServiceSubjectParams .))}} 672 | c.{{.GetName}} = New{{.GetName}}Client(nc 673 | {{- range $pkgSubjectParams -}} 674 | , c.pkgParam{{ . }} 675 | {{- end}}) 676 | {{- end}} 677 | {{- end}} 678 | return &c 679 | } 680 | 681 | func (c *Client) SetEncoding(encoding string) { 682 | c.defaultEncoding = encoding 683 | {{- range .Service}} 684 | if c.{{.GetName}} != nil { 685 | c.{{.GetName}}.Encoding = encoding 686 | } 687 | {{- end}} 688 | } 689 | 690 | func (c *Client) SetTimeout(t time.Duration) { 691 | c.defaultTimeout = t 692 | {{- range .Service}} 693 | if c.{{.GetName}} != nil { 694 | c.{{.GetName}}.Timeout = t 695 | } 696 | {{- end}} 697 | } 698 | 699 | {{- range .Service}} 700 | {{- if ne 0 (len (GetServiceSubjectParams .))}} 701 | 702 | func (c *Client) Set{{.GetName}}Params( 703 | {{- range GetServiceSubjectParams .}} 704 | {{ . }} string, 705 | {{- end}} 706 | ) { 707 | c.{{.GetName}} = New{{.GetName}}Client( 708 | c.nc, 709 | {{- range $pkgSubjectParams}} 710 | c.pkgParam{{ . }}, 711 | {{- end}} 712 | {{- range GetServiceSubjectParams .}} 713 | {{ . }}, 714 | {{- end}} 715 | ) 716 | c.{{.GetName}}.Encoding = c.defaultEncoding 717 | c.{{.GetName}}.Timeout = c.defaultTimeout 718 | } 719 | 720 | func (c *Client) New{{.GetName}}( 721 | {{- range GetServiceSubjectParams .}} 722 | {{ . }} string, 723 | {{- end}} 724 | ) *{{.GetName}}Client { 725 | client := New{{.GetName}}Client( 726 | c.nc, 727 | {{- range $pkgSubjectParams}} 728 | c.pkgParam{{ . }}, 729 | {{- end}} 730 | {{- range GetServiceSubjectParams .}} 731 | {{ . }}, 732 | {{- end}} 733 | ) 734 | client.Encoding = c.defaultEncoding 735 | client.Timeout = c.defaultTimeout 736 | return client 737 | } 738 | {{- end}} 739 | {{- end}} 740 | 741 | {{- if Prometheus}} 742 | 743 | func init() { 744 | {{- range .Service}} 745 | // register metrics for service {{.GetName}} 746 | prometheus.MustRegister(clientRCTFor{{.GetName}}) 747 | prometheus.MustRegister(serverHETFor{{.GetName}}) 748 | prometheus.MustRegister(clientCallsFor{{.GetName}}) 749 | prometheus.MustRegister(serverRequestsFor{{.GetName}}) 750 | {{- end}} 751 | } 752 | {{- end}}` 753 | -------------------------------------------------------------------------------- /testrunner_test.go: -------------------------------------------------------------------------------- 1 | package nrpc 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | "github.com/nats-io/nats-server/v2/logger" 10 | "github.com/nats-io/nats-server/v2/server" 11 | ) 12 | 13 | var NatsURL string 14 | 15 | func TestMain(m *testing.M) { 16 | gnatsd := server.New(&server.Options{Port: server.RANDOM_PORT}) 17 | gnatsd.SetLogger( 18 | logger.NewStdLogger(false, false, false, false, false), 19 | false, false) 20 | go gnatsd.Start() 21 | defer gnatsd.Shutdown() 22 | 23 | if !gnatsd.ReadyForConnections(time.Second) { 24 | log.Fatal("Cannot start the gnatsd server") 25 | } 26 | NatsURL = "nats://" + gnatsd.Addr().String() 27 | 28 | os.Exit(m.Run()) 29 | } 30 | --------------------------------------------------------------------------------