├── .gitignore ├── archive ├── docs │ ├── .nojekyll │ ├── c4 │ │ ├── php.md │ │ ├── java.md │ │ ├── python.md │ │ └── nodejs.md │ ├── c2 │ │ ├── stream.md │ │ ├── conn-manage.md │ │ ├── load-balancer.md │ │ ├── trace.md │ │ ├── hello-grpc.md │ │ └── interceptor.md │ ├── c3 │ │ └── middleware.md │ ├── .DS_Store │ ├── _media │ │ ├── favicon.ico │ │ ├── logo-grpc.png │ │ ├── logo-gopher.png │ │ ├── grpc_rest_gateway.png │ │ ├── grpc_trace_events.jpg │ │ ├── grpc_trace_requests.jpg │ │ ├── grpc_concept_diagram_00.png │ │ ├── icon.svg │ │ └── bar.svg │ ├── _coverpage.md │ ├── _sidebar.md │ ├── reference.md │ ├── README.md │ ├── c1 │ │ ├── install.md │ │ └── protobuf-go.md │ └── index.html └── src │ ├── .DS_Store │ ├── hello │ ├── .DS_Store │ ├── client │ │ └── main.go │ └── server │ │ └── main.go │ ├── proto │ ├── .DS_Store │ ├── hello │ │ ├── hello.proto │ │ └── hello.pb.go │ ├── compile.sh │ ├── hello_http │ │ ├── hello_http.proto │ │ └── hello_http.pb.gw.go │ ├── README.md │ ├── test │ │ └── test.proto │ └── google │ │ └── api │ │ ├── annotations.proto │ │ └── annotations.pb.go │ ├── hello_http │ ├── .DS_Store │ ├── server_http │ │ └── main.go │ ├── client │ │ └── main.go │ └── server │ │ └── main.go │ ├── hello_tls │ ├── .DS_Store │ ├── client │ │ └── main.go │ └── server │ │ └── main.go │ ├── hello_http_2 │ ├── .DS_Store │ ├── client │ │ └── main.go │ └── server │ │ └── main.go │ ├── hello_token │ ├── .DS_Store │ ├── client │ │ └── main.go │ └── server │ │ └── main.go │ ├── hello_trace │ ├── .DS_Store │ ├── client │ │ └── main.go │ └── server │ │ └── main.go │ ├── hello_interceptor │ ├── .DS_Store │ ├── client │ │ └── main.go │ └── server │ │ └── main.go │ ├── go.mod │ └── keys │ ├── server.key │ └── server.pem ├── src ├── docs │ ├── advance │ │ ├── monitor │ │ │ ├── index.md │ │ │ ├── log.md │ │ │ ├── metrics.md │ │ │ └── tracing.md │ │ ├── index.md │ │ ├── metadata.md │ │ └── auth.md │ ├── assets │ │ ├── favicon.ico │ │ ├── logo-grpc.png │ │ ├── logo-gopher.png │ │ ├── grpc_rest_gateway.png │ │ ├── grpc_trace_events.jpg │ │ ├── grpc_trace_requests.jpg │ │ ├── grpc_concept_diagram_00.png │ │ ├── icon.svg │ │ └── bar.svg │ ├── ecosystem │ │ ├── grpcurl.md │ │ ├── middleware.md │ │ └── index.md │ ├── SUMMARY.md │ ├── reference.md │ ├── basic │ │ ├── index.md │ │ ├── hello-grpc.md │ │ └── stream.md │ └── preface.md ├── auth │ ├── client_test.go │ ├── keys │ │ ├── gen.sh │ │ ├── server.crt │ │ └── server.key │ ├── client.go │ └── server.go ├── go.mod ├── metadata │ ├── client_test.go │ ├── server.go │ └── client.go ├── protos │ ├── ping │ │ └── ping.proto │ ├── google │ │ ├── api │ │ │ ├── annotations.proto │ │ │ └── annotations.pb.go │ │ └── protobuf │ │ │ ├── source_context.proto │ │ │ ├── empty.proto │ │ │ ├── struct.proto │ │ │ ├── wrappers.proto │ │ │ └── duration.proto │ └── example │ │ └── example.proto ├── ping │ ├── client_test.go │ ├── server.go │ └── client.go ├── interceptor │ ├── client_test.go │ ├── server.go │ └── client.go └── go.sum ├── docs ├── .nojekyll ├── favicon.png ├── assets │ ├── favicon.ico │ ├── logo-gopher.png │ ├── logo-grpc.png │ ├── grpc_rest_gateway.png │ ├── grpc_trace_events.jpg │ ├── grpc_trace_requests.jpg │ ├── grpc_concept_diagram_00.png │ ├── icon.svg │ └── bar.svg ├── FontAwesome │ └── fonts │ │ ├── FontAwesome.ttf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 ├── fonts │ ├── open-sans-v17-all-charsets-300.woff2 │ ├── open-sans-v17-all-charsets-600.woff2 │ ├── open-sans-v17-all-charsets-700.woff2 │ ├── open-sans-v17-all-charsets-800.woff2 │ ├── open-sans-v17-all-charsets-italic.woff2 │ ├── open-sans-v17-all-charsets-regular.woff2 │ ├── open-sans-v17-all-charsets-300italic.woff2 │ ├── open-sans-v17-all-charsets-600italic.woff2 │ ├── open-sans-v17-all-charsets-700italic.woff2 │ ├── open-sans-v17-all-charsets-800italic.woff2 │ ├── source-code-pro-v11-all-charsets-500.woff2 │ ├── fonts.css │ └── SOURCE-CODE-PRO-LICENSE.txt ├── css │ ├── print.css │ └── general.css ├── ayu-highlight.css ├── highlight.css ├── theme │ ├── pagetoc.css │ └── pagetoc.js ├── favicon.svg └── tomorrow-night.css ├── book.toml ├── README.md ├── LICENSE └── theme ├── pagetoc.css └── pagetoc.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | -------------------------------------------------------------------------------- /archive/docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /archive/docs/c4/php.md: -------------------------------------------------------------------------------- 1 | # PHP 2 | -------------------------------------------------------------------------------- /archive/docs/c2/stream.md: -------------------------------------------------------------------------------- 1 | # 流式调用 2 | -------------------------------------------------------------------------------- /archive/docs/c4/java.md: -------------------------------------------------------------------------------- 1 | # Java 2 | -------------------------------------------------------------------------------- /archive/docs/c4/python.md: -------------------------------------------------------------------------------- 1 | # Python 2 | -------------------------------------------------------------------------------- /archive/docs/c3/middleware.md: -------------------------------------------------------------------------------- 1 | # 中间件 2 | -------------------------------------------------------------------------------- /archive/docs/c4/nodejs.md: -------------------------------------------------------------------------------- 1 | # node.js 2 | -------------------------------------------------------------------------------- /archive/docs/c2/conn-manage.md: -------------------------------------------------------------------------------- 1 | # 连接管理 2 | 3 | -------------------------------------------------------------------------------- /archive/docs/c2/load-balancer.md: -------------------------------------------------------------------------------- 1 | # 负载均衡(load-balancer) 2 | -------------------------------------------------------------------------------- /src/docs/advance/monitor/index.md: -------------------------------------------------------------------------------- 1 | # 监控 2 | 3 | --- 4 | -------------------------------------------------------------------------------- /src/docs/advance/monitor/log.md: -------------------------------------------------------------------------------- 1 | # log 2 | 3 | --- 4 | -------------------------------------------------------------------------------- /src/docs/advance/monitor/metrics.md: -------------------------------------------------------------------------------- 1 | # metrics 2 | 3 | --- 4 | 5 | -------------------------------------------------------------------------------- /src/docs/advance/index.md: -------------------------------------------------------------------------------- 1 | # 进阶 2 | 3 | --- 4 | 这个部分会介绍一些 gRPC 的高级功能特性。 5 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | This file makes sure that Github Pages doesn't process mdBook's output. 2 | -------------------------------------------------------------------------------- /docs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/favicon.png -------------------------------------------------------------------------------- /archive/docs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/docs/.DS_Store -------------------------------------------------------------------------------- /archive/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/src/.DS_Store -------------------------------------------------------------------------------- /docs/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/assets/favicon.ico -------------------------------------------------------------------------------- /archive/src/hello/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/src/hello/.DS_Store -------------------------------------------------------------------------------- /archive/src/proto/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/src/proto/.DS_Store -------------------------------------------------------------------------------- /docs/assets/logo-gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/assets/logo-gopher.png -------------------------------------------------------------------------------- /docs/assets/logo-grpc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/assets/logo-grpc.png -------------------------------------------------------------------------------- /src/docs/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/src/docs/assets/favicon.ico -------------------------------------------------------------------------------- /src/docs/assets/logo-grpc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/src/docs/assets/logo-grpc.png -------------------------------------------------------------------------------- /archive/docs/_media/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/docs/_media/favicon.ico -------------------------------------------------------------------------------- /archive/src/hello_http/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/src/hello_http/.DS_Store -------------------------------------------------------------------------------- /archive/src/hello_tls/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/src/hello_tls/.DS_Store -------------------------------------------------------------------------------- /src/docs/assets/logo-gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/src/docs/assets/logo-gopher.png -------------------------------------------------------------------------------- /src/docs/ecosystem/grpcurl.md: -------------------------------------------------------------------------------- 1 | # grpcurl 2 | 3 | --- 4 | 5 | > 项目地址:[grpcurl](https://github.com/fullstorydev/grpcurl) 6 | -------------------------------------------------------------------------------- /archive/docs/_media/logo-grpc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/docs/_media/logo-grpc.png -------------------------------------------------------------------------------- /archive/src/hello_http_2/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/src/hello_http_2/.DS_Store -------------------------------------------------------------------------------- /archive/src/hello_token/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/src/hello_token/.DS_Store -------------------------------------------------------------------------------- /archive/src/hello_trace/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/src/hello_trace/.DS_Store -------------------------------------------------------------------------------- /docs/assets/grpc_rest_gateway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/assets/grpc_rest_gateway.png -------------------------------------------------------------------------------- /docs/assets/grpc_trace_events.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/assets/grpc_trace_events.jpg -------------------------------------------------------------------------------- /archive/docs/_media/logo-gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/docs/_media/logo-gopher.png -------------------------------------------------------------------------------- /docs/assets/grpc_trace_requests.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/assets/grpc_trace_requests.jpg -------------------------------------------------------------------------------- /src/docs/assets/grpc_rest_gateway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/src/docs/assets/grpc_rest_gateway.png -------------------------------------------------------------------------------- /src/docs/assets/grpc_trace_events.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/src/docs/assets/grpc_trace_events.jpg -------------------------------------------------------------------------------- /archive/src/hello_interceptor/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/src/hello_interceptor/.DS_Store -------------------------------------------------------------------------------- /docs/FontAwesome/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/FontAwesome/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /docs/assets/grpc_concept_diagram_00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/assets/grpc_concept_diagram_00.png -------------------------------------------------------------------------------- /src/docs/assets/grpc_trace_requests.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/src/docs/assets/grpc_trace_requests.jpg -------------------------------------------------------------------------------- /archive/docs/_media/grpc_rest_gateway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/docs/_media/grpc_rest_gateway.png -------------------------------------------------------------------------------- /archive/docs/_media/grpc_trace_events.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/docs/_media/grpc_trace_events.jpg -------------------------------------------------------------------------------- /archive/docs/_media/grpc_trace_requests.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/docs/_media/grpc_trace_requests.jpg -------------------------------------------------------------------------------- /src/docs/assets/grpc_concept_diagram_00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/src/docs/assets/grpc_concept_diagram_00.png -------------------------------------------------------------------------------- /archive/docs/_media/grpc_concept_diagram_00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/archive/docs/_media/grpc_concept_diagram_00.png -------------------------------------------------------------------------------- /docs/FontAwesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/FontAwesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/FontAwesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/FontAwesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/FontAwesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/FontAwesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/open-sans-v17-all-charsets-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/fonts/open-sans-v17-all-charsets-300.woff2 -------------------------------------------------------------------------------- /docs/fonts/open-sans-v17-all-charsets-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/fonts/open-sans-v17-all-charsets-600.woff2 -------------------------------------------------------------------------------- /docs/fonts/open-sans-v17-all-charsets-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/fonts/open-sans-v17-all-charsets-700.woff2 -------------------------------------------------------------------------------- /docs/fonts/open-sans-v17-all-charsets-800.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/fonts/open-sans-v17-all-charsets-800.woff2 -------------------------------------------------------------------------------- /docs/FontAwesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/FontAwesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/docs/ecosystem/middleware.md: -------------------------------------------------------------------------------- 1 | # gRPC Middleware 2 | 3 | --- 4 | 5 | > 项目地址:[go-grpc-middleware](https://github.com/grpc-ecosystem/go-grpc-middleware) 6 | -------------------------------------------------------------------------------- /docs/fonts/open-sans-v17-all-charsets-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/fonts/open-sans-v17-all-charsets-italic.woff2 -------------------------------------------------------------------------------- /docs/fonts/open-sans-v17-all-charsets-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/fonts/open-sans-v17-all-charsets-regular.woff2 -------------------------------------------------------------------------------- /docs/fonts/open-sans-v17-all-charsets-300italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/fonts/open-sans-v17-all-charsets-300italic.woff2 -------------------------------------------------------------------------------- /docs/fonts/open-sans-v17-all-charsets-600italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/fonts/open-sans-v17-all-charsets-600italic.woff2 -------------------------------------------------------------------------------- /docs/fonts/open-sans-v17-all-charsets-700italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/fonts/open-sans-v17-all-charsets-700italic.woff2 -------------------------------------------------------------------------------- /docs/fonts/open-sans-v17-all-charsets-800italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/fonts/open-sans-v17-all-charsets-800italic.woff2 -------------------------------------------------------------------------------- /docs/fonts/source-code-pro-v11-all-charsets-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jergoo/go-grpc-tutorial/HEAD/docs/fonts/source-code-pro-v11-all-charsets-500.woff2 -------------------------------------------------------------------------------- /src/docs/ecosystem/index.md: -------------------------------------------------------------------------------- 1 | # gRPC 生态 2 | 3 | --- 4 | 5 | gRPC 的发展非常迅速,从 gRPC 扩展出来的生态包含了各种各样的工具,这部分介绍几个常用的工具。 6 | 7 | > 项目地址:[grpc-ecosystem](https://github.com/grpc-ecosystem) 8 | -------------------------------------------------------------------------------- /archive/src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jergoo/go-grpc-example 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/golang/protobuf v1.4.2 7 | github.com/grpc-ecosystem/grpc-gateway v1.14.6 8 | golang.org/x/net v0.0.0-20200625001655-4c5254603344 9 | google.golang.org/grpc v1.30.0 10 | ) 11 | -------------------------------------------------------------------------------- /src/auth/client_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestPing(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | }{ 11 | {name: "ping"}, 12 | } 13 | for _, tt := range tests { 14 | t.Run(tt.name, func(t *testing.T) { 15 | Ping() 16 | }) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/auth/keys/gen.sh: -------------------------------------------------------------------------------- 1 | #! bin/sh 2 | 3 | openssl req -newkey rsa:2048 -x509 -nodes -sha256 -days 3650 \ 4 | -keyout server.key -new -out server.crt \ 5 | -subj /CN=grpc.server -reqexts SAN -extensions SAN \ 6 | -config <(cat /System/Library/OpenSSL/openssl.cnf \ 7 | <(printf '[SAN]\nsubjectAltName=DNS:grpc.server')) 8 | 9 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | title = "Go gRPC 简明指南" 3 | authors = ["jergoo"] 4 | description = "Go gRPC Tutorial" 5 | language = "zh" 6 | multilingual = false 7 | src = "src/docs" 8 | 9 | [build] 10 | build-dir = "docs" 11 | 12 | [preprocessor.pagetoc] 13 | [output.html] 14 | additional-css = ["theme/pagetoc.css"] 15 | additional-js = ["theme/pagetoc.js"] 16 | -------------------------------------------------------------------------------- /archive/src/keys/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BgUrgQQAIg== 3 | -----END EC PARAMETERS----- 4 | -----BEGIN EC PRIVATE KEY----- 5 | MIGkAgEBBDCGtG0mVhqS2S+XmFnqBo/gHPH6TPxw0C9AujL/I7+IFldRoRuaFX5u 6 | Z2PyqzDcqZmgBwYFK4EEACKhZANiAAQ9FD9C+8rnCfPW4hy/KRFOeYuXvH5TBc4t 7 | tqvKUAsmpnQVYuxYpr5k2p+w1sRN0l8B+uMmFXzmB1wCSiGwOrCEEh2wFao7zeeS 8 | 0KFc7jZpy6Lkch6eS6DK7NayrdENwLw= 9 | -----END EC PRIVATE KEY----- 10 | -------------------------------------------------------------------------------- /src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jergoo/go-grpc-tutorial 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/golang/protobuf v1.5.2 7 | google.golang.org/grpc v1.51.0 8 | google.golang.org/protobuf v1.28.1 9 | ) 10 | 11 | require ( 12 | golang.org/x/net v0.2.0 // indirect 13 | golang.org/x/sys v0.2.0 // indirect 14 | golang.org/x/text v0.4.0 // indirect 15 | google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /archive/src/proto/hello/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; // 指定proto版本 2 | package hello; // 指定包名 3 | 4 | option go_package = "hello"; 5 | 6 | // 定义Hello服务 7 | service Hello { 8 | // 定义SayHello方法 9 | rpc SayHello(HelloRequest) returns (HelloResponse) {} 10 | } 11 | 12 | // HelloRequest 请求结构 13 | message HelloRequest { 14 | string name = 1; 15 | } 16 | 17 | // HelloResponse 响应结构 18 | message HelloResponse { 19 | string message = 1; 20 | } 21 | -------------------------------------------------------------------------------- /archive/docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | ![logo](_media/logo-gopher.png ':id=logo-go') 2 | ![logo](_media/logo-grpc.png ':id=logo-grpc') 3 | 4 |
5 | 6 |
7 | 8 |
Go gRPC Tutorial
9 |
10 | 11 | -

🚚_coding choo-choo_🚌🚌🚌🚌🚌🚌🚌

12 | 13 | 14 | [GitHub](https://github.com/jergoo/go-grpc-tutorial) 15 | [开始学习](#gRPC) 16 | 17 | ![color](#2fc4b2) 18 | -------------------------------------------------------------------------------- /src/metadata/client_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestPing(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | }{ 11 | {name: "ping"}, 12 | } 13 | for _, tt := range tests { 14 | t.Run(tt.name, func(t *testing.T) { 15 | Ping() 16 | }) 17 | } 18 | } 19 | 20 | func TestMultiPong(t *testing.T) { 21 | tests := []struct { 22 | name string 23 | }{ 24 | {name: "multi pong"}, 25 | } 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | MultiPong() 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /archive/docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * **入门** 2 | * [安装](c1/install.md "grpc install") 3 | * [Protobuf语法](c1/protobuf.md) 4 | * [Protobuf⇢Go转换](c1/protobuf-go.md) 5 | * **实践** 6 | * [Hello gRPC](c2/hello-grpc.md) 7 | * [认证](c2/auth.md) 8 | * [拦截器](c2/interceptor.md) 9 | * [流式传输](c2/stream.md) 10 | * [内置Trace](c2/trace.md) 11 | * [负载均衡](c2/load-balancer.md) 12 | * **进阶** 13 | * [Http网关](c3/gateway.md) 14 | * **多语言** 15 | * [Java](c4/java.md) 16 | * [PHP](c4/php.md) 17 | * [Python](c4/python.md) 18 | * [Node.js](c4/nodejs.md) 19 | * [**参考**](reference.md) 20 | -------------------------------------------------------------------------------- /src/docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [前言](./preface.md) 4 | 5 | --- 6 | 7 | - [入门](./basic/index.md) 8 | - [Hello gRPC](./basic/hello-grpc.md) 9 | - [gRPC 流](./basic/stream.md) 10 | - [Protobuf](./basic/protobuf.md) 11 | 12 | - [进阶](./advance/index.md) 13 | - [拦截器](./advance/interceptor.md) 14 | - [metadata](./advance/metadata.md) 15 | - [安全认证](./advance/auth.md) 16 | 17 | - [生态](./ecosystem/index.md) 18 | - [gRPC Gateway](./ecosystem/gateway.md) 19 | - [gRPC Middleware](./ecosystem/middleware.md) 20 | - [grpcurl](./ecosystem/grpcurl.md) 21 | 22 | --- 23 | 24 | [参考](./reference.md) 25 | -------------------------------------------------------------------------------- /archive/docs/reference.md: -------------------------------------------------------------------------------- 1 | # 参考 2 | 3 | ## 相关资料: 4 | 5 | * [grpc官网](https://grpc.io/) 6 | * [grpc中文文档](http://doc.oschina.net/grpc) 7 | * [protobuf官方文档](https://developers.google.com/protocol-buffers/) 8 | 9 | 10 | ## 相关项目 11 | 12 | * [grpc/grpc](https://github.com/grpc/grpc) 13 | * [grpc-go](https://github.com/grpc/grpc-go/) 14 | * [grpc-java](https://github.com/grpc/grpc-java) 15 | * [google/protobuf](https://github.com/google/protobuf) 16 | * [golang/protobuf](https://github.com/golang/protobuf) 17 | * [grpc-middleware](https://github.com/grpc-ecosystem/go-grpc-middleware) 18 | * [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) 19 | -------------------------------------------------------------------------------- /archive/src/proto/compile.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # 编译test.proto 4 | protoc -I . --go_out=plugins=grpc:. test/test.proto 5 | 6 | # 编译hello.proto 7 | protoc -I . --go_out=plugins=grpc:. hello/hello.proto 8 | 9 | # 编译google api,新版编译器可以省略M参数 10 | protoc -I . --go_out=plugins=grpc,Mgoogle/protobuf/descriptor.proto=github.com/golang/protobuf/protoc-gen-go/descriptor:. google/api/*.proto 11 | 12 | # 编译hello_http.proto 13 | protoc -I . --go_out=plugins=grpc,Mgoogle/api/annotations.proto=github.com/jergoo/go-grpc-example/proto/google/api:. hello_http/*.proto 14 | 15 | # 编译hello_http.proto gateway 16 | protoc --grpc-gateway_out=logtostderr=true:. hello_http/hello_http.proto 17 | -------------------------------------------------------------------------------- /archive/src/proto/hello_http/hello_http.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package hello_http; 4 | option go_package = "hello_http"; 5 | 6 | import "google/api/annotations.proto"; 7 | 8 | // 定义Hello服务 9 | service HelloHTTP { 10 | // 定义SayHello方法 11 | rpc SayHello(HelloHTTPRequest) returns (HelloHTTPResponse) { 12 | // http option 13 | option (google.api.http) = { 14 | post: "/example/echo" 15 | body: "*" 16 | }; 17 | } 18 | } 19 | 20 | // HelloRequest 请求结构 21 | message HelloHTTPRequest { 22 | string name = 1; 23 | } 24 | 25 | // HelloResponse 响应结构 26 | message HelloHTTPResponse { 27 | string message = 1; 28 | } 29 | -------------------------------------------------------------------------------- /src/protos/ping/ping.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; // 指定proto版本 2 | package protos; // 指定包名 3 | 4 | // 指定go包路径 5 | option go_package = "protos/ping"; 6 | 7 | // 定义PingPong服务 8 | service PingPong { 9 | // 单次请求-响应模式 10 | rpc Ping(PingRequest) returns (PongResponse); 11 | // 服务端流模式 12 | rpc MultiPong(PingRequest) returns (stream PongResponse); 13 | // 客户端流模式 14 | rpc MultiPing(stream PingRequest) returns (PongResponse); 15 | // 双向流模式 16 | rpc MultiPingPong(stream PingRequest) returns (stream PongResponse); 17 | } 18 | 19 | // PingRequest 请求结构 20 | message PingRequest { 21 | string value = 1; 22 | } 23 | 24 | // PongResponse 响应结构 25 | message PongResponse { 26 | string value = 1; 27 | } 28 | -------------------------------------------------------------------------------- /src/docs/reference.md: -------------------------------------------------------------------------------- 1 | # 参考 2 | 3 | --- 4 | 5 | > **Doc & Book** 6 | > 7 | > * [gRPC website](https://grpc.io/) 8 | > * [protobuf website](https://developers.google.com/protocol-buffers) 9 | > * [gRPC: Up and Running](https://www.oreilly.com/library/view/grpc-up-and/9781492058328/) 10 | 11 | > **Projects** 12 | > 13 | > * [grpc/grpc](https://github.com/grpc/grpc) 14 | > * [grpc-go](https://github.com/grpc/grpc-go/) 15 | > * [google/protobuf](https://github.com/google/protobuf) 16 | > * [golang/protobuf](https://github.com/golang/protobuf) 17 | > * [grpc-middleware](https://github.com/grpc-ecosystem/go-grpc-middleware) 18 | > * [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) 19 | > * [grpcurl](https://github.com/fullstorydev/grpcurl) 20 | > * [buf](https://buf.build/) 21 | -------------------------------------------------------------------------------- /archive/src/hello/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入proto包 5 | 6 | "golang.org/x/net/context" 7 | "google.golang.org/grpc" 8 | "google.golang.org/grpc/grpclog" 9 | ) 10 | 11 | const ( 12 | // Address gRPC服务地址 13 | Address = "127.0.0.1:50052" 14 | ) 15 | 16 | func main() { 17 | // 连接 18 | conn, err := grpc.Dial(Address, grpc.WithInsecure()) 19 | if err != nil { 20 | grpclog.Fatalln(err) 21 | } 22 | defer conn.Close() 23 | 24 | // 初始化客户端 25 | c := pb.NewHelloClient(conn) 26 | 27 | // 调用方法 28 | req := &pb.HelloRequest{Name: "gRPC"} 29 | res, err := c.SayHello(context.Background(), req) 30 | 31 | if err != nil { 32 | grpclog.Fatalln(err) 33 | } 34 | 35 | grpclog.Println(res.Message) 36 | } 37 | -------------------------------------------------------------------------------- /archive/src/hello_trace/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入proto包 5 | 6 | "golang.org/x/net/context" 7 | "google.golang.org/grpc" 8 | "google.golang.org/grpc/grpclog" 9 | ) 10 | 11 | const ( 12 | // Address gRPC服务地址 13 | Address = "127.0.0.1:50052" 14 | ) 15 | 16 | func main() { 17 | // 连接 18 | conn, err := grpc.Dial(Address, grpc.WithInsecure()) 19 | if err != nil { 20 | grpclog.Fatalln(err) 21 | } 22 | defer conn.Close() 23 | 24 | // 初始化客户端 25 | c := pb.NewHelloClient(conn) 26 | 27 | // 调用方法 28 | req := &pb.HelloRequest{Name: "gRPC"} 29 | res, err := c.SayHello(context.Background(), req) 30 | if err != nil { 31 | grpclog.Fatalln(err) 32 | } 33 | 34 | grpclog.Println(res.Message) 35 | } 36 | -------------------------------------------------------------------------------- /archive/src/proto/README.md: -------------------------------------------------------------------------------- 1 | # 编译器使用 2 | 3 | ## 使用`protoc`命令编译`.proto`文件: 4 | 5 | * -I 参数:指定import路径,可以指定多个 -I参数,按顺序查找,默认只查找当前目录 6 | * --go_out :golang编译支持,支持以下参数: 7 | * plugins=plugin1+plugin2 - 指定插件,目前只有grpc,即:plugins=grpc 8 | * M 参数 - 指定导入的.proto文件路径编译后对应的golang包名(不指定本参数默认就是.proto文件中import语句的路径) 9 | * import_prefix=xxx - 为所有import路径添加前缀,主要用于编译子目录内的多个proto文件,这个参数按理说很有用,尤其适用替代hello_http.proto编译时的M参数,但是实际使用时有个蛋疼的问题,自己尝试看看吧 10 | * import_path=foo/bar - 用于指定未声明package或go_package的文件的包名,最右面的斜线前的字符会被忽略 11 | * :编译文件路径 .proto文件路径(支持通配符) 12 | * 同一个包内包含多个.proto文件时使用通配符同时编译所有文件,单独编译每个文件会存在变量命名冲突,例如编译hello_http.proto那里所示 13 | 14 | > 完整示例: 15 | 16 | ```protobuf 17 | protoc --go_out=plugins=grpc,Mfoo/bar.proto=bar,import_prefix=foo/,import_path=foo/bar:. ./*.proto 18 | ``` 19 | -------------------------------------------------------------------------------- /src/docs/basic/index.md: -------------------------------------------------------------------------------- 1 | # 入门 2 | 3 | --- 4 | 5 | 这个部分通过创建一个简单的服务说明 go gRPC 的基本使用方法和场景,并介绍 protobuf 的基本语法。 6 | 7 | ## 环境准备 8 | 9 | ### protobuf 编译器 10 | 11 | 项目地址:[google/protobuf](https://github.com/google/protobuf) 12 | 13 | 这里使用`brew`工具安装,也可以下载编译好的可执行文件: 14 | 15 | ```sh 16 | $ brew install protobuf 17 | ``` 18 | 19 | 执行`protoc`命令查看当前版本: 20 | 21 | ```sh 22 | $ protoc --version 23 | libprotoc 3.21.6 24 | ``` 25 | 26 | ### go 编译插件 27 | 28 | 项目地址: 29 | * [protobuf-go](https://github.com/protocolbuffers/protobuf-go) 30 | * [grpc-go](https://github.com/grpc/grpc-go) 31 | 32 | 安装: 33 | ```sh 34 | $ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 35 | $ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 36 | 37 | $ export PATH="$PATH:$(go env GOPATH)/bin" 38 | ``` 39 | -------------------------------------------------------------------------------- /src/docs/preface.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 | --- 4 | 5 | 10 | 11 |
12 | logo 13 | logo 14 |
15 | 16 | [gRPC](https://grpc.io/) 是一个高性能、开源、通用的 RPC 框架,由 Google 推出,基于 [HTTP2](https://http2.github.io/) 协议标准设计开发,采用 [Protocol Buffers](https://developers.google.com/protocol-buffers/) 数据序列化协议,支持多种开发语言,广泛应用于分布式系统服务间的调用。 17 | 18 | 本项目旨在通过一些简单的示例,提供一个简明的 go gRPC 使用指南,帮助新手快速学会使用 gRPC,而非深究其底层原理,内容包含 gRPC 常用的功能特性,以及 gRPC 生态中的一些工具。 19 | 20 | > 本项目及源码地址:[go-grpc-tutorial](https://github.com/jergoo/go-grpc-tutorial) 21 | 22 | 23 | -------------------------------------------------------------------------------- /archive/src/hello_http/server_http/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/grpc-ecosystem/grpc-gateway/runtime" 7 | "golang.org/x/net/context" 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/grpclog" 10 | 11 | gw "github.com/jergoo/go-grpc-example/proto/hello_http" 12 | ) 13 | 14 | func main() { 15 | ctx := context.Background() 16 | ctx, cancel := context.WithCancel(ctx) 17 | defer cancel() 18 | 19 | // grpc服务地址 20 | endpoint := "127.0.0.1:50052" 21 | mux := runtime.NewServeMux() 22 | opts := []grpc.DialOption{grpc.WithInsecure()} 23 | 24 | // HTTP转grpc 25 | err := gw.RegisterHelloHTTPHandlerFromEndpoint(ctx, mux, endpoint, opts) 26 | if err != nil { 27 | grpclog.Fatalf("Register handler err:%v\n", err) 28 | } 29 | 30 | grpclog.Println("HTTP Listen on 8080") 31 | http.ListenAndServe(":8080", mux) 32 | } 33 | -------------------------------------------------------------------------------- /archive/src/proto/test/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package test; 3 | 4 | option go_package="test"; 5 | 6 | // TestService 测试服务 7 | service TestService { 8 | // Test 测试方法 9 | rpc Test(Request) returns (Response) {}; 10 | } 11 | 12 | // Request 请求结构 13 | message Request { 14 | string name = 1; 15 | } 16 | 17 | // Response 响应结构 18 | message Response { 19 | string message = 1; 20 | } 21 | 22 | // Test 测试 23 | message Test { 24 | int32 age = 1; 25 | int64 count = 2; 26 | double money = 3; 27 | float score = 4; 28 | string name = 5; 29 | bool fat = 6; 30 | bytes char = 7; 31 | // Status 枚举状态 32 | enum Status { 33 | OK = 0; 34 | FAIL = 1; 35 | } 36 | Status status = 8; 37 | // Child 子结构 38 | message Child { 39 | string sex = 1; 40 | } 41 | Child child = 9; 42 | map dict = 10; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /archive/src/hello_http/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | pb "github.com/jergoo/go-grpc-example/proto/hello_http" 5 | "golang.org/x/net/context" 6 | "google.golang.org/grpc" 7 | "google.golang.org/grpc/grpclog" 8 | ) 9 | 10 | const ( 11 | // Address gRPC服务地址 12 | Address = "127.0.0.1:50052" 13 | ) 14 | 15 | func main() { 16 | // 连接 17 | conn, err := grpc.Dial(Address, grpc.WithInsecure()) 18 | 19 | if err != nil { 20 | grpclog.Fatalln(err) 21 | } 22 | 23 | defer conn.Close() 24 | 25 | // 初始化客户端 26 | c := pb.NewHelloHTTPClient(conn) 27 | 28 | // 调用方法 29 | reqBody := new(pb.HelloHTTPRequest) 30 | reqBody.Name = "gRPC" 31 | r, err := c.SayHello(context.Background(), reqBody) 32 | if err != nil { 33 | grpclog.Fatalln(err) 34 | } 35 | 36 | grpclog.Println(r.Message) 37 | } 38 | 39 | // OR: curl -X POST -k http://localhost:8080/example/echo -d '{"name": "gRPC-HTTP is working!"}' 40 | -------------------------------------------------------------------------------- /docs/css/print.css: -------------------------------------------------------------------------------- 1 | 2 | #sidebar, 3 | #menu-bar, 4 | .nav-chapters, 5 | .mobile-nav-chapters { 6 | display: none; 7 | } 8 | 9 | #page-wrapper.page-wrapper { 10 | transform: none; 11 | margin-left: 0px; 12 | overflow-y: initial; 13 | } 14 | 15 | #content { 16 | max-width: none; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | .page { 22 | overflow-y: initial; 23 | } 24 | 25 | code { 26 | background-color: #666666; 27 | border-radius: 5px; 28 | 29 | /* Force background to be printed in Chrome */ 30 | -webkit-print-color-adjust: exact; 31 | } 32 | 33 | pre > .buttons { 34 | z-index: 2; 35 | } 36 | 37 | a, a:visited, a:active, a:hover { 38 | color: #4183c4; 39 | text-decoration: none; 40 | } 41 | 42 | h1, h2, h3, h4, h5, h6 { 43 | page-break-inside: avoid; 44 | page-break-after: avoid; 45 | } 46 | 47 | pre, code { 48 | page-break-inside: avoid; 49 | white-space: pre-wrap; 50 | } 51 | 52 | .fa { 53 | display: none !important; 54 | } 55 | -------------------------------------------------------------------------------- /archive/src/hello_tls/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入proto包 5 | 6 | "golang.org/x/net/context" 7 | "google.golang.org/grpc" 8 | "google.golang.org/grpc/credentials" // 引入grpc认证包 9 | "google.golang.org/grpc/grpclog" 10 | ) 11 | 12 | const ( 13 | // Address gRPC服务地址 14 | Address = "127.0.0.1:50052" 15 | ) 16 | 17 | func main() { 18 | // TLS连接 19 | creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "server name") 20 | if err != nil { 21 | grpclog.Fatalf("Failed to create TLS credentials %v", err) 22 | } 23 | 24 | conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds)) 25 | if err != nil { 26 | grpclog.Fatalln(err) 27 | } 28 | defer conn.Close() 29 | 30 | // 初始化客户端 31 | c := pb.NewHelloClient(conn) 32 | 33 | // 调用方法 34 | req := &pb.HelloRequest{Name: "gRPC"} 35 | res, err := c.SayHello(context.Background(), req) 36 | if err != nil { 37 | grpclog.Fatalln(err) 38 | } 39 | 40 | grpclog.Println(res.Message) 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go gRPC Tutorial 2 | 3 | 一份简明的 go gRPC 使用指南,包含 gRPC 常用的功能特性,以及 gRPC 生态中的常用工具。 4 | 5 | 在线阅读:[Go gRPC Tutorial](https://jergoo.github.io/go-grpc-tutorial/) 6 | 7 | 更新进度: 8 | 9 | - [x] 入门 10 | - [x] Hello gRPC 11 | - [x] gRPC 流 12 | - [x] Protobuf 13 | 14 | - [x] 进阶 15 | - [x] 拦截器 16 | - [x] metadata 17 | - [x] 安全认证 18 | 19 | - [ ] 生态 20 | - [ ] gRPC Gateway 21 | - [ ] gRPC Middleware 22 | - [ ] grpcurl 23 | 24 | --- 25 | 26 | > 历史版本:SegmentFault **Golang gRPC实践** 系列文章: 27 | > * [Golang gRPC实践 连载一 gRPC介绍与安装](https://segmentfault.com/a/1190000007880647) 28 | > * [Golang gRPC实践 连载二 Hello gRPC](https://segmentfault.com/a/1190000007909829) 29 | > * [Golang gRPC实践 连载三 Protobuf语法](https://segmentfault.com/a/1190000007917576) 30 | > * [Golang gRPC实践 连载四 gRPC认证](https://segmentfault.com/a/1190000007933303) 31 | > * [Golang gRPC实践 连载五 拦截器 Interceptor](https://segmentfault.com/a/1190000007997759) 32 | > * [Golang gRPC实践 连载六 内置Trace](https://segmentfault.com/a/1190000008087436) 33 | > * [Golang gRPC实践 连载七 http转换](https://segmentfault.com/a/1190000008106582) 34 | -------------------------------------------------------------------------------- /src/protos/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Google Inc. 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 | package google.api; 18 | 19 | import "google/api/http.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | option java_multiple_files = true; 23 | option java_outer_classname = "AnnotationsProto"; 24 | option java_package = "com.google.api"; 25 | 26 | extend google.protobuf.MethodOptions { 27 | // See `HttpRule`. 28 | HttpRule http = 72295728; 29 | } 30 | -------------------------------------------------------------------------------- /src/ping/client_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestPing(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | }{ 11 | {name: "ping"}, 12 | } 13 | for _, tt := range tests { 14 | t.Run(tt.name, func(t *testing.T) { 15 | Ping() 16 | }) 17 | } 18 | } 19 | 20 | func TestMultiPong(t *testing.T) { 21 | tests := []struct { 22 | name string 23 | }{ 24 | {name: "multi pong"}, 25 | } 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | MultiPong() 29 | }) 30 | } 31 | } 32 | 33 | func TestMultiPing(t *testing.T) { 34 | tests := []struct { 35 | name string 36 | }{ 37 | {name: "multi ping"}, 38 | } 39 | for _, tt := range tests { 40 | t.Run(tt.name, func(t *testing.T) { 41 | MultiPing() 42 | }) 43 | } 44 | } 45 | 46 | func TestMultiPingPong(t *testing.T) { 47 | tests := []struct { 48 | name string 49 | }{ 50 | {name: "multi ping&pong"}, 51 | } 52 | for _, tt := range tests { 53 | t.Run(tt.name, func(t *testing.T) { 54 | MultiPingPong() 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /archive/docs/README.md: -------------------------------------------------------------------------------- 1 | # gRPC 2 | 3 | [gRPC](https://grpc.io/) 是一个高性能、开源、通用的RPC框架,由Google推出,基于[HTTP2](https://http2.github.io/)协议标准设计开发,默认采用[Protocol Buffers](https://developers.google.com/protocol-buffers/)数据序列化协议,支持多种开发语言。gRPC提供了一种简单的方法来精确的定义服务,并且为客户端和服务端自动生成可靠的功能库。 4 | 5 | 在gRPC客户端可以直接调用不同服务器上的远程程序,使用姿势看起来就像调用本地程序一样,很容易去构建分布式应用和服务。和很多RPC系统一样,服务端负责实现定义好的接口并处理客户端的请求,客户端根据接口描述直接调用需要的服务。客户端和服务端可以分别使用gRPC支持的不同语言实现。 6 | 7 | ![](_media/grpc_concept_diagram_00.png) 8 | 9 | 10 | ## 主要特性 11 | 12 | * 强大的IDL 13 | 14 | gRPC使用ProtoBuf来定义服务,ProtoBuf是由Google开发的一种数据序列化协议(类似于XML、JSON、hessian)。ProtoBuf能够将数据进行序列化,并广泛应用在数据存储、通信协议等方面。 15 | 16 | * 多语言支持 17 | 18 | gRPC支持多种语言,并能够基于语言自动生成客户端和服务端功能库。目前已提供了C版本grpc、Java版本grpc-java 和 Go版本grpc-go,其它语言的版本正在积极开发中,其中,grpc支持C、C++、Node.js、Python、Ruby、Objective-C、PHP和C\#等语言,grpc-java已经支持Android开发。 19 | 20 | * HTTP2 21 | 22 | gRPC基于HTTP2标准设计,所以相对于其他RPC框架,gRPC带来了更多强大功能,如双向流、头部压缩、多复用请求等。这些功能给移动设备带来重大益处,如节省带宽、降低TCP链接次数、节省CPU使用和延长电池寿命等。同时,gRPC还能够提高了云端服务和Web应用的性能。gRPC既能够在客户端应用,也能够在服务器端应用,从而以透明的方式实现客户端和服务器端的通信和简化通信系统的构建。 23 | 24 | 更多介绍请查看[官方网站](https://grpc.io/) 25 | -------------------------------------------------------------------------------- /archive/src/proto/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Google Inc. 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 | package google.api; 18 | 19 | import "google/api/http.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | option java_multiple_files = true; 23 | option java_outer_classname = "AnnotationsProto"; 24 | option java_package = "com.google.api"; 25 | 26 | extend google.protobuf.MethodOptions { 27 | // See `HttpRule`. 28 | HttpRule http = 72295728; 29 | } 30 | -------------------------------------------------------------------------------- /src/interceptor/client_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestPing(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | }{ 11 | {name: "ping"}, 12 | } 13 | for _, tt := range tests { 14 | t.Run(tt.name, func(t *testing.T) { 15 | Ping() 16 | }) 17 | } 18 | } 19 | 20 | func TestMultiPong(t *testing.T) { 21 | tests := []struct { 22 | name string 23 | }{ 24 | {name: "multi pong"}, 25 | } 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | MultiPong() 29 | }) 30 | } 31 | } 32 | 33 | func TestMultiPing(t *testing.T) { 34 | tests := []struct { 35 | name string 36 | }{ 37 | {name: "multi ping"}, 38 | } 39 | for _, tt := range tests { 40 | t.Run(tt.name, func(t *testing.T) { 41 | MultiPing() 42 | }) 43 | } 44 | } 45 | 46 | func TestMultiPingPong(t *testing.T) { 47 | tests := []struct { 48 | name string 49 | }{ 50 | {name: "multi ping&pong"}, 51 | } 52 | for _, tt := range tests { 53 | t.Run(tt.name, func(t *testing.T) { 54 | MultiPingPong() 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/auth/keys/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICyTCCAbGgAwIBAgIJAN3XznX3lP02MA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV 3 | BAMTC2dycGMuc2VydmVyMB4XDTIyMTAwMzA4NDUwOVoXDTMyMDkzMDA4NDUwOVow 4 | FjEUMBIGA1UEAxMLZ3JwYy5zZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 5 | ggEKAoIBAQDNQDxDilpAhIbRze9j3oTwN+CHoypdUFwG6GHeG4vcOgwi4iOfXig7 6 | TVUwThB9qZ91GdNO7/UIIBflbeaZCvOePm/OZOZ1TLJ/XX9hM/W/EEbAKRg8mpB5 7 | ZL35xGzkOfcLHRvCzYvWmKZRN0Vw5zTgR9GvZ5BgCfDS942hOH4J3fIRA2JPRgFe 8 | EaQlrjMOqC2KvR5JIou8nyqMakByPfXAtE0N0Tcj6NSPG/WcMmpXVWtEYLu5RKl3 9 | npanE2qrlzJbgR5vWKoiAWMnucP3b7AVAU5E1a37jWd2pcgsp+upKGVbOsbpHE2C 10 | 8WTqLoArC2UVcim4gMxkS//Hk18NhSupAgMBAAGjGjAYMBYGA1UdEQQPMA2CC2dy 11 | cGMuc2VydmVyMA0GCSqGSIb3DQEBCwUAA4IBAQBtkcf23vHS3jsb3+sVQUYnuS/6 12 | ZGUqPssNooT3tii0fIBOVwvh9c2C6qiqTuuqOzxoEu3mV/vV+yxz8torIPeYin3I 13 | rE5HaKNjtZ8btoPGB3NC3qNz0vcLroqfaYPPMTK5ml0glO0LX0HWE5o1i6WAljTJ 14 | zT8Ap53BohldLIA63+VPaEx/68JggYL5bFTNM7fMmECQePRC91IpgbdtUs3bWgEE 15 | H9S8etJsWaJR1og9RM3K/TkM3/9MUi3jXwvxLgm2YSVTVJQoLOIyJllbXMr1GVH9 16 | 0b711BB8fJl9WIG1Fi08Qu4Rwgz8WGHXwl7cUgSq+BUZFk25wDpJjxHeTAxh 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /archive/src/hello/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入编译生成的包 8 | 9 | "golang.org/x/net/context" 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/grpclog" 12 | ) 13 | 14 | const ( 15 | // Address gRPC服务地址 16 | Address = "127.0.0.1:50052" 17 | ) 18 | 19 | // 定义helloService并实现约定的接口 20 | type helloService struct{} 21 | 22 | // HelloService Hello服务 23 | var HelloService = helloService{} 24 | 25 | // SayHello 实现Hello服务接口 26 | func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { 27 | resp := new(pb.HelloResponse) 28 | resp.Message = fmt.Sprintf("Hello %s.", in.Name) 29 | 30 | return resp, nil 31 | } 32 | 33 | func main() { 34 | listen, err := net.Listen("tcp", Address) 35 | if err != nil { 36 | grpclog.Fatalf("Failed to listen: %v", err) 37 | } 38 | 39 | // 实例化grpc Server 40 | s := grpc.NewServer() 41 | 42 | // 注册HelloService 43 | pb.RegisterHelloServer(s, HelloService) 44 | 45 | grpclog.Println("Listen on " + Address) 46 | s.Serve(listen) 47 | } 48 | -------------------------------------------------------------------------------- /archive/src/hello_http/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | 6 | pb "github.com/jergoo/go-grpc-example/proto/hello_http" 7 | "golang.org/x/net/context" 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/grpclog" 10 | ) 11 | 12 | const ( 13 | // Address gRPC服务地址 14 | Address = "127.0.0.1:50052" 15 | ) 16 | 17 | // 定义helloHTTPService并实现约定的接口 18 | type helloHTTPService struct{} 19 | 20 | // HelloHTTPService 实现服务端接口 21 | var HelloHTTPService = helloHTTPService{} 22 | 23 | // SayHello ... 24 | func (h helloHTTPService) SayHello(ctx context.Context, in *pb.HelloHTTPRequest) (*pb.HelloHTTPResponse, error) { 25 | resp := new(pb.HelloHTTPResponse) 26 | resp.Message = "Hello " + in.Name + "." 27 | 28 | return resp, nil 29 | } 30 | 31 | func main() { 32 | listen, err := net.Listen("tcp", Address) 33 | if err != nil { 34 | grpclog.Fatalf("failed to listen: %v", err) 35 | } 36 | 37 | // 实例化grpc Server 38 | s := grpc.NewServer() 39 | 40 | // 注册HelloHTTPService 41 | pb.RegisterHelloHTTPServer(s, HelloHTTPService) 42 | 43 | grpclog.Println("Listen on " + Address) 44 | 45 | s.Serve(listen) 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jergoo Mars 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /archive/src/hello_http_2/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | pb "github.com/jergoo/go-grpc-example/proto/hello_http" // 引入proto包 5 | 6 | "golang.org/x/net/context" 7 | "google.golang.org/grpc" 8 | "google.golang.org/grpc/credentials" // 引入grpc认证包 9 | "google.golang.org/grpc/grpclog" 10 | ) 11 | 12 | const ( 13 | // Address gRPC服务地址 14 | Address = "127.0.0.1:50052" 15 | ) 16 | 17 | func main() { 18 | // TLS连接 19 | creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "server name") 20 | if err != nil { 21 | grpclog.Fatalf("Failed to create TLS credentials %v", err) 22 | } 23 | conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds)) 24 | 25 | if err != nil { 26 | grpclog.Fatalln(err) 27 | } 28 | 29 | defer conn.Close() 30 | 31 | // 初始化客户端 32 | c := pb.NewHelloHTTPClient(conn) 33 | 34 | // 调用方法 35 | reqBody := new(pb.HelloHTTPRequest) 36 | reqBody.Name = "gRPC" 37 | r, err := c.SayHello(context.Background(), reqBody) 38 | 39 | if err != nil { 40 | grpclog.Fatalln(err) 41 | } 42 | 43 | grpclog.Println(r.Message) 44 | } 45 | 46 | // OR: curl -X POST -k https://localhost:50052/example/echo -d '{"name": "gRPC-HTTP is working!"}' 47 | -------------------------------------------------------------------------------- /src/protos/example/example.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; // 指定proto版本 2 | package example; // 指定包名 3 | option go_package="protos/example"; // 指定go包路径 4 | 5 | // ExampleService 示例 6 | service ExampleService { 7 | // Single 单次请求响应模式 8 | rpc Single(Request) returns (Response); 9 | // ServerStream 服务端流模式 10 | rpc ServerStream(Request) returns (stream Response); 11 | // ClientStream 客户端流模式 12 | rpc ClientStream(stream Request) returns (Response); 13 | // BiStream 双向流模式 14 | rpc BiStream(stream Request) returns (stream Response); 15 | } 16 | 17 | // Request 请求结构 18 | message Request { 19 | optional string value = 1; 20 | } 21 | 22 | // Response 响应结构 23 | message Response { 24 | string valuee = 1; 25 | } 26 | 27 | // Msg message 数据类型示例 28 | message Msg { 29 | int32 i32 = 1; 30 | int64 i64 = 2; 31 | float f32 = 3; 32 | double f64 = 4; 33 | string str = 5; 34 | bool boolean = 6; 35 | bytes byteArr = 7; 36 | map dict = 8; 37 | Status status = 9; 38 | EmbMsg embMsg = 10; 39 | repeated int64 intArr = 11; 40 | } 41 | 42 | message EmbMsg { 43 | string value = 1; 44 | } 45 | 46 | // Status 枚举 47 | enum Status { 48 | OK = 0; 49 | FAIL = 1; 50 | } 51 | -------------------------------------------------------------------------------- /archive/src/keys/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDLTCCArOgAwIBAgIJAK6v8AGZwTeWMAkGByqGSM49BAEwgYUxCzAJBgNVBAYT 3 | AkNOMRAwDgYDVQQIEwdCZWlqaW5nMRAwDgYDVQQHEwdCZWlqaW5nMRIwEAYDVQQK 4 | EwlYWCBDby5MdGQxDDAKBgNVBAsTA2RldjEUMBIGA1UEAxMLc2VydmVyIG5hbWUx 5 | GjAYBgkqhkiG9w0BCQEWC3h4eEB4eHguY29tMB4XDTE2MDcyODA0MzkzNloXDTI2 6 | MDcyNjA0MzkzNlowgYUxCzAJBgNVBAYTAkNOMRAwDgYDVQQIEwdCZWlqaW5nMRAw 7 | DgYDVQQHEwdCZWlqaW5nMRIwEAYDVQQKEwlYWCBDby5MdGQxDDAKBgNVBAsTA2Rl 8 | djEUMBIGA1UEAxMLc2VydmVyIG5hbWUxGjAYBgkqhkiG9w0BCQEWC3h4eEB4eHgu 9 | Y29tMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEPRQ/QvvK5wnz1uIcvykRTnmLl7x+ 10 | UwXOLbarylALJqZ0FWLsWKa+ZNqfsNbETdJfAfrjJhV85gdcAkohsDqwhBIdsBWq 11 | O83nktChXO42acui5HIenkugyuzWsq3RDcC8o4HtMIHqMB0GA1UdDgQWBBQ9XXa5 12 | nGN/C43KGh22VsyC3vs9KTCBugYDVR0jBIGyMIGvgBQ9XXa5nGN/C43KGh22VsyC 13 | 3vs9KaGBi6SBiDCBhTELMAkGA1UEBhMCQ04xEDAOBgNVBAgTB0JlaWppbmcxEDAO 14 | BgNVBAcTB0JlaWppbmcxEjAQBgNVBAoTCVhYIENvLkx0ZDEMMAoGA1UECxMDZGV2 15 | MRQwEgYDVQQDEwtzZXJ2ZXIgbmFtZTEaMBgGCSqGSIb3DQEJARYLeHh4QHh4eC5j 16 | b22CCQCur/ABmcE3ljAMBgNVHRMEBTADAQH/MAkGByqGSM49BAEDaQAwZgIxAJBX 17 | 9oK1XpIqp2oWddPyrwoYVOv6SUiTgYxrmBA6Uw9+BauPQqSOE7ZSG53lMfYBewIx 18 | AKSvcxs6tQ5/qPN6Ukn34tC9HafmGgdUN42LB8QTkR+97EueZ7wdWRjP+MIzfOXB 19 | Cg== 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /docs/ayu-highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | Based off of the Ayu theme 3 | Original by Dempfi (https://github.com/dempfi/ayu) 4 | */ 5 | 6 | .hljs { 7 | display: block; 8 | overflow-x: auto; 9 | background: #191f26; 10 | color: #e6e1cf; 11 | } 12 | 13 | .hljs-comment, 14 | .hljs-quote { 15 | color: #5c6773; 16 | font-style: italic; 17 | } 18 | 19 | .hljs-variable, 20 | .hljs-template-variable, 21 | .hljs-attribute, 22 | .hljs-attr, 23 | .hljs-regexp, 24 | .hljs-link, 25 | .hljs-selector-id, 26 | .hljs-selector-class { 27 | color: #ff7733; 28 | } 29 | 30 | .hljs-number, 31 | .hljs-meta, 32 | .hljs-builtin-name, 33 | .hljs-literal, 34 | .hljs-type, 35 | .hljs-params { 36 | color: #ffee99; 37 | } 38 | 39 | .hljs-string, 40 | .hljs-bullet { 41 | color: #b8cc52; 42 | } 43 | 44 | .hljs-title, 45 | .hljs-built_in, 46 | .hljs-section { 47 | color: #ffb454; 48 | } 49 | 50 | .hljs-keyword, 51 | .hljs-selector-tag, 52 | .hljs-symbol { 53 | color: #ff7733; 54 | } 55 | 56 | .hljs-name { 57 | color: #36a3d9; 58 | } 59 | 60 | .hljs-tag { 61 | color: #00568d; 62 | } 63 | 64 | .hljs-emphasis { 65 | font-style: italic; 66 | } 67 | 68 | .hljs-strong { 69 | font-weight: bold; 70 | } 71 | 72 | .hljs-addition { 73 | color: #91b362; 74 | } 75 | 76 | .hljs-deletion { 77 | color: #d96c75; 78 | } 79 | -------------------------------------------------------------------------------- /archive/src/hello_tls/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | pb "github.com/jergoo/go-grpc-example/proto/hello" 8 | 9 | "golang.org/x/net/context" 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/credentials" // 引入grpc认证包 12 | "google.golang.org/grpc/grpclog" 13 | ) 14 | 15 | const ( 16 | // Address gRPC服务地址 17 | Address = "127.0.0.1:50052" 18 | ) 19 | 20 | // 定义helloService并实现约定的接口 21 | type helloService struct{} 22 | 23 | // HelloService Hello服务 24 | var HelloService = helloService{} 25 | 26 | // SayHello 实现Hello服务接口 27 | func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { 28 | resp := new(pb.HelloResponse) 29 | resp.Message = fmt.Sprintf("Hello %s.", in.Name) 30 | 31 | return resp, nil 32 | } 33 | 34 | func main() { 35 | listen, err := net.Listen("tcp", Address) 36 | if err != nil { 37 | grpclog.Fatalf("Failed to listen: %v", err) 38 | } 39 | 40 | // TLS认证 41 | creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem", "../../keys/server.key") 42 | if err != nil { 43 | grpclog.Fatalf("Failed to generate credentials %v", err) 44 | } 45 | 46 | // 实例化grpc Server, 并开启TLS认证 47 | s := grpc.NewServer(grpc.Creds(creds)) 48 | 49 | // 注册HelloService 50 | pb.RegisterHelloServer(s, HelloService) 51 | 52 | grpclog.Println("Listen on " + Address + " with TLS") 53 | 54 | s.Serve(listen) 55 | } 56 | -------------------------------------------------------------------------------- /archive/src/hello_trace/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | 8 | pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入编译生成的包 9 | 10 | "golang.org/x/net/context" 11 | "golang.org/x/net/trace" 12 | "google.golang.org/grpc" 13 | "google.golang.org/grpc/grpclog" 14 | ) 15 | 16 | const ( 17 | // Address gRPC服务地址 18 | Address = "127.0.0.1:50052" 19 | ) 20 | 21 | // 定义helloService并实现约定的接口 22 | type helloService struct{} 23 | 24 | // HelloService Hello服务 25 | var HelloService = helloService{} 26 | 27 | // SayHello 实现Hello服务接口 28 | func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { 29 | resp := new(pb.HelloResponse) 30 | resp.Message = fmt.Sprintf("Hello %s.", in.Name) 31 | 32 | return resp, nil 33 | } 34 | 35 | func main() { 36 | listen, err := net.Listen("tcp", Address) 37 | if err != nil { 38 | grpclog.Fatalf("failed to listen: %v", err) 39 | } 40 | 41 | // 实例化grpc Server 42 | s := grpc.NewServer() 43 | 44 | // 注册HelloService 45 | pb.RegisterHelloServer(s, HelloService) 46 | 47 | // 开启trace 48 | go startTrace() 49 | 50 | grpclog.Println("Listen on " + Address) 51 | s.Serve(listen) 52 | } 53 | 54 | func startTrace() { 55 | trace.AuthRequest = func(req *http.Request) (any, sensitive bool) { 56 | return true, true 57 | } 58 | go http.ListenAndServe(":50051", nil) 59 | grpclog.Println("Trace listen on 50051") 60 | } 61 | -------------------------------------------------------------------------------- /src/auth/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "google.golang.org/grpc" 8 | "google.golang.org/grpc/credentials" 9 | 10 | pb "github.com/jergoo/go-grpc-tutorial/protos/ping" // 引入编译生成的包 11 | ) 12 | 13 | // Ping 单次请求-响应模式 14 | func Ping() { 15 | // 读取服务端证书,并制定对应服务名 16 | creds, err := credentials.NewClientTLSFromFile("keys/server.crt", "grpc.server") 17 | if err != nil { 18 | log.Fatalf("load crt fail: %v", err) 19 | } 20 | 21 | // 连接配置 22 | opts := []grpc.DialOption{ 23 | // TLS 认证 24 | grpc.WithTransportCredentials(creds), 25 | // Token 认证 26 | grpc.WithPerRPCCredentials(CustomAuth{Token: "1234567890"}), 27 | } 28 | conn, err := grpc.Dial("localhost:1234", opts...) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | defer conn.Close() 33 | 34 | // 实例化客户端并调用 35 | client := pb.NewPingPongClient(conn) 36 | res, err := client.Ping(context.Background(), &pb.PingRequest{Value: "ping"}) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | log.Println(res.Value) 41 | } 42 | 43 | // CustomAuth 自定义认证类型 44 | type CustomAuth struct { 45 | Token string 46 | } 47 | 48 | // GetRequestMetadata 生成认证信息 49 | func (a CustomAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 50 | return map[string]string{ 51 | "authorization": a.Token, 52 | }, nil 53 | } 54 | 55 | // RequireTransportSecurity 是否开启 TLS 56 | func (a CustomAuth) RequireTransportSecurity() bool { 57 | return true 58 | } 59 | -------------------------------------------------------------------------------- /docs/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | * An increased contrast highlighting scheme loosely based on the 3 | * "Base16 Atelier Dune Light" theme by Bram de Haan 4 | * (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) 5 | * Original Base16 color scheme by Chris Kempson 6 | * (https://github.com/chriskempson/base16) 7 | */ 8 | 9 | /* Comment */ 10 | .hljs-comment, 11 | .hljs-quote { 12 | color: #575757; 13 | } 14 | 15 | /* Red */ 16 | .hljs-variable, 17 | .hljs-template-variable, 18 | .hljs-attribute, 19 | .hljs-tag, 20 | .hljs-name, 21 | .hljs-regexp, 22 | .hljs-link, 23 | .hljs-name, 24 | .hljs-selector-id, 25 | .hljs-selector-class { 26 | color: #d70025; 27 | } 28 | 29 | /* Orange */ 30 | .hljs-number, 31 | .hljs-meta, 32 | .hljs-built_in, 33 | .hljs-builtin-name, 34 | .hljs-literal, 35 | .hljs-type, 36 | .hljs-params { 37 | color: #b21e00; 38 | } 39 | 40 | /* Green */ 41 | .hljs-string, 42 | .hljs-symbol, 43 | .hljs-bullet { 44 | color: #008200; 45 | } 46 | 47 | /* Blue */ 48 | .hljs-title, 49 | .hljs-section { 50 | color: #0030f2; 51 | } 52 | 53 | /* Purple */ 54 | .hljs-keyword, 55 | .hljs-selector-tag { 56 | color: #9d00ec; 57 | } 58 | 59 | .hljs { 60 | display: block; 61 | overflow-x: auto; 62 | background: #f6f7f6; 63 | color: #000; 64 | } 65 | 66 | .hljs-emphasis { 67 | font-style: italic; 68 | } 69 | 70 | .hljs-strong { 71 | font-weight: bold; 72 | } 73 | 74 | .hljs-addition { 75 | color: #22863a; 76 | background-color: #f0fff4; 77 | } 78 | 79 | .hljs-deletion { 80 | color: #b31d28; 81 | background-color: #ffeef0; 82 | } 83 | -------------------------------------------------------------------------------- /theme/pagetoc.css: -------------------------------------------------------------------------------- 1 | @media only screen and (max-width:1439px) { 2 | .sidetoc { 3 | display: none; 4 | } 5 | } 6 | 7 | @media only screen and (min-width:1440px) { 8 | main { 9 | position: relative; 10 | } 11 | .sidetoc { 12 | margin-left: auto; 13 | margin-right: auto; 14 | left: calc(100% + (var(--content-max-width))/4 - 140px); 15 | position: absolute; 16 | } 17 | .pagetoc { 18 | position: fixed; 19 | width: 180px; 20 | height: calc(100vh - var(--menu-bar-height) - 0.67em * 4); 21 | overflow: auto; 22 | font-size: 1.2rem; 23 | } 24 | .pagetoc a { 25 | border-left: 1px solid var(--sidebar-bg); 26 | color: var(--fg) !important; 27 | display: block; 28 | padding-bottom: 5px; 29 | padding-top: 5px; 30 | padding-left: 10px; 31 | text-align: left; 32 | text-decoration: none; 33 | } 34 | .pagetoc a:hover, 35 | .pagetoc a.active { 36 | background: var(--sidebar-bg); 37 | color: var(--sidebar-fg) !important; 38 | } 39 | .pagetoc .active { 40 | background: var(--sidebar-bg); 41 | color: var(--sidebar-fg); 42 | } 43 | .pagetoc a[class^='pagetoc-H']:only-child { 44 | display: none !important; 45 | } 46 | .pagetoc .pagetoc-H2 { 47 | padding-left: 20px; 48 | } 49 | .pagetoc .pagetoc-H3 { 50 | padding-left: 40px; 51 | } 52 | .pagetoc .pagetoc-H4 { 53 | padding-left: 60px; 54 | } 55 | .pagetoc .pagetoc-H5 { 56 | display: none; 57 | } 58 | .pagetoc .pagetoc-H6 { 59 | display: none; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /docs/theme/pagetoc.css: -------------------------------------------------------------------------------- 1 | @media only screen and (max-width:1439px) { 2 | .sidetoc { 3 | display: none; 4 | } 5 | } 6 | 7 | @media only screen and (min-width:1440px) { 8 | main { 9 | position: relative; 10 | } 11 | .sidetoc { 12 | margin-left: auto; 13 | margin-right: auto; 14 | left: calc(100% + (var(--content-max-width))/4 - 140px); 15 | position: absolute; 16 | } 17 | .pagetoc { 18 | position: fixed; 19 | width: 180px; 20 | height: calc(100vh - var(--menu-bar-height) - 0.67em * 4); 21 | overflow: auto; 22 | font-size: 1.2rem; 23 | } 24 | .pagetoc a { 25 | border-left: 1px solid var(--sidebar-bg); 26 | color: var(--fg) !important; 27 | display: block; 28 | padding-bottom: 5px; 29 | padding-top: 5px; 30 | padding-left: 10px; 31 | text-align: left; 32 | text-decoration: none; 33 | } 34 | .pagetoc a:hover, 35 | .pagetoc a.active { 36 | background: var(--sidebar-bg); 37 | color: var(--sidebar-fg) !important; 38 | } 39 | .pagetoc .active { 40 | background: var(--sidebar-bg); 41 | color: var(--sidebar-fg); 42 | } 43 | .pagetoc a[class^='pagetoc-H']:only-child { 44 | display: none !important; 45 | } 46 | .pagetoc .pagetoc-H2 { 47 | padding-left: 20px; 48 | } 49 | .pagetoc .pagetoc-H3 { 50 | padding-left: 40px; 51 | } 52 | .pagetoc .pagetoc-H4 { 53 | padding-left: 60px; 54 | } 55 | .pagetoc .pagetoc-H5 { 56 | display: none; 57 | } 58 | .pagetoc .pagetoc-H6 { 59 | display: none; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/auth/keys/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDNQDxDilpAhIbR 3 | ze9j3oTwN+CHoypdUFwG6GHeG4vcOgwi4iOfXig7TVUwThB9qZ91GdNO7/UIIBfl 4 | beaZCvOePm/OZOZ1TLJ/XX9hM/W/EEbAKRg8mpB5ZL35xGzkOfcLHRvCzYvWmKZR 5 | N0Vw5zTgR9GvZ5BgCfDS942hOH4J3fIRA2JPRgFeEaQlrjMOqC2KvR5JIou8nyqM 6 | akByPfXAtE0N0Tcj6NSPG/WcMmpXVWtEYLu5RKl3npanE2qrlzJbgR5vWKoiAWMn 7 | ucP3b7AVAU5E1a37jWd2pcgsp+upKGVbOsbpHE2C8WTqLoArC2UVcim4gMxkS//H 8 | k18NhSupAgMBAAECggEABIm8C14+uphzhqX/+fxMgicqHrWb1S5WqoxuxYNkZz0d 9 | UZQgZ4CkcRM3o/bF5JFghi1I/8DMcR6MiLUSWSXl1wTYYf7XHZbRh4FgtJx6fXv1 10 | pIAEGirssNJqZOBT7APgE23UVE1JHziGzNrk/ScPgznzR+aJSaZOnct9ZSgjmUI1 11 | 2gO4XuQxCrcswrD95FjivRLOYY3iDd4c3Ls1TaUAfyRl177D9vfFAH/30iT9o0AA 12 | wbrLy8iFty35vv8i6bfNXMonDpTRMJ/3dSXnczpedcGJu2KhEjAq95SUL8EfL/86 13 | dFQhvkpYCYIwiSom3wXQwduiarzI2IkK4i66OvHh0QKBgQDlw1bBL8GreTNTDA4j 14 | xgsrRfkVTGzoxNvTEt/EEzQRgXV9yya0O/G41B2qhR32JpH3m4Q7znr4j5pSpKDA 15 | NnGy7+BDXvPf/P+l3cpYplhuVJ4ClbLW/AT0vgH098ORaaY3YV0WbB9wt7La17Fi 16 | vDZkbIq6Ozz7fxqMHnycRvcElQKBgQDksFV2FVjiuCG41MQsum4s2qpWTnZ05mtn 17 | 7tb6uAc3b1G3Ylwemkad5CngAY8akw62qofHuI5q0c90EdbbEY2LVGSjGwQwiYGi 18 | 8bYSP/xH6brW6px8wBCi0ilBNyJgDqxTJ9/L+Yb2nUTkSjIy9M5T7kR+cNy2VnXt 19 | WU0adq3RxQKBgGrpNRIZhbWjZOVsw5uTyJivK5Lkh/zbtSoHK0YSZXUL1z7ca7sL 20 | 9ZM6aD9zR5jFI8CihapKjt/f+kAgSIiIDOnlQmY25aXBFLf6dvA/HwDfsNKlE52D 21 | kU7Mpx50t3SPQ3+bd3Q6hXquj7PTD8BQscKS2w6DCAJCh3jRqswRHVntAoGAYqEu 22 | apwwVq/FU3roFMZfCBdSyaBTdH2AKzYWH9HIwv7OxoG0azpsZGJoMQVbG8H64YtQ 23 | vjF5stZEW7Hp8VdzrXi7YqEfQHO9pnIK7XoNK1LB0zkiPqdzRVXQlyDZQYZyifaz 24 | jbxTmf8gHDlnxxRP/GDRx5qThkxp1fDi0RwIQWUCgYEAs1uHhxpF+iWy0oOnglFX 25 | ZqZJjbP5BshM9Gj9tSmIO9eJbyaimVs3/Tp2iETmlgqckItYMzOeI8FLcls5NLi0 26 | BWPBum0mNwwvhWK60roNzrul04zlvnLTII89i5p07+zzR4AoySLZbH5+uzghEgmZ 27 | LR6qiogzv/GJ8FC1wqj2DYg= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /src/metadata/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net" 7 | 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/metadata" 10 | 11 | pb "github.com/jergoo/go-grpc-tutorial/protos/ping" // 引入编译生成的包 12 | ) 13 | 14 | // PingPongServer 实现 pb.PingPongServer 接口 15 | type PingPongServer struct { 16 | pb.UnimplementedPingPongServer // 兼容性需要,避免未实现server接口全部方法 17 | } 18 | 19 | // Ping 单次请求-响应模式 20 | func (s *PingPongServer) Ping(ctx context.Context, req *pb.PingRequest) (*pb.PongResponse, error) { 21 | // 读取请求metadata 22 | md, ok := metadata.FromIncomingContext(ctx) 23 | if ok { 24 | log.Printf("Got md: %v", md) 25 | } 26 | 27 | // 设置响应metadata 28 | grpc.SetHeader(ctx, metadata.New(map[string]string{"rkey": "rval"})) 29 | 30 | return &pb.PongResponse{Value: "pong"}, nil 31 | } 32 | 33 | // MultiPong 服务端流模式 34 | func (s *PingPongServer) MultiPong(req *pb.PingRequest, stream pb.PingPong_MultiPongServer) error { 35 | // 读取请求metadata 36 | md, ok := metadata.FromIncomingContext(stream.Context()) 37 | if ok { 38 | log.Printf("Got md: %v", md) 39 | } 40 | 41 | // 设置响应metadata 42 | err := stream.SendHeader(metadata.New(map[string]string{"rkey": "rval"})) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | for i := 0; i < 3; i++ { 48 | data := &pb.PongResponse{Value: "pong"} 49 | // 发送消息 50 | err := stream.Send(data) 51 | if err != nil { 52 | return err 53 | } 54 | if err != nil { 55 | log.Println(err) 56 | } 57 | } 58 | return nil 59 | } 60 | 61 | // 启动server 62 | func main() { 63 | srv := grpc.NewServer() 64 | // 注册 PingPongServer 65 | pb.RegisterPingPongServer(srv, &PingPongServer{}) 66 | lis, err := net.Listen("tcp", ":1234") 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | log.Println("listen on 1234") 71 | srv.Serve(lis) 72 | } 73 | -------------------------------------------------------------------------------- /archive/src/hello_token/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入proto包 5 | 6 | "golang.org/x/net/context" 7 | "google.golang.org/grpc" 8 | "google.golang.org/grpc/credentials" // 引入grpc认证包 9 | "google.golang.org/grpc/grpclog" 10 | ) 11 | 12 | const ( 13 | // Address gRPC服务地址 14 | Address = "127.0.0.1:50052" 15 | 16 | // OpenTLS 是否开启TLS认证 17 | OpenTLS = true 18 | ) 19 | 20 | // customCredential 自定义认证 21 | type customCredential struct{} 22 | 23 | // GetRequestMetadata 实现自定义认证接口 24 | func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 25 | return map[string]string{ 26 | "appid": "101010", 27 | "appkey": "i am key", 28 | }, nil 29 | } 30 | 31 | // RequireTransportSecurity 自定义认证是否开启TLS 32 | func (c customCredential) RequireTransportSecurity() bool { 33 | return OpenTLS 34 | } 35 | 36 | func main() { 37 | var err error 38 | var opts []grpc.DialOption 39 | 40 | if OpenTLS { 41 | // TLS连接 42 | creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "server name") 43 | if err != nil { 44 | grpclog.Fatalf("Failed to create TLS credentials %v", err) 45 | } 46 | opts = append(opts, grpc.WithTransportCredentials(creds)) 47 | } else { 48 | opts = append(opts, grpc.WithInsecure()) 49 | } 50 | 51 | // 使用自定义认证 52 | opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential))) 53 | 54 | conn, err := grpc.Dial(Address, opts...) 55 | 56 | if err != nil { 57 | grpclog.Fatalln(err) 58 | } 59 | 60 | defer conn.Close() 61 | 62 | // 初始化客户端 63 | c := pb.NewHelloClient(conn) 64 | 65 | // 调用方法 66 | req := &pb.HelloRequest{Name: "gRPC"} 67 | res, err := c.SayHello(context.Background(), req) 68 | if err != nil { 69 | grpclog.Fatalln(err) 70 | } 71 | 72 | grpclog.Println(res.Message) 73 | } 74 | -------------------------------------------------------------------------------- /theme/pagetoc.js: -------------------------------------------------------------------------------- 1 | // Un-active everything when you click it 2 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el) { 3 | el.addEventHandler("click", function() { 4 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el) { 5 | el.classList.remove("active"); 6 | }); 7 | el.classList.add("active"); 8 | }); 9 | }); 10 | 11 | var updateFunction = function() { 12 | 13 | var id; 14 | var elements = document.getElementsByClassName("header"); 15 | Array.prototype.forEach.call(elements, function(el) { 16 | if (window.pageYOffset >= el.offsetTop) { 17 | id = el; 18 | } 19 | }); 20 | 21 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el) { 22 | el.classList.remove("active"); 23 | }); 24 | if (!id) return; 25 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el) { 26 | if (id.href.localeCompare(el.href) == 0) { 27 | el.classList.add("active"); 28 | } 29 | }); 30 | }; 31 | 32 | // Populate sidebar on load 33 | window.addEventListener('load', function() { 34 | var pagetoc = document.getElementsByClassName("pagetoc")[0]; 35 | var elements = document.getElementsByClassName("header"); 36 | Array.prototype.forEach.call(elements, function (el) { 37 | var link = document.createElement("a"); 38 | link.appendChild(document.createTextNode(el.text)); 39 | link.href = el.href; 40 | link.classList.add("pagetoc-" + el.parentElement.tagName); 41 | pagetoc.appendChild(link); 42 | }); 43 | updateFunction.call(); 44 | }); 45 | 46 | 47 | 48 | // Handle active elements on scroll 49 | window.addEventListener("scroll", updateFunction); 50 | -------------------------------------------------------------------------------- /docs/theme/pagetoc.js: -------------------------------------------------------------------------------- 1 | // Un-active everything when you click it 2 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el) { 3 | el.addEventHandler("click", function() { 4 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el) { 5 | el.classList.remove("active"); 6 | }); 7 | el.classList.add("active"); 8 | }); 9 | }); 10 | 11 | var updateFunction = function() { 12 | 13 | var id; 14 | var elements = document.getElementsByClassName("header"); 15 | Array.prototype.forEach.call(elements, function(el) { 16 | if (window.pageYOffset >= el.offsetTop) { 17 | id = el; 18 | } 19 | }); 20 | 21 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el) { 22 | el.classList.remove("active"); 23 | }); 24 | if (!id) return; 25 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el) { 26 | if (id.href.localeCompare(el.href) == 0) { 27 | el.classList.add("active"); 28 | } 29 | }); 30 | }; 31 | 32 | // Populate sidebar on load 33 | window.addEventListener('load', function() { 34 | var pagetoc = document.getElementsByClassName("pagetoc")[0]; 35 | var elements = document.getElementsByClassName("header"); 36 | Array.prototype.forEach.call(elements, function (el) { 37 | var link = document.createElement("a"); 38 | link.appendChild(document.createTextNode(el.text)); 39 | link.href = el.href; 40 | link.classList.add("pagetoc-" + el.parentElement.tagName); 41 | pagetoc.appendChild(link); 42 | }); 43 | updateFunction.call(); 44 | }); 45 | 46 | 47 | 48 | // Handle active elements on scroll 49 | window.addEventListener("scroll", updateFunction); 50 | -------------------------------------------------------------------------------- /src/auth/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "log" 7 | "net" 8 | 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/codes" 11 | "google.golang.org/grpc/credentials" 12 | "google.golang.org/grpc/metadata" 13 | 14 | pb "github.com/jergoo/go-grpc-tutorial/protos/ping" // 引入编译生成的包 15 | ) 16 | 17 | // PingPongServer 实现 pb.PingPongServer 接口 18 | type PingPongServer struct { 19 | pb.UnimplementedPingPongServer // 兼容性需要,避免未实现server接口全部方法 20 | } 21 | 22 | // Ping 单次请求-响应模式 23 | func (s *PingPongServer) Ping(ctx context.Context, req *pb.PingRequest) (*pb.PongResponse, error) { 24 | return &pb.PongResponse{Value: "pong"}, nil 25 | } 26 | 27 | // 服务端拦截器 - token 认证 28 | func authInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { 29 | md, ok := metadata.FromIncomingContext(ctx) 30 | if !ok { 31 | return nil, errors.New("authorization missing") 32 | } 33 | 34 | var token string 35 | if auth, ok := md["authorization"]; ok { 36 | token = auth[0] 37 | } 38 | if token != "1234567890" { 39 | return nil, grpc.Errorf(codes.Unauthenticated, "token invalid") 40 | } 41 | 42 | // 处理请求 43 | return handler(ctx, req) 44 | } 45 | 46 | // 启动server 47 | func main() { 48 | // 加载证书文件 49 | creds, err := credentials.NewServerTLSFromFile("keys/server.crt", "keys/server.key") 50 | if err != nil { 51 | log.Fatalf("load crt fail:%v", err) 52 | } 53 | opts := []grpc.ServerOption{ 54 | grpc.Creds(creds), // TLS 55 | grpc.UnaryInterceptor(authInterceptor), // Token 56 | } 57 | 58 | srv := grpc.NewServer(opts...) 59 | // 注册 PingPongServer 60 | pb.RegisterPingPongServer(srv, &PingPongServer{}) 61 | lis, err := net.Listen("tcp", ":1234") 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | log.Println("listen on 1234") 66 | srv.Serve(lis) 67 | } 68 | -------------------------------------------------------------------------------- /docs/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 2 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 3 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 4 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 5 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 6 | golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= 7 | golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 8 | golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= 9 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 10 | golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= 11 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 12 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 13 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 14 | google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 h1:a2S6M0+660BgMNl++4JPlcAO/CjkqYItDEZwkoDQK7c= 15 | google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= 16 | google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= 17 | google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= 18 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 19 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 20 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 21 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 22 | -------------------------------------------------------------------------------- /archive/src/hello_token/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | pb "github.com/jergoo/go-grpc-example/proto/hello" 8 | 9 | "golang.org/x/net/context" 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/codes" 12 | "google.golang.org/grpc/credentials" // 引入grpc认证包 13 | "google.golang.org/grpc/grpclog" 14 | "google.golang.org/grpc/metadata" // 引入grpc meta包 15 | ) 16 | 17 | const ( 18 | // Address gRPC服务地址 19 | Address = "127.0.0.1:50052" 20 | ) 21 | 22 | // 定义helloService并实现约定的接口 23 | type helloService struct{} 24 | 25 | // HelloService ... 26 | var HelloService = helloService{} 27 | 28 | // SayHello 实现Hello服务接口 29 | func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { 30 | // 解析metada中的信息并验证 31 | md, ok := metadata.FromIncomingContext(ctx) 32 | if !ok { 33 | return nil, grpc.Errorf(codes.Unauthenticated, "无Token认证信息") 34 | } 35 | 36 | var ( 37 | appid string 38 | appkey string 39 | ) 40 | 41 | if val, ok := md["appid"]; ok { 42 | appid = val[0] 43 | } 44 | 45 | if val, ok := md["appkey"]; ok { 46 | appkey = val[0] 47 | } 48 | 49 | if appid != "101010" || appkey != "i am key" { 50 | return nil, grpc.Errorf(codes.Unauthenticated, "Token认证信息无效: appid=%s, appkey=%s", appid, appkey) 51 | } 52 | 53 | resp := new(pb.HelloResponse) 54 | resp.Message = fmt.Sprintf("Hello %s.\nToken info: appid=%s,appkey=%s", in.Name, appid, appkey) 55 | 56 | return resp, nil 57 | } 58 | 59 | func main() { 60 | listen, err := net.Listen("tcp", Address) 61 | if err != nil { 62 | grpclog.Fatalf("failed to listen: %v", err) 63 | } 64 | 65 | // TLS认证 66 | creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem", "../../keys/server.key") 67 | if err != nil { 68 | grpclog.Fatalf("Failed to generate credentials %v", err) 69 | } 70 | 71 | // 实例化grpc Server, 并开启TLS认证 72 | s := grpc.NewServer(grpc.Creds(creds)) 73 | 74 | // 注册HelloService 75 | pb.RegisterHelloServer(s, HelloService) 76 | 77 | grpclog.Println("Listen on " + Address + " with TLS + Token") 78 | 79 | s.Serve(listen) 80 | } 81 | -------------------------------------------------------------------------------- /src/metadata/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "log" 7 | "strconv" 8 | "time" 9 | 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/metadata" 12 | 13 | pb "github.com/jergoo/go-grpc-tutorial/protos/ping" // 引入编译生成的包 14 | ) 15 | 16 | // Ping 单次请求-响应模式 17 | func Ping() { 18 | conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure()) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | defer conn.Close() 23 | 24 | // 初始化metadata 25 | md := metadata.New(map[string]string{"ts": strconv.FormatInt(time.Now().UnixMicro(), 10)}) 26 | md.Set("key", "v1", "v2") 27 | 28 | // md := metadata.Pairs("key", "v1", "key", "v2") 29 | 30 | // md := make(metadata.MD) 31 | // md.Set("key", "v1", "v2") 32 | 33 | // context包装metadata 34 | ctx := metadata.NewOutgoingContext(context.Background(), md) 35 | ctx = metadata.AppendToOutgoingContext(ctx, "k3", "v3") 36 | 37 | // 实例化客户端并调用 38 | client := pb.NewPingPongClient(conn) 39 | 40 | var reponseMD metadata.MD 41 | res, err := client.Ping(ctx, &pb.PingRequest{Value: "ping"}, grpc.Header(&reponseMD)) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | log.Printf("Got response md: %v", reponseMD) 47 | log.Println(res.Value) 48 | } 49 | 50 | // MultiPong 服务端流模式 51 | func MultiPong() { 52 | conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure()) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | defer conn.Close() 57 | 58 | // 初始化metadata 59 | md := metadata.New(map[string]string{"ts": strconv.FormatInt(time.Now().UnixMicro(), 10)}) 60 | ctx := metadata.NewOutgoingContext(context.Background(), md) 61 | 62 | // 实例化客户端并调用 63 | client := pb.NewPingPongClient(conn) 64 | // 获得对 stream 对象的引用 65 | stream, err := client.MultiPong(ctx, &pb.PingRequest{Value: "ping"}) 66 | if err != nil { 67 | log.Fatal(err) 68 | } 69 | 70 | // 读取响应metadata 71 | mdResponse, err := stream.Header() 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | log.Printf("get response md: %v", mdResponse) 76 | 77 | // 循环接收数据流 78 | for { 79 | msg, err := stream.Recv() 80 | if err != nil { 81 | if err == io.EOF { 82 | break 83 | } 84 | log.Fatal(err) 85 | } 86 | log.Println(msg.Value) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /docs/tomorrow-night.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Night Theme */ 2 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ 3 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 4 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ 5 | 6 | /* Tomorrow Comment */ 7 | .hljs-comment { 8 | color: #969896; 9 | } 10 | 11 | /* Tomorrow Red */ 12 | .hljs-variable, 13 | .hljs-attribute, 14 | .hljs-tag, 15 | .hljs-regexp, 16 | .ruby .hljs-constant, 17 | .xml .hljs-tag .hljs-title, 18 | .xml .hljs-pi, 19 | .xml .hljs-doctype, 20 | .html .hljs-doctype, 21 | .css .hljs-id, 22 | .css .hljs-class, 23 | .css .hljs-pseudo { 24 | color: #cc6666; 25 | } 26 | 27 | /* Tomorrow Orange */ 28 | .hljs-number, 29 | .hljs-preprocessor, 30 | .hljs-pragma, 31 | .hljs-built_in, 32 | .hljs-literal, 33 | .hljs-params, 34 | .hljs-constant { 35 | color: #de935f; 36 | } 37 | 38 | /* Tomorrow Yellow */ 39 | .ruby .hljs-class .hljs-title, 40 | .css .hljs-rule .hljs-attribute { 41 | color: #f0c674; 42 | } 43 | 44 | /* Tomorrow Green */ 45 | .hljs-string, 46 | .hljs-value, 47 | .hljs-inheritance, 48 | .hljs-header, 49 | .hljs-name, 50 | .ruby .hljs-symbol, 51 | .xml .hljs-cdata { 52 | color: #b5bd68; 53 | } 54 | 55 | /* Tomorrow Aqua */ 56 | .hljs-title, 57 | .css .hljs-hexcolor { 58 | color: #8abeb7; 59 | } 60 | 61 | /* Tomorrow Blue */ 62 | .hljs-function, 63 | .python .hljs-decorator, 64 | .python .hljs-title, 65 | .ruby .hljs-function .hljs-title, 66 | .ruby .hljs-title .hljs-keyword, 67 | .perl .hljs-sub, 68 | .javascript .hljs-title, 69 | .coffeescript .hljs-title { 70 | color: #81a2be; 71 | } 72 | 73 | /* Tomorrow Purple */ 74 | .hljs-keyword, 75 | .javascript .hljs-function { 76 | color: #b294bb; 77 | } 78 | 79 | .hljs { 80 | display: block; 81 | overflow-x: auto; 82 | background: #1d1f21; 83 | color: #c5c8c6; 84 | } 85 | 86 | .coffeescript .javascript, 87 | .javascript .xml, 88 | .tex .hljs-formula, 89 | .xml .javascript, 90 | .xml .vbscript, 91 | .xml .css, 92 | .xml .hljs-cdata { 93 | opacity: 0.5; 94 | } 95 | 96 | .hljs-addition { 97 | color: #718c00; 98 | } 99 | 100 | .hljs-deletion { 101 | color: #c82829; 102 | } 103 | -------------------------------------------------------------------------------- /src/ping/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net" 9 | 10 | "google.golang.org/grpc" 11 | 12 | pb "github.com/jergoo/go-grpc-tutorial/protos/ping" // 引入编译生成的包 13 | ) 14 | 15 | // PingPongServer 实现 pb.PingPongServer 接口 16 | type PingPongServer struct { 17 | pb.UnimplementedPingPongServer // 兼容性需要,避免未实现server接口全部方法 18 | } 19 | 20 | // Ping 单次请求-响应模式 21 | func (s *PingPongServer) Ping(ctx context.Context, req *pb.PingRequest) (*pb.PongResponse, error) { 22 | return &pb.PongResponse{Value: "pong"}, nil 23 | } 24 | 25 | // MultiPong 服务端流模式 26 | func (s *PingPongServer) MultiPong(req *pb.PingRequest, stream pb.PingPong_MultiPongServer) error { 27 | for i := 0; i < 10; i++ { 28 | data := &pb.PongResponse{Value: "pong"} 29 | // 发送消息 30 | err := stream.Send(data) 31 | if err != nil { 32 | return err 33 | } 34 | } 35 | return nil 36 | } 37 | 38 | // MultiPing 客户端流模式 39 | func (s *PingPongServer) MultiPing(stream pb.PingPong_MultiPingServer) error { 40 | msgs := []string{} 41 | for { 42 | // 提前结束接收消息 43 | if len(msgs) > 5 { 44 | return stream.SendAndClose(&pb.PongResponse{Value: "ping enough, max 5"}) 45 | } 46 | 47 | msg, err := stream.Recv() 48 | if err != nil { 49 | // 客户端消息结束,返回响应信息 50 | if err == io.EOF { 51 | return stream.SendAndClose(&pb.PongResponse{Value: fmt.Sprintf("got %d ping", len(msgs))}) 52 | } 53 | return err 54 | } 55 | msgs = append(msgs, msg.Value) 56 | } 57 | } 58 | 59 | // MultiPingPong 双向流模式 60 | func (s *PingPongServer) MultiPingPong(stream pb.PingPong_MultiPingPongServer) error { 61 | msgs := []string{} 62 | for { 63 | // 接收消息 64 | msg, err := stream.Recv() 65 | if err != nil { 66 | if err == io.EOF { 67 | break 68 | } 69 | return err 70 | } 71 | msgs = append(msgs, msg.Value) 72 | 73 | // 每收到两个消息响应一次 74 | if len(msgs)%2 == 0 { 75 | err = stream.Send(&pb.PongResponse{Value: "pong"}) 76 | if err != nil { 77 | return err 78 | } 79 | } 80 | } 81 | return nil 82 | } 83 | 84 | // 启动server 85 | func main() { 86 | srv := grpc.NewServer() 87 | // 注册 PingPongServer 88 | pb.RegisterPingPongServer(srv, &PingPongServer{}) 89 | lis, err := net.Listen("tcp", ":1234") 90 | if err != nil { 91 | log.Fatal(err) 92 | } 93 | log.Println("listen on 1234") 94 | srv.Serve(lis) 95 | } 96 | -------------------------------------------------------------------------------- /archive/docs/c1/install.md: -------------------------------------------------------------------------------- 1 | # 安装 2 | 3 | 环境: 4 | ------- 5 | 6 | * 操作系统:macOS 10.12.5 / Ubuntu 16.04 7 | * golang 版本:1.8.3 8 | * protobuf 版本:3.3.2 9 | * grpc-go 版本:1.4.2 10 | 11 | 12 | 准备工作 13 | ------- 14 | 15 | * Linux 安装 [linuxbrew](https://github.com/Homebrew/linuxbrew) 16 | * macOS 安装 [homebrew](http://brew.sh/) 17 | 18 | 19 | protobuf 20 | -------- 21 | 22 | 项目地址:[google/protobuf](https://github.com/google/protobuf) 23 | 24 | 这里直接使用`brew`工具安装 25 | 26 | ```sh 27 | $ brew install protobuf 28 | ``` 29 | `brew`默认会安装最新版本,执行`protoc`命令查看当前版本: 30 | 31 | ```sh 32 | $ protoc --version 33 | libprotoc 3.3.2 34 | ``` 35 | 36 | 37 | grpc-go 38 | ------- 39 | 40 | 项目地址:[grpc-go](https://github.com/grpc/grpc-go) 41 | 42 | * 要求golang版本 >= 1.6 43 | 44 | ```sh 45 | $ go get -u google.golang.org/grpc 46 | ``` 47 | 48 | 49 | golang protobuf 50 | --------------- 51 | 52 | 项目地址:[golang/protobuf](https://github.com/golang/protobuf) 53 | 54 | * 要求golang版本 > 1.4 55 | * 使用前要求安装protobuf编译器 56 | 57 | ```sh 58 | $ go get -u github.com/golang/protobuf/{proto,protoc-gen-go} 59 | ``` 60 | 61 | 安装`protoc-gen-go`到 `$GOPATH/bin`。 注意:该目录必须在系统的环境变量`$PATH`中。 62 | 63 | 如果一路没有问题的话,到此为止,需要的环境都安装好了😀。 64 | 65 | 66 | 编译器使用 67 | -------- 68 | 69 | 使用`protoc`命令编译`.proto`文件,不同语言支持需要指定输出参数,如: 70 | 71 | ```sh 72 | $ protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto 73 | ``` 74 | 75 | 这里详细介绍golang的编译姿势: 76 | 77 | * `-I` 参数:指定import路径,可以指定多个`-I`参数,编译时按顺序查找,不指定时默认查找当前目录 78 | * `--go_out` :golang编译支持,支持以下参数 79 | 80 | * `plugins=plugin1+plugin2` - 指定插件,目前只支持grpc,即:`plugins=grpc` 81 | * `M` 参数 - 指定导入的.proto文件路径编译后对应的golang包名(不指定本参数默认就是`.proto`文件中`import`语句的路径) 82 | * `import_prefix=xxx` - 为所有`import`路径添加前缀,主要用于编译子目录内的多个proto文件,这个参数按理说很有用,尤其适用替代一些情况时的`M`参数,但是实际使用时有个蛋疼的问题导致并不能达到我们预想的效果,自己尝试看看吧 83 | * `import_path=foo/bar` - 用于指定未声明`package`或`go_package`的文件的包名,最右面的斜线前的字符会被忽略 84 | * 末尾 `:编译文件路径 .proto文件路径(支持通配符)` 85 | 86 | 完整示例: 87 | 88 | ```sh 89 | $ protoc -I . --go_out=plugins=grpc,Mfoo/bar.proto=bar,import_prefix=foo/,import_path=foo/bar:. ./*.proto 90 | ``` 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /archive/src/hello_interceptor/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入proto包 7 | 8 | "golang.org/x/net/context" 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/credentials" // 引入grpc认证包 11 | "google.golang.org/grpc/grpclog" 12 | ) 13 | 14 | const ( 15 | // Address gRPC服务地址 16 | Address = "127.0.0.1:50052" 17 | 18 | // OpenTLS 是否开启TLS认证 19 | OpenTLS = true 20 | ) 21 | 22 | // customCredential 自定义认证 23 | type customCredential struct{} 24 | 25 | // GetRequestMetadata 实现自定义认证接口 26 | func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 27 | return map[string]string{ 28 | "appid": "101010", 29 | "appkey": "i am key", 30 | }, nil 31 | } 32 | 33 | // RequireTransportSecurity 自定义认证是否开启TLS 34 | func (c customCredential) RequireTransportSecurity() bool { 35 | return OpenTLS 36 | } 37 | 38 | func main() { 39 | var err error 40 | var opts []grpc.DialOption 41 | 42 | if OpenTLS { 43 | // TLS连接 44 | creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "server name") 45 | if err != nil { 46 | grpclog.Fatalf("Failed to create TLS credentials %v", err) 47 | } 48 | opts = append(opts, grpc.WithTransportCredentials(creds)) 49 | } else { 50 | opts = append(opts, grpc.WithInsecure()) 51 | } 52 | 53 | // 指定自定义认证 54 | opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential))) 55 | // 指定客户端interceptor 56 | opts = append(opts, grpc.WithUnaryInterceptor(interceptor)) 57 | 58 | conn, err := grpc.Dial(Address, opts...) 59 | if err != nil { 60 | grpclog.Fatalln(err) 61 | } 62 | defer conn.Close() 63 | 64 | // 初始化客户端 65 | c := pb.NewHelloClient(conn) 66 | 67 | // 调用方法 68 | req := &pb.HelloRequest{Name: "gRPC"} 69 | res, err := c.SayHello(context.Background(), req) 70 | if err != nil { 71 | grpclog.Fatalln(err) 72 | } 73 | 74 | grpclog.Println(res.Message) 75 | } 76 | 77 | // interceptor 客户端拦截器 78 | func interceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { 79 | start := time.Now() 80 | err := invoker(ctx, method, req, reply, cc, opts...) 81 | grpclog.Printf("method=%s req=%v rep=%v duration=%s error=%v\n", method, req, reply, time.Since(start), err) 82 | return err 83 | } 84 | -------------------------------------------------------------------------------- /src/protos/google/protobuf/source_context.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option java_package = "com.google.protobuf"; 37 | option java_outer_classname = "SourceContextProto"; 38 | option java_multiple_files = true; 39 | option objc_class_prefix = "GPB"; 40 | option go_package = "google.golang.org/protobuf/types/known/sourcecontextpb"; 41 | 42 | // `SourceContext` represents information about the source of a 43 | // protobuf element, like the file in which it is defined. 44 | message SourceContext { 45 | // The path-qualified name of the .proto file that contained the associated 46 | // protobuf element. For example: `"google/protobuf/source_context.proto"`. 47 | string file_name = 1; 48 | } 49 | -------------------------------------------------------------------------------- /src/protos/google/protobuf/empty.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option go_package = "google.golang.org/protobuf/types/known/emptypb"; 37 | option java_package = "com.google.protobuf"; 38 | option java_outer_classname = "EmptyProto"; 39 | option java_multiple_files = true; 40 | option objc_class_prefix = "GPB"; 41 | option cc_enable_arenas = true; 42 | 43 | // A generic empty message that you can re-use to avoid defining duplicated 44 | // empty messages in your APIs. A typical example is to use it as the request 45 | // or the response type of an API method. For instance: 46 | // 47 | // service Foo { 48 | // rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); 49 | // } 50 | // 51 | message Empty {} 52 | -------------------------------------------------------------------------------- /archive/docs/c2/trace.md: -------------------------------------------------------------------------------- 1 | # 内置Trace 2 | 3 | grpc内置了客户端和服务端的请求追踪,基于`golang.org/x/net/trace`包实现,默认是开启状态,可以查看事件和请求日志,对于基本的请求状态查看调试也是很有帮助的,客户端与服务端基本一致,这里以服务端开启trace server为例,修改hello项目服务端代码: 4 | 5 | ## 目录结构 6 | 7 | ``` 8 | |—— hello_trace/ 9 | |—— client/ 10 | |—— main.go // 客户端 11 | |—— server/ 12 | |—— main.go // 服务端 13 | |—— proto/ 14 | |—— hello/ 15 | |—— hello.proto // proto描述文件 16 | |—— hello.pb.go // proto编译后文件 17 | ``` 18 | 19 | ## 示例代码 20 | 21 | ```go 22 | package main 23 | 24 | import ( 25 | "fmt" 26 | "net" 27 | "net/http" 28 | 29 | pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入编译生成的包 30 | 31 | "golang.org/x/net/context" 32 | "golang.org/x/net/trace" 33 | "google.golang.org/grpc" 34 | "google.golang.org/grpc/grpclog" 35 | ) 36 | 37 | const ( 38 | // Address gRPC服务地址 39 | Address = "127.0.0.1:50052" 40 | ) 41 | 42 | // 定义helloService并实现约定的接口 43 | type helloService struct{} 44 | 45 | // HelloService Hello服务 46 | var HelloService = helloService{} 47 | 48 | // SayHello 实现Hello服务接口 49 | func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { 50 | resp := new(pb.HelloResponse) 51 | resp.Message = fmt.Sprintf("Hello %s.", in.Name) 52 | 53 | return resp, nil 54 | } 55 | 56 | func main() { 57 | listen, err := net.Listen("tcp", Address) 58 | if err != nil { 59 | grpclog.Fatalf("failed to listen: %v", err) 60 | } 61 | 62 | // 实例化grpc Server 63 | s := grpc.NewServer() 64 | 65 | // 注册HelloService 66 | pb.RegisterHelloServer(s, HelloService) 67 | 68 | // 开启trace 69 | go startTrace() 70 | 71 | grpclog.Println("Listen on " + Address) 72 | s.Serve(listen) 73 | } 74 | 75 | func startTrace() { 76 | trace.AuthRequest = func(req *http.Request) (any, sensitive bool) { 77 | return true, true 78 | } 79 | go http.ListenAndServe(":50051", nil) 80 | grpclog.Println("Trace listen on 50051") 81 | } 82 | ``` 83 | 这里我们开启一个http服务监听50051端口,用来查看grpc请求的trace信息 84 | 85 | 运行: 86 | 87 | ```sh 88 | $ go run main.go 89 | 90 | Listen on 127.0.0.1:50052 91 | Trace listen on 50051 92 | 93 | # 进入client目录执行一次客户端请求 94 | ``` 95 | 96 | 97 | ## 服务端事件查看 98 | 99 | 访问:localhost:50051/debug/events,结果如图: 100 | 101 | ![](../_media/grpc_trace_events.jpg) 102 | 103 | 可以看到服务端注册的服务和服务正常启动的事件信息。 104 | 105 | 106 | ## 请求日志信息查看 107 | 108 | 访问:localhost:50051/debug/requests,结果如图: 109 | 110 | ![](../_media/grpc_trace_requests.jpg) 111 | 112 | 这里可以显示最近的请求状态,包括请求的服务、参数、耗时、响应,对于简单的状态查看还是很方便的,默认值显示最近10条记录。 113 | -------------------------------------------------------------------------------- /src/docs/advance/monitor/tracing.md: -------------------------------------------------------------------------------- 1 | # tracing 2 | 3 | --- 4 | 5 | grpc内置了客户端和服务端的请求追踪,基于`golang.org/x/net/trace`包实现,默认是开启状态,可以查看事件和请求日志,对于基本的请求状态查看调试也是很有帮助的,客户端与服务端基本一致,这里以服务端开启trace server为例,修改hello项目服务端代码: 6 | 7 | ## 目录结构 8 | 9 | ``` 10 | |—— hello_trace/ 11 | |—— client/ 12 | |—— main.go // 客户端 13 | |—— server/ 14 | |—— main.go // 服务端 15 | |—— proto/ 16 | |—— hello/ 17 | |—— hello.proto // proto描述文件 18 | |—— hello.pb.go // proto编译后文件 19 | ``` 20 | 21 | ## 示例代码 22 | 23 | ```go 24 | package main 25 | 26 | import ( 27 | "fmt" 28 | "net" 29 | "net/http" 30 | 31 | pb "github.com/jergoo/go-grpc-tutorial/proto/hello" // 引入编译生成的包 32 | 33 | "golang.org/x/net/context" 34 | "golang.org/x/net/trace" 35 | "google.golang.org/grpc" 36 | "google.golang.org/grpc/grpclog" 37 | ) 38 | 39 | const ( 40 | // Address gRPC服务地址 41 | Address = "127.0.0.1:50052" 42 | ) 43 | 44 | // 定义helloService并实现约定的接口 45 | type helloService struct{} 46 | 47 | // HelloService Hello服务 48 | var HelloService = helloService{} 49 | 50 | // SayHello 实现Hello服务接口 51 | func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { 52 | resp := new(pb.HelloResponse) 53 | resp.Message = fmt.Sprintf("Hello %s.", in.Name) 54 | 55 | return resp, nil 56 | } 57 | 58 | func main() { 59 | listen, err := net.Listen("tcp", Address) 60 | if err != nil { 61 | grpclog.Fatalf("failed to listen: %v", err) 62 | } 63 | 64 | // 实例化grpc Server 65 | s := grpc.NewServer() 66 | 67 | // 注册HelloService 68 | pb.RegisterHelloServer(s, HelloService) 69 | 70 | // 开启trace 71 | go startTrace() 72 | 73 | grpclog.Println("Listen on " + Address) 74 | s.Serve(listen) 75 | } 76 | 77 | func startTrace() { 78 | trace.AuthRequest = func(req *http.Request) (any, sensitive bool) { 79 | return true, true 80 | } 81 | go http.ListenAndServe(":50051", nil) 82 | grpclog.Println("Trace listen on 50051") 83 | } 84 | ``` 85 | 这里我们开启一个http服务监听50051端口,用来查看grpc请求的trace信息 86 | 87 | 运行: 88 | 89 | ```sh 90 | $ go run main.go 91 | 92 | Listen on 127.0.0.1:50052 93 | Trace listen on 50051 94 | 95 | # 进入client目录执行一次客户端请求 96 | ``` 97 | 98 | 99 | ## 服务端事件查看 100 | 101 | 访问:localhost:50051/debug/events,结果如图: 102 | 103 | ![](../_media/grpc_trace_events.jpg) 104 | 105 | 可以看到服务端注册的服务和服务正常启动的事件信息。 106 | 107 | 108 | ## 请求日志信息查看 109 | 110 | 访问:localhost:50051/debug/requests,结果如图: 111 | 112 | ![](../_media/grpc_trace_requests.jpg) 113 | 114 | 这里可以显示最近的请求状态,包括请求的服务、参数、耗时、响应,对于简单的状态查看还是很方便的,默认值显示最近10条记录。 115 | -------------------------------------------------------------------------------- /archive/src/hello_interceptor/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | pb "github.com/jergoo/go-grpc-example/proto/hello" 8 | 9 | "golang.org/x/net/context" 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/codes" // grpc 响应状态码 12 | "google.golang.org/grpc/credentials" // grpc认证包 13 | "google.golang.org/grpc/grpclog" 14 | "google.golang.org/grpc/metadata" // grpc metadata包 15 | ) 16 | 17 | const ( 18 | // Address gRPC服务地址 19 | Address = "127.0.0.1:50052" 20 | ) 21 | 22 | // 定义helloService并实现约定的接口 23 | type helloService struct{} 24 | 25 | // HelloService Hello服务 26 | var HelloService = helloService{} 27 | 28 | // SayHello 实现Hello服务接口 29 | func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { 30 | resp := new(pb.HelloResponse) 31 | resp.Message = fmt.Sprintf("Hello %s.", in.Name) 32 | 33 | return resp, nil 34 | } 35 | 36 | func main() { 37 | listen, err := net.Listen("tcp", Address) 38 | if err != nil { 39 | grpclog.Fatalf("Failed to listen: %v", err) 40 | } 41 | 42 | var opts []grpc.ServerOption 43 | 44 | // TLS认证 45 | creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem", "../../keys/server.key") 46 | if err != nil { 47 | grpclog.Fatalf("Failed to generate credentials %v", err) 48 | } 49 | 50 | opts = append(opts, grpc.Creds(creds)) 51 | 52 | // 注册interceptor 53 | opts = append(opts, grpc.UnaryInterceptor(interceptor)) 54 | 55 | // 实例化grpc Server 56 | s := grpc.NewServer(opts...) 57 | 58 | // 注册HelloService 59 | pb.RegisterHelloServer(s, HelloService) 60 | 61 | grpclog.Println("Listen on " + Address + " with TLS + Token + Interceptor") 62 | 63 | s.Serve(listen) 64 | } 65 | 66 | // auth 验证Token 67 | func auth(ctx context.Context) error { 68 | md, ok := metadata.FromIncomingContext(ctx) 69 | if !ok { 70 | return grpc.Errorf(codes.Unauthenticated, "无Token认证信息") 71 | } 72 | 73 | var ( 74 | appid string 75 | appkey string 76 | ) 77 | 78 | if val, ok := md["appid"]; ok { 79 | appid = val[0] 80 | } 81 | 82 | if val, ok := md["appkey"]; ok { 83 | appkey = val[0] 84 | } 85 | 86 | if appid != "101010" || appkey != "i am key" { 87 | return grpc.Errorf(codes.Unauthenticated, "Token认证信息无效: appid=%s, appkey=%s", appid, appkey) 88 | } 89 | 90 | return nil 91 | } 92 | 93 | // interceptor 拦截器 94 | func interceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 95 | err := auth(ctx) 96 | if err != nil { 97 | return nil, err 98 | } 99 | // 继续处理请求 100 | return handler(ctx, req) 101 | } 102 | -------------------------------------------------------------------------------- /src/protos/google/api/annotations.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: google/api/annotations.proto 3 | 4 | /* 5 | Package google_api is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | google/api/annotations.proto 9 | google/api/http.proto 10 | 11 | It has these top-level messages: 12 | Http 13 | HttpRule 14 | CustomHttpPattern 15 | */ 16 | package google_api 17 | 18 | import proto "github.com/golang/protobuf/proto" 19 | import fmt "fmt" 20 | import math "math" 21 | import google_protobuf "github.com/golang/protobuf/protoc-gen-go/descriptor" 22 | 23 | // Reference imports to suppress errors if they are not otherwise used. 24 | var _ = proto.Marshal 25 | var _ = fmt.Errorf 26 | var _ = math.Inf 27 | 28 | // This is a compile-time assertion to ensure that this generated file 29 | // is compatible with the proto package it is being compiled against. 30 | // A compilation error at this line likely means your copy of the 31 | // proto package needs to be updated. 32 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 33 | 34 | var E_Http = &proto.ExtensionDesc{ 35 | ExtendedType: (*google_protobuf.MethodOptions)(nil), 36 | ExtensionType: (*HttpRule)(nil), 37 | Field: 72295728, 38 | Name: "google.api.http", 39 | Tag: "bytes,72295728,opt,name=http", 40 | Filename: "google/api/annotations.proto", 41 | } 42 | 43 | func init() { 44 | proto.RegisterExtension(E_Http) 45 | } 46 | 47 | func init() { proto.RegisterFile("google/api/annotations.proto", fileDescriptor0) } 48 | 49 | var fileDescriptor0 = []byte{ 50 | // 169 bytes of a gzipped FileDescriptorProto 51 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x49, 0xcf, 0xcf, 0x4f, 52 | 0xcf, 0x49, 0xd5, 0x4f, 0x2c, 0xc8, 0xd4, 0x4f, 0xcc, 0xcb, 0xcb, 0x2f, 0x49, 0x2c, 0xc9, 0xcc, 53 | 0xcf, 0x2b, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x82, 0xc8, 0xea, 0x25, 0x16, 0x64, 54 | 0x4a, 0x89, 0x22, 0xa9, 0xcc, 0x28, 0x29, 0x29, 0x80, 0x28, 0x91, 0x52, 0x80, 0x0a, 0x83, 0x79, 55 | 0x49, 0xa5, 0x69, 0xfa, 0x29, 0xa9, 0xc5, 0xc9, 0x45, 0x99, 0x05, 0x25, 0xf9, 0x45, 0x10, 0x15, 56 | 0x56, 0xde, 0x5c, 0x2c, 0x20, 0xf5, 0x42, 0x72, 0x7a, 0x50, 0xd3, 0x60, 0x4a, 0xf5, 0x7c, 0x53, 57 | 0x4b, 0x32, 0xf2, 0x53, 0xfc, 0x0b, 0xc0, 0x56, 0x4a, 0x6c, 0x38, 0xb5, 0x47, 0x49, 0x81, 0x51, 58 | 0x83, 0xdb, 0x48, 0x44, 0x0f, 0x61, 0xad, 0x9e, 0x47, 0x49, 0x49, 0x41, 0x50, 0x69, 0x4e, 0x6a, 59 | 0x10, 0xd8, 0x10, 0x27, 0x15, 0x2e, 0xbe, 0xe4, 0xfc, 0x5c, 0x24, 0x05, 0x4e, 0x02, 0x8e, 0x08, 60 | 0x67, 0x07, 0x80, 0x4c, 0x0e, 0x60, 0x4c, 0x62, 0x03, 0x5b, 0x61, 0x0c, 0x08, 0x00, 0x00, 0xff, 61 | 0xff, 0x4f, 0xd1, 0x89, 0x83, 0xde, 0x00, 0x00, 0x00, 62 | } 63 | -------------------------------------------------------------------------------- /archive/src/proto/google/api/annotations.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: google/api/annotations.proto 3 | 4 | /* 5 | Package google_api is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | google/api/annotations.proto 9 | google/api/http.proto 10 | 11 | It has these top-level messages: 12 | Http 13 | HttpRule 14 | CustomHttpPattern 15 | */ 16 | package google_api 17 | 18 | import proto "github.com/golang/protobuf/proto" 19 | import fmt "fmt" 20 | import math "math" 21 | import google_protobuf "github.com/golang/protobuf/protoc-gen-go/descriptor" 22 | 23 | // Reference imports to suppress errors if they are not otherwise used. 24 | var _ = proto.Marshal 25 | var _ = fmt.Errorf 26 | var _ = math.Inf 27 | 28 | // This is a compile-time assertion to ensure that this generated file 29 | // is compatible with the proto package it is being compiled against. 30 | // A compilation error at this line likely means your copy of the 31 | // proto package needs to be updated. 32 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 33 | 34 | var E_Http = &proto.ExtensionDesc{ 35 | ExtendedType: (*google_protobuf.MethodOptions)(nil), 36 | ExtensionType: (*HttpRule)(nil), 37 | Field: 72295728, 38 | Name: "google.api.http", 39 | Tag: "bytes,72295728,opt,name=http", 40 | Filename: "google/api/annotations.proto", 41 | } 42 | 43 | func init() { 44 | proto.RegisterExtension(E_Http) 45 | } 46 | 47 | func init() { proto.RegisterFile("google/api/annotations.proto", fileDescriptor0) } 48 | 49 | var fileDescriptor0 = []byte{ 50 | // 169 bytes of a gzipped FileDescriptorProto 51 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x49, 0xcf, 0xcf, 0x4f, 52 | 0xcf, 0x49, 0xd5, 0x4f, 0x2c, 0xc8, 0xd4, 0x4f, 0xcc, 0xcb, 0xcb, 0x2f, 0x49, 0x2c, 0xc9, 0xcc, 53 | 0xcf, 0x2b, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x82, 0xc8, 0xea, 0x25, 0x16, 0x64, 54 | 0x4a, 0x89, 0x22, 0xa9, 0xcc, 0x28, 0x29, 0x29, 0x80, 0x28, 0x91, 0x52, 0x80, 0x0a, 0x83, 0x79, 55 | 0x49, 0xa5, 0x69, 0xfa, 0x29, 0xa9, 0xc5, 0xc9, 0x45, 0x99, 0x05, 0x25, 0xf9, 0x45, 0x10, 0x15, 56 | 0x56, 0xde, 0x5c, 0x2c, 0x20, 0xf5, 0x42, 0x72, 0x7a, 0x50, 0xd3, 0x60, 0x4a, 0xf5, 0x7c, 0x53, 57 | 0x4b, 0x32, 0xf2, 0x53, 0xfc, 0x0b, 0xc0, 0x56, 0x4a, 0x6c, 0x38, 0xb5, 0x47, 0x49, 0x81, 0x51, 58 | 0x83, 0xdb, 0x48, 0x44, 0x0f, 0x61, 0xad, 0x9e, 0x47, 0x49, 0x49, 0x41, 0x50, 0x69, 0x4e, 0x6a, 59 | 0x10, 0xd8, 0x10, 0x27, 0x15, 0x2e, 0xbe, 0xe4, 0xfc, 0x5c, 0x24, 0x05, 0x4e, 0x02, 0x8e, 0x08, 60 | 0x67, 0x07, 0x80, 0x4c, 0x0e, 0x60, 0x4c, 0x62, 0x03, 0x5b, 0x61, 0x0c, 0x08, 0x00, 0x00, 0xff, 61 | 0xff, 0x4f, 0xd1, 0x89, 0x83, 0xde, 0x00, 0x00, 0x00, 62 | } 63 | -------------------------------------------------------------------------------- /archive/docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Go gRPC Tutorial -- Jergoo 6 | 7 | 8 | 9 | 10 | 11 | 20 | 21 | 22 |
Please wait...
23 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/ping/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "log" 7 | "time" 8 | 9 | "google.golang.org/grpc" 10 | 11 | pb "github.com/jergoo/go-grpc-tutorial/protos/ping" // 引入编译生成的包 12 | ) 13 | 14 | // Ping 单次请求-响应模式 15 | func Ping() { 16 | conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure()) 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | defer conn.Close() 21 | 22 | // 实例化客户端并调用 23 | client := pb.NewPingPongClient(conn) 24 | res, err := client.Ping(context.Background(), &pb.PingRequest{Value: "ping"}) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | log.Println(res.Value) 29 | } 30 | 31 | // MultiPong 服务端流模式 32 | func MultiPong() { 33 | conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure()) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | defer conn.Close() 38 | 39 | // 实例化客户端并调用 40 | client := pb.NewPingPongClient(conn) 41 | // 获得对 stream 对象的引用 42 | stream, err := client.MultiPong(context.Background(), &pb.PingRequest{Value: "ping"}) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | // 循环接收数据流 48 | for { 49 | msg, err := stream.Recv() 50 | if err != nil { 51 | if err == io.EOF { 52 | break 53 | } 54 | log.Fatal(err) 55 | } 56 | log.Println(msg.Value) 57 | } 58 | } 59 | 60 | // MultiPing 客户端流模式 61 | func MultiPing() { 62 | conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure()) 63 | if err != nil { 64 | log.Fatal(err) 65 | } 66 | defer conn.Close() 67 | 68 | // 实例化客户端并调用 69 | client := pb.NewPingPongClient(conn) 70 | stream, err := client.MultiPing(context.Background()) 71 | if err != nil { 72 | log.Fatal(err) 73 | } 74 | 75 | // 发送数据 76 | for i := 0; i < 5; i++ { 77 | data := &pb.PingRequest{Value: "ping"} 78 | err = stream.Send(data) 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | } 83 | 84 | // 发送结束并获取服务端响应 85 | res, err := stream.CloseAndRecv() 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | 90 | log.Println(res.Value) 91 | } 92 | 93 | // MultiPingPong 双向流模式 94 | func MultiPingPong() { 95 | conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure()) 96 | if err != nil { 97 | log.Fatal(err) 98 | } 99 | defer conn.Close() 100 | 101 | // 实例化客户端并调用 102 | client := pb.NewPingPongClient(conn) 103 | stream, err := client.MultiPingPong(context.Background()) 104 | if err != nil { 105 | log.Fatal(err) 106 | } 107 | 108 | // 在另一个goroutine中接收数据 109 | c := make(chan struct{}) 110 | go func(stream pb.PingPong_MultiPingPongClient, c chan struct{}) { 111 | defer func() { 112 | c <- struct{}{} 113 | }() 114 | for { 115 | msg, err := stream.Recv() 116 | if err != nil { 117 | if err == io.EOF { 118 | break 119 | } 120 | log.Fatal(err) 121 | } 122 | log.Printf("recv:%s\n", msg.Value) 123 | } 124 | }(stream, c) 125 | 126 | // 发送数据 127 | for i := 0; i < 6; i++ { 128 | data := &pb.PingRequest{Value: "ping"} 129 | err = stream.Send(data) 130 | if err != nil { 131 | log.Fatal(err) 132 | } 133 | log.Printf("send:%s\n", data.Value) 134 | time.Sleep(500 * time.Millisecond) 135 | } 136 | 137 | // 结束发送 138 | stream.CloseSend() 139 | // 等待接收完成 140 | <-c 141 | } 142 | -------------------------------------------------------------------------------- /docs/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/docs/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /archive/docs/_media/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /archive/docs/c1/protobuf-go.md: -------------------------------------------------------------------------------- 1 | # Protobuf⇢Go转换 2 | 3 | 这里使用一个测试文件对照说明常用结构的protobuf到golang的转换。只说明关键部分代码,详细内容请查看完整文件。示例文件在`proto/test`目录下。 4 | 5 | 6 | ## Package 7 | 8 | 在proto文件中使用`package`关键字声明包名,默认转换成go中的包名与此一致,如果需要指定不一样的包名,可以使用`go_package`选项: 9 | 10 | ```protobuf 11 | package test; 12 | option go_package="test"; 13 | ``` 14 | 15 | ## Message 16 | 17 | proto中的`message`对应go中的`struct`,全部使用驼峰命名规则。嵌套定义的`message`,`enum`转换为go之后,名称变为`Parent_Child`结构。 18 | 19 | 示例proto: 20 | 21 | ```protobuf 22 | // Test 测试 23 | message Test { 24 | int32 age = 1; 25 | int64 count = 2; 26 | double money = 3; 27 | float score = 4; 28 | string name = 5; 29 | bool fat = 6; 30 | bytes char = 7; 31 | // Status 枚举状态 32 | enum Status { 33 | OK = 0; 34 | FAIL = 1; 35 | } 36 | Status status = 8; 37 | // Child 子结构 38 | message Child { 39 | string sex = 1; 40 | } 41 | Child child = 9; 42 | map dict = 10; 43 | } 44 | ``` 45 | 46 | 转换结果: 47 | 48 | ```go 49 | // Status 枚举状态 50 | type Test_Status int32 51 | 52 | const ( 53 | Test_OK Test_Status = 0 54 | Test_FAIL Test_Status = 1 55 | ) 56 | 57 | // Test 测试 58 | type Test struct { 59 | Age int32 `protobuf:"varint,1,opt,name=age" json:"age,omitempty"` 60 | Count int64 `protobuf:"varint,2,opt,name=count" json:"count,omitempty"` 61 | Money float64 `protobuf:"fixed64,3,opt,name=money" json:"money,omitempty"` 62 | Score float32 `protobuf:"fixed32,4,opt,name=score" json:"score,omitempty"` 63 | Name string `protobuf:"bytes,5,opt,name=name" json:"name,omitempty"` 64 | Fat bool `protobuf:"varint,6,opt,name=fat" json:"fat,omitempty"` 65 | Char []byte `protobuf:"bytes,7,opt,name=char,proto3" json:"char,omitempty"` 66 | Status Test_Status `protobuf:"varint,8,opt,name=status,enum=test.Test_Status" json:"status,omitempty"` 67 | Child *Test_Child `protobuf:"bytes,9,opt,name=child" json:"child,omitempty"` 68 | Dict map[string]string `protobuf:"bytes,10,rep,name=dict" json:"dict,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` 69 | } 70 | 71 | // Child 子结构 72 | type Test_Child struct { 73 | Sex string `protobuf:"bytes,1,opt,name=sex" json:"sex,omitempty"` 74 | } 75 | ``` 76 | 除了会生成对应的结构外,还会有些工具方法,如字段的getter: 77 | 78 | ```go 79 | func (m *Test) GetAge() int32 { 80 | if m != nil { 81 | return m.Age 82 | } 83 | return 0 84 | } 85 | ``` 86 | 87 | 枚举类型会生成对应名称的常量,同时会有两个map方便使用: 88 | 89 | ```go 90 | var Test_Status_name = map[int32]string{ 91 | 0: "OK", 92 | 1: "FAIL", 93 | } 94 | var Test_Status_value = map[string]int32{ 95 | "OK": 0, 96 | "FAIL": 1, 97 | } 98 | ``` 99 | 100 | ## Service 101 | 102 | 定义一个简单的Service,`TestService`有一个方法`Test`,接收一个`Request`参数,返回`Response`: 103 | 104 | ```protobuf 105 | // TestService 测试服务 106 | service TestService { 107 | // Test 测试方法 108 | rpc Test(Request) returns (Response) {}; 109 | } 110 | 111 | // Request 请求结构 112 | message Request { 113 | string name = 1; 114 | } 115 | 116 | // Response 响应结构 117 | message Response { 118 | string message = 1; 119 | } 120 | ``` 121 | 122 | 转换结果: 123 | 124 | ```go 125 | // 客户端接口 126 | type TestServiceClient interface { 127 | // Test 测试方法 128 | Test(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) 129 | } 130 | 131 | // 服务端接口 132 | type TestServiceServer interface { 133 | // Test 测试方法 134 | Test(context.Context, *Request) (*Response, error) 135 | } 136 | ``` 137 | 138 | 生成的go代码中包含该Service定义的接口,客户端接口已经自动实现了,直接供客户端使用者调用,服务端接口需要由服务提供方实现。 139 | -------------------------------------------------------------------------------- /archive/src/hello_http_2/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "io/ioutil" 6 | "net" 7 | "net/http" 8 | "strings" 9 | 10 | "github.com/grpc-ecosystem/grpc-gateway/runtime" 11 | pb "github.com/jergoo/go-grpc-example/proto/hello_http" 12 | "golang.org/x/net/context" 13 | "golang.org/x/net/http2" 14 | "google.golang.org/grpc" 15 | "google.golang.org/grpc/credentials" 16 | "google.golang.org/grpc/grpclog" 17 | ) 18 | 19 | // 定义helloHTTPService并实现约定的接口 20 | type helloHTTPService struct{} 21 | 22 | // HelloHTTPService Hello HTTP服务 23 | var HelloHTTPService = helloHTTPService{} 24 | 25 | // SayHello 实现Hello服务接口 26 | func (h helloHTTPService) SayHello(ctx context.Context, in *pb.HelloHTTPRequest) (*pb.HelloHTTPResponse, error) { 27 | resp := new(pb.HelloHTTPResponse) 28 | resp.Message = "Hello " + in.Name + "." 29 | 30 | return resp, nil 31 | } 32 | 33 | func main() { 34 | endpoint := "127.0.0.1:50052" 35 | conn, err := net.Listen("tcp", endpoint) 36 | if err != nil { 37 | grpclog.Fatalf("TCP Listen err:%v\n", err) 38 | } 39 | 40 | // grpc server 41 | creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem", "../../keys/server.key") 42 | if err != nil { 43 | grpclog.Fatalf("Failed to create server TLS credentials %v", err) 44 | } 45 | grpcServer := grpc.NewServer(grpc.Creds(creds)) 46 | pb.RegisterHelloHTTPServer(grpcServer, HelloHTTPService) 47 | 48 | // gw server 49 | ctx := context.Background() 50 | dcreds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "server name") 51 | if err != nil { 52 | grpclog.Fatalf("Failed to create client TLS credentials %v", err) 53 | } 54 | dopts := []grpc.DialOption{grpc.WithTransportCredentials(dcreds)} 55 | gwmux := runtime.NewServeMux() 56 | if err = pb.RegisterHelloHTTPHandlerFromEndpoint(ctx, gwmux, endpoint, dopts); err != nil { 57 | grpclog.Fatalf("Failed to register gw server: %v\n", err) 58 | } 59 | 60 | // http服务 61 | mux := http.NewServeMux() 62 | mux.Handle("/", gwmux) 63 | 64 | srv := &http.Server{ 65 | Addr: endpoint, 66 | Handler: grpcHandlerFunc(grpcServer, mux), 67 | TLSConfig: getTLSConfig(), 68 | } 69 | 70 | grpclog.Infof("gRPC and https listen on: %s\n", endpoint) 71 | 72 | if err = srv.Serve(tls.NewListener(conn, srv.TLSConfig)); err != nil { 73 | grpclog.Fatal("ListenAndServe: ", err) 74 | } 75 | 76 | return 77 | } 78 | 79 | func getTLSConfig() *tls.Config { 80 | cert, _ := ioutil.ReadFile("../../keys/server.pem") 81 | key, _ := ioutil.ReadFile("../../keys/server.key") 82 | var demoKeyPair *tls.Certificate 83 | pair, err := tls.X509KeyPair(cert, key) 84 | if err != nil { 85 | grpclog.Fatalf("TLS KeyPair err: %v\n", err) 86 | } 87 | demoKeyPair = &pair 88 | return &tls.Config{ 89 | Certificates: []tls.Certificate{*demoKeyPair}, 90 | NextProtos: []string{http2.NextProtoTLS}, // HTTP2 TLS支持 91 | } 92 | } 93 | 94 | // grpcHandlerFunc returns an http.Handler that delegates to grpcServer on incoming gRPC 95 | // connections or otherHandler otherwise. Copied from cockroachdb. 96 | func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler { 97 | if otherHandler == nil { 98 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 99 | grpcServer.ServeHTTP(w, r) 100 | }) 101 | } 102 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 103 | if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") { 104 | grpcServer.ServeHTTP(w, r) 105 | } else { 106 | otherHandler.ServeHTTP(w, r) 107 | } 108 | }) 109 | } 110 | -------------------------------------------------------------------------------- /src/docs/basic/hello-grpc.md: -------------------------------------------------------------------------------- 1 | # Hello gRPC 2 | 3 | --- 4 | 5 | 从一个简单的示例说明 go gRPC 的基本使用流程,实现一个 PingPong 服务,客户端发送 ping 请求,服务端返回 pong 响应。 6 | 7 | **源码目录:** 8 | 9 | ``` 10 | |—- src/ 11 | |-- ping/ 12 | |—— client.go // 客户端 13 | |—— server.go // 服务端 14 | |—- protos/ping/ 15 | |—— ping.proto // protobuf描述文件 16 | |—— ping.pb.go // protoc编译生成 17 | |-- ping_grpc.pb.go // protoc编译生成 18 | ``` 19 | 20 | ## 编写 protobuf 文件 21 | 22 | ```protobuf 23 | // src/protos/ping/ping.proto 24 | syntax = "proto3"; // 指定proto版本 25 | package protos; // 指定包名 26 | 27 | // 指定go包路径 28 | option go_package = "protos/ping"; 29 | 30 | // 定义PingPong服务 31 | service PingPong { 32 | // Ping 发送 ping 请求,接收 pong 响应 33 | rpc Ping(PingRequest) returns (PongResponse); 34 | } 35 | 36 | // PingRequest 请求结构 37 | message PingRequest { 38 | string value = 1; // value字段为string类型 39 | } 40 | 41 | // PongResponse 响应结构 42 | message PongResponse { 43 | string value = 1; // value字段为string类型 44 | } 45 | ``` 46 | 47 | 定义了一个名为 `PingPong` 的 service,包含一个 `Ping` 方法,同时声明了 `PingRequest` 和 `PongResponse` 消息结构用于请求和响应。客户端使用 `PingRequest` 参数调用 `Ping` 方法请求服务端,服务端响应 `PongResponse` 消息,一个基本的服务就定义好了。 48 | 49 | ## 编译 protobuf 文件 50 | 51 | ```sh 52 | $ cd src 53 | $ protoc --go_out=. --go-grpc_out=. ./protos/ping/ping.proto 54 | ``` 55 | 在src目录执行编译命令,会在目录 `src/protos/ping` 内生成两个文件 `ping.pb.go` 和 `ping_grpc.pb.go`。可以大概看一下这两个文件的内容,`ping.pb.go` 包含了之前定义的两个message相关的结构,`ping_grpc.pb.go` 包含了定义的service相关的客户端和服务端接口,**不要修改这两个文件的内容**。 56 | 57 | ## 实现服务端接口 58 | 59 | ```go 60 | // src/ping/server.go 61 | package main 62 | 63 | import ( 64 | "context" 65 | "log" 66 | "net" 67 | 68 | "google.golang.org/grpc" 69 | 70 | pb "github.com/jergoo/go-grpc-tutorial/protos/ping" // 引入编译生成的包 71 | ) 72 | 73 | // PingPongServer 实现 pb.PingPongServer 接口 74 | type PingPongServer struct { 75 | pb.UnimplementedPingPongServer 76 | } 77 | 78 | // Ping 单次请求-响应模式 79 | func (s *PingPongServer) Ping(ctx context.Context, req *pb.PingRequest) (*pb.PongResponse, error) { 80 | return &pb.PongResponse{Value: "pong"}, nil 81 | } 82 | 83 | // 启动server 84 | func main() { 85 | srv := grpc.NewServer() 86 | // 注册 PingPongServer 87 | pb.RegisterPingPongServer(srv, &PingPongServer{}) 88 | lis, err := net.Listen("tcp", ":1234") 89 | if err != nil { 90 | log.Fatal(err) 91 | } 92 | log.Println("listen on 1234") 93 | srv.Serve(lis) 94 | } 95 | ``` 96 | 97 | 服务端引入编译生成的包,定义一个 `PingPongServer` 用于实现约定的接口,接口描述可以查看 `ping_grpc.pb.go` 文件中的 `PingPongServer` 接口。实例化 grpc Server 并注册 `PingPongServer` 开始提供服务。 98 | 99 | ## 客户端调用 100 | 101 | ```go 102 | // src/ping/client.go 103 | package main 104 | 105 | import ( 106 | "context" 107 | "log" 108 | 109 | "google.golang.org/grpc" 110 | 111 | pb "github.com/jergoo/go-grpc-tutorial/protos/ping" // 引入编译生成的包 112 | ) 113 | 114 | // Ping 单次请求-响应模式 115 | func Ping() { 116 | conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure()) 117 | if err != nil { 118 | log.Fatal(err) 119 | } 120 | defer conn.Close() 121 | 122 | // 实例化客户端并调用 123 | client := pb.NewPingPongClient(conn) 124 | res, err := client.Ping(context.Background(), &pb.PingRequest{Value: "ping"}) 125 | if err != nil { 126 | log.Fatal(err) 127 | } 128 | log.Println(res.Value) 129 | } 130 | ``` 131 | 132 | 客户端初始化连接,使用 `ping_grpc.pb.go` 中的 `PingPongClient` 实例调用 `Ping` 方法,即可向服务端发起请求并获取响应,就像调用本地方法一样。 133 | 134 | --- 135 | 136 | > 以上就是一个最基础的 gRPC 服务,使用非常简单,底层网络细节全部由 gRPC 处理,开发者只需要关注业务接口设计和实现,基本流程如下: 137 | > 1. 编写 protobuf 描述文件,定义消息结构和服务接口 138 | > 2. 编译 protobuf 文件,生成服务端和客户端接口代码 139 | > 3. 实现 `*_grpc.pb.go` 文件中描述的服务端接口 140 | > 4. 使用 `*_grpc.pb.go` 文件中的client调用服务 141 | -------------------------------------------------------------------------------- /src/docs/advance/metadata.md: -------------------------------------------------------------------------------- 1 | # metadata 2 | 3 | --- 4 | 5 | 服务间使用 Http 相互调用时,经常会设置一些业务自定义 header 如时间戳、trace信息等,gRPC使用 HTTP/2 协议自然也是支持的,gRPC 通过 `google.golang.org/grpc/metadata` 包内的 `MD` 类型提供相关的功能接口。 6 | 7 | **源码目录:** 8 | 9 | ``` 10 | |—- src/ 11 | |-- metadata/ 12 | |—— client.go // 客户端 13 | |—— server.go // 服务端 14 | |—- protos/ping/ 15 | |—— ping.proto // protobuf描述文件 16 | |—— ping.pb.go // protoc编译生成 17 | |-- ping_grpc.pb.go // protoc编译生成 18 | ``` 19 | 20 | ## 类型定义 21 | 22 | ```go 23 | // MD is a mapping from metadata keys to values. Users should use the following 24 | // two convenience functions New and Pairs to generate MD. 25 | type MD map[string][]string 26 | ``` 27 | 28 | `metadata.MD` 类型的定义非常简单,可以像一个普通的 map 一样直接操作,同时 metadata 包里封装了很多工具方法供我们使用。 29 | 30 | ```go 31 | // 使用 New 方法创建 32 | md := metadata.New(map[string]string{"k1":"v1", "k2", "v2"}) 33 | 34 | // 直接使用 make 创建 35 | md := make(metadata.MD) 36 | 37 | // 使用 Pairs 方法创建 38 | md := metadata.Pairs("k1", "v1-1", "k1", "v1-2") 39 | 40 | // 一些操作 41 | md.Set("key", "v1", "v2") 42 | md.Append("key", "v3") 43 | md.Delete("key") 44 | vals := md.Get("key") 45 | 46 | ``` 47 | 48 | ## 发送与接收 49 | ### 客户端 50 | 51 | 客户端请求的 metadata 是通过设置 context 使用的,metadata 包提供了两个 context 相关的方法,设置好 context 后直接在调用 rpc 方法时传入即可,示例代码如下: 52 | 53 | ```go 54 | md := metadata.New(map[string]string{"k1":"v1", "k2", "v2"}) 55 | 56 | // 使用 NewOutgoingContext 初始化一个新的 context 57 | ctx := metadata.NewOutgoingContext(context.Background(), md) 58 | 59 | // 使用 AppendToOutgoingContext 向 context 追加 metadata 60 | ctx = metadata.AppendToOutgoingContext(ctx, "k3", "v3") 61 | 62 | ``` 63 | 64 | 客户端接收响应中的 metadata 需要区分普通 rpc 和 stream rpc,示例如下: 65 | 66 | ```go 67 | // 普通 rpc,使用 grpc.Header 方法包装为 CallOption 68 | var md metadata.MD 69 | res, err := client.Ping(ctx, &pb.PingRequest{Value: "ping"}, grpc.Header(&md)) 70 | 71 | // stream rpc 72 | stream, err := client.MultiPong(context.Background(), &pb.PingRequest{Value: "ping"}) 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | 77 | // 通过 stream 对象的 Header 方法获取 78 | md, err := stream.Header() 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | ``` 83 | 84 | ### 服务端 85 | 86 | 对应客户端请求的 metadata 是使用 context 设置的,那么服务端在接收时自然也是从 context 中读取,metadata 包中的 `FromIncommingContext` 方法就是用来读取 context 中的 metadata数据的,示例如下: 87 | 88 | ```go 89 | // unary rpc 90 | func (s *PingPongServer) Ping(ctx context.Context, req *pb.PingRequest) (*pb.PongResponse, error) { 91 | // 读取请求metadata 92 | md, ok := metadata.FromIncomingContext(ctx) 93 | if ok { 94 | log.Printf("Got md: %v", md) 95 | } 96 | 97 | ... 98 | 99 | // stream rpc 100 | func (s *PingPongServer) MultiPingPong(stream pb.PingPong_MultiPingPongServer) error { 101 | md, ok := metadata.FromIncomingContext(stream.Context()) 102 | if ok { 103 | log.Printf("Got md: %v", md) 104 | } 105 | 106 | ... 107 | ``` 108 | 109 | 服务端设置响应的 metadata 也非常简单,只需要调用封装好的 `SetHeader` 或 `SendHeader` 方法即可,示例如下: 110 | 111 | ```go 112 | // unary rpc 113 | func (s *PingPongServer) Ping(ctx context.Context, req *pb.PingRequest) (*pb.PongResponse, error) { 114 | // 读取请求metadata 115 | md, ok := metadata.FromIncomingContext(ctx) 116 | if ok { 117 | log.Printf("Got md: %v", md) 118 | } 119 | 120 | // SetHeader设置响应 metadata 121 | grpc.SetHeader(ctx, metadata.New(map[string]string{"rkey": "rval"})) 122 | // 注意 SendHeader 只能调用一次 123 | // grpc.SendHeader(ctx, metadata.New(map[string]string{"rkey": "rval"})) 124 | 125 | .... 126 | 127 | // stream rpc, 调用 stream 的 SetHeader 方法 128 | func (s *PingPongServer) MultiPong(req *pb.PingRequest, stream pb.PingPong_MultiPongServer) error { 129 | stream.SetHeader(metadata.New(map[string]string{"rkey": "rval"})) 130 | 131 | // 注意 SendHeader 只能调用一次 132 | // stream.SendHeader(metadata.New(map[string]string{"rkey": "rval"})) 133 | ``` 134 | --- 135 | 136 | -------------------------------------------------------------------------------- /archive/src/proto/hello_http/hello_http.pb.gw.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. 2 | // source: hello_http/hello_http.proto 3 | 4 | /* 5 | Package hello_http is a reverse proxy. 6 | 7 | It translates gRPC into RESTful JSON APIs. 8 | */ 9 | package hello_http 10 | 11 | import ( 12 | "io" 13 | "net/http" 14 | 15 | "github.com/golang/protobuf/proto" 16 | "github.com/grpc-ecosystem/grpc-gateway/runtime" 17 | "github.com/grpc-ecosystem/grpc-gateway/utilities" 18 | "golang.org/x/net/context" 19 | "google.golang.org/grpc" 20 | "google.golang.org/grpc/codes" 21 | "google.golang.org/grpc/grpclog" 22 | "google.golang.org/grpc/status" 23 | ) 24 | 25 | var _ codes.Code 26 | var _ io.Reader 27 | var _ status.Status 28 | var _ = runtime.String 29 | var _ = utilities.NewDoubleArray 30 | 31 | func request_HelloHTTP_SayHello_0(ctx context.Context, marshaler runtime.Marshaler, client HelloHTTPClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { 32 | var protoReq HelloHTTPRequest 33 | var metadata runtime.ServerMetadata 34 | 35 | if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { 36 | return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) 37 | } 38 | 39 | msg, err := client.SayHello(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) 40 | return msg, metadata, err 41 | 42 | } 43 | 44 | // RegisterHelloHTTPHandlerFromEndpoint is same as RegisterHelloHTTPHandler but 45 | // automatically dials to "endpoint" and closes the connection when "ctx" gets done. 46 | func RegisterHelloHTTPHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { 47 | conn, err := grpc.Dial(endpoint, opts...) 48 | if err != nil { 49 | return err 50 | } 51 | defer func() { 52 | if err != nil { 53 | if cerr := conn.Close(); cerr != nil { 54 | grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) 55 | } 56 | return 57 | } 58 | go func() { 59 | <-ctx.Done() 60 | if cerr := conn.Close(); cerr != nil { 61 | grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) 62 | } 63 | }() 64 | }() 65 | 66 | return RegisterHelloHTTPHandler(ctx, mux, conn) 67 | } 68 | 69 | // RegisterHelloHTTPHandler registers the http handlers for service HelloHTTP to "mux". 70 | // The handlers forward requests to the grpc endpoint over "conn". 71 | func RegisterHelloHTTPHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { 72 | client := NewHelloHTTPClient(conn) 73 | 74 | mux.Handle("POST", pattern_HelloHTTP_SayHello_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { 75 | ctx, cancel := context.WithCancel(ctx) 76 | defer cancel() 77 | if cn, ok := w.(http.CloseNotifier); ok { 78 | go func(done <-chan struct{}, closed <-chan bool) { 79 | select { 80 | case <-done: 81 | case <-closed: 82 | cancel() 83 | } 84 | }(ctx.Done(), cn.CloseNotify()) 85 | } 86 | inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) 87 | rctx, err := runtime.AnnotateContext(ctx, mux, req) 88 | if err != nil { 89 | runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) 90 | return 91 | } 92 | resp, md, err := request_HelloHTTP_SayHello_0(rctx, inboundMarshaler, client, req, pathParams) 93 | ctx = runtime.NewServerMetadataContext(ctx, md) 94 | if err != nil { 95 | runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) 96 | return 97 | } 98 | 99 | forward_HelloHTTP_SayHello_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) 100 | 101 | }) 102 | 103 | return nil 104 | } 105 | 106 | var ( 107 | pattern_HelloHTTP_SayHello_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"example", "echo"}, "")) 108 | ) 109 | 110 | var ( 111 | forward_HelloHTTP_SayHello_0 = runtime.ForwardResponseMessage 112 | ) 113 | -------------------------------------------------------------------------------- /docs/fonts/fonts.css: -------------------------------------------------------------------------------- 1 | /* Open Sans is licensed under the Apache License, Version 2.0. See http://www.apache.org/licenses/LICENSE-2.0 */ 2 | /* Source Code Pro is under the Open Font License. See https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL */ 3 | 4 | /* open-sans-300 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 5 | @font-face { 6 | font-family: 'Open Sans'; 7 | font-style: normal; 8 | font-weight: 300; 9 | src: local('Open Sans Light'), local('OpenSans-Light'), 10 | url('open-sans-v17-all-charsets-300.woff2') format('woff2'); 11 | } 12 | 13 | /* open-sans-300italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 14 | @font-face { 15 | font-family: 'Open Sans'; 16 | font-style: italic; 17 | font-weight: 300; 18 | src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'), 19 | url('open-sans-v17-all-charsets-300italic.woff2') format('woff2'); 20 | } 21 | 22 | /* open-sans-regular - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 23 | @font-face { 24 | font-family: 'Open Sans'; 25 | font-style: normal; 26 | font-weight: 400; 27 | src: local('Open Sans Regular'), local('OpenSans-Regular'), 28 | url('open-sans-v17-all-charsets-regular.woff2') format('woff2'); 29 | } 30 | 31 | /* open-sans-italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 32 | @font-face { 33 | font-family: 'Open Sans'; 34 | font-style: italic; 35 | font-weight: 400; 36 | src: local('Open Sans Italic'), local('OpenSans-Italic'), 37 | url('open-sans-v17-all-charsets-italic.woff2') format('woff2'); 38 | } 39 | 40 | /* open-sans-600 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 41 | @font-face { 42 | font-family: 'Open Sans'; 43 | font-style: normal; 44 | font-weight: 600; 45 | src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), 46 | url('open-sans-v17-all-charsets-600.woff2') format('woff2'); 47 | } 48 | 49 | /* open-sans-600italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 50 | @font-face { 51 | font-family: 'Open Sans'; 52 | font-style: italic; 53 | font-weight: 600; 54 | src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'), 55 | url('open-sans-v17-all-charsets-600italic.woff2') format('woff2'); 56 | } 57 | 58 | /* open-sans-700 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 59 | @font-face { 60 | font-family: 'Open Sans'; 61 | font-style: normal; 62 | font-weight: 700; 63 | src: local('Open Sans Bold'), local('OpenSans-Bold'), 64 | url('open-sans-v17-all-charsets-700.woff2') format('woff2'); 65 | } 66 | 67 | /* open-sans-700italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 68 | @font-face { 69 | font-family: 'Open Sans'; 70 | font-style: italic; 71 | font-weight: 700; 72 | src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), 73 | url('open-sans-v17-all-charsets-700italic.woff2') format('woff2'); 74 | } 75 | 76 | /* open-sans-800 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 77 | @font-face { 78 | font-family: 'Open Sans'; 79 | font-style: normal; 80 | font-weight: 800; 81 | src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), 82 | url('open-sans-v17-all-charsets-800.woff2') format('woff2'); 83 | } 84 | 85 | /* open-sans-800italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 86 | @font-face { 87 | font-family: 'Open Sans'; 88 | font-style: italic; 89 | font-weight: 800; 90 | src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'), 91 | url('open-sans-v17-all-charsets-800italic.woff2') format('woff2'); 92 | } 93 | 94 | /* source-code-pro-500 - latin_vietnamese_latin-ext_greek_cyrillic-ext_cyrillic */ 95 | @font-face { 96 | font-family: 'Source Code Pro'; 97 | font-style: normal; 98 | font-weight: 500; 99 | src: url('source-code-pro-v11-all-charsets-500.woff2') format('woff2'); 100 | } 101 | -------------------------------------------------------------------------------- /src/protos/google/protobuf/struct.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "google.golang.org/protobuf/types/known/structpb"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "StructProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // `Struct` represents a structured data value, consisting of fields 44 | // which map to dynamically typed values. In some languages, `Struct` 45 | // might be supported by a native representation. For example, in 46 | // scripting languages like JS a struct is represented as an 47 | // object. The details of that representation are described together 48 | // with the proto support for the language. 49 | // 50 | // The JSON representation for `Struct` is JSON object. 51 | message Struct { 52 | // Unordered map of dynamically typed values. 53 | map fields = 1; 54 | } 55 | 56 | // `Value` represents a dynamically typed value which can be either 57 | // null, a number, a string, a boolean, a recursive struct value, or a 58 | // list of values. A producer of value is expected to set one of these 59 | // variants. Absence of any variant indicates an error. 60 | // 61 | // The JSON representation for `Value` is JSON value. 62 | message Value { 63 | // The kind of value. 64 | oneof kind { 65 | // Represents a null value. 66 | NullValue null_value = 1; 67 | // Represents a double value. 68 | double number_value = 2; 69 | // Represents a string value. 70 | string string_value = 3; 71 | // Represents a boolean value. 72 | bool bool_value = 4; 73 | // Represents a structured value. 74 | Struct struct_value = 5; 75 | // Represents a repeated `Value`. 76 | ListValue list_value = 6; 77 | } 78 | } 79 | 80 | // `NullValue` is a singleton enumeration to represent the null value for the 81 | // `Value` type union. 82 | // 83 | // The JSON representation for `NullValue` is JSON `null`. 84 | enum NullValue { 85 | // Null value. 86 | NULL_VALUE = 0; 87 | } 88 | 89 | // `ListValue` is a wrapper around a repeated field of values. 90 | // 91 | // The JSON representation for `ListValue` is JSON array. 92 | message ListValue { 93 | // Repeated field of dynamically typed values. 94 | repeated Value values = 1; 95 | } 96 | -------------------------------------------------------------------------------- /src/interceptor/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net" 9 | 10 | "google.golang.org/grpc" 11 | 12 | pb "github.com/jergoo/go-grpc-tutorial/protos/ping" // 引入编译生成的包 13 | ) 14 | 15 | // PingPongServer 实现 pb.PingPongServer 接口 16 | type PingPongServer struct { 17 | pb.UnimplementedPingPongServer // 兼容性需要,避免未实现server接口全部方法 18 | } 19 | 20 | // Ping 单次请求-响应模式 21 | func (s *PingPongServer) Ping(ctx context.Context, req *pb.PingRequest) (*pb.PongResponse, error) { 22 | return &pb.PongResponse{Value: "pong"}, nil 23 | } 24 | 25 | // MultiPong 服务端流模式 26 | func (s *PingPongServer) MultiPong(req *pb.PingRequest, stream pb.PingPong_MultiPongServer) error { 27 | for i := 0; i < 10; i++ { 28 | data := &pb.PongResponse{Value: "pong"} 29 | // 发送消息 30 | err := stream.Send(data) 31 | if err != nil { 32 | return err 33 | } 34 | } 35 | return nil 36 | } 37 | 38 | // MultiPing 客户端流模式 39 | func (s *PingPongServer) MultiPing(stream pb.PingPong_MultiPingServer) error { 40 | msgs := []string{} 41 | for { 42 | // 提前结束接收消息 43 | if len(msgs) > 5 { 44 | return stream.SendAndClose(&pb.PongResponse{Value: "ping enough, max 5"}) 45 | } 46 | 47 | msg, err := stream.Recv() 48 | if err != nil { 49 | // 客户端消息结束,返回响应信息 50 | if err == io.EOF { 51 | return stream.SendAndClose(&pb.PongResponse{Value: fmt.Sprintf("got %d ping", len(msgs))}) 52 | } 53 | return err 54 | } 55 | msgs = append(msgs, msg.Value) 56 | } 57 | } 58 | 59 | // MultiPingPong 双向流模式 60 | func (s *PingPongServer) MultiPingPong(stream pb.PingPong_MultiPingPongServer) error { 61 | msgs := []string{} 62 | for { 63 | // 接收消息 64 | msg, err := stream.Recv() 65 | if err != nil { 66 | if err == io.EOF { 67 | break 68 | } 69 | return err 70 | } 71 | msgs = append(msgs, msg.Value) 72 | 73 | // 每收到两个消息响应一次 74 | if len(msgs)%2 == 0 { 75 | err = stream.Send(&pb.PongResponse{Value: "pong"}) 76 | if err != nil { 77 | return err 78 | } 79 | } 80 | } 81 | return nil 82 | } 83 | 84 | // type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error) 85 | 86 | // 服务端拦截器 - 记录请求和响应日志 87 | func serverUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { 88 | // 前置逻辑 89 | log.Printf("[Server Interceptor] accept request: %s", info.FullMethod) 90 | 91 | // 处理请求 92 | response, err := handler(ctx, req) 93 | 94 | // 后置逻辑 95 | log.Printf("[Server Interceptor] response: %s", response) 96 | 97 | return response, err 98 | } 99 | 100 | // type StreamServerInterceptor func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error 101 | 102 | // 服务端拦截器 - 记录stream请求和响应日志 103 | func serverStreamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 104 | // 前置逻辑 105 | log.Printf("[Server Stream Interceptor] accept request: %s", info.FullMethod) 106 | 107 | // 处理请求,使用自定义 ServerStream 108 | err := handler(srv, &customServerStream{ss}) 109 | return err 110 | } 111 | 112 | type customServerStream struct { 113 | grpc.ServerStream 114 | } 115 | 116 | func (s *customServerStream) SendMsg(m interface{}) error { 117 | log.Printf("[Server Stream Interceptor] send: %T", m) 118 | return s.ServerStream.SendMsg(m) 119 | } 120 | 121 | func (s *customServerStream) RecvMsg(m interface{}) error { 122 | log.Printf("[Server Stream Interceptor] recv: %T", m) 123 | return s.ServerStream.RecvMsg(m) 124 | } 125 | 126 | // 启动server 127 | func main() { 128 | // 以option的方式添加拦截器 129 | opts := []grpc.ServerOption{ 130 | grpc.UnaryInterceptor(serverUnaryInterceptor), 131 | grpc.StreamInterceptor(serverStreamInterceptor), 132 | } 133 | srv := grpc.NewServer(opts...) 134 | 135 | // 注册 PingPongServer 136 | pb.RegisterPingPongServer(srv, &PingPongServer{}) 137 | lis, err := net.Listen("tcp", ":1234") 138 | if err != nil { 139 | log.Fatal(err) 140 | } 141 | log.Println("listen on 1234") 142 | srv.Serve(lis) 143 | } 144 | -------------------------------------------------------------------------------- /archive/docs/c2/hello-grpc.md: -------------------------------------------------------------------------------- 1 | # Hello gRPC 2 | 3 | 按照惯例,这里也从一个Hello项目开始,本项目定义了一个Hello Service,客户端发送包含字符串名字的请求,服务端返回Hello消息。 4 | 5 | **流程:** 6 | 7 | 1. 编写`.proto`描述文件 8 | 2. 编译生成`.pb.go`文件 9 | 3. 服务端实现约定的接口并提供服务 10 | 4. 客户端按照约定调用`.pb.go`文件中的方法请求服务 11 | 12 | **项目结构:** 13 | 14 | ``` 15 | |—— hello/ 16 | |—— client/ 17 | |—— main.go // 客户端 18 | |—— server/ 19 | |—— main.go // 服务端 20 | |—— proto/ 21 | |—— hello/ 22 | |—— hello.proto // proto描述文件 23 | |—— hello.pb.go // proto编译后文件 24 | ``` 25 | 26 | 27 | **Step1:编写描述文件:hello.proto** 28 | 29 | ```protobuf 30 | syntax = "proto3"; // 指定proto版本 31 | package hello; // 指定默认包名 32 | 33 | // 指定golang包名 34 | option go_package = "hello"; 35 | 36 | // 定义Hello服务 37 | service Hello { 38 | // 定义SayHello方法 39 | rpc SayHello(HelloRequest) returns (HelloResponse) {} 40 | } 41 | 42 | // HelloRequest 请求结构 43 | message HelloRequest { 44 | string name = 1; 45 | } 46 | 47 | // HelloResponse 响应结构 48 | message HelloResponse { 49 | string message = 1; 50 | } 51 | ``` 52 | 53 | `hello.proto`文件中定义了一个Hello Service,该服务包含一个`SayHello`方法,同时声明了`HelloRequest`和`HelloResponse`消息结构用于请求和响应。客户端使用`HelloRequest`参数调用`SayHello`方法请求服务端,服务端响应`HelloResponse`消息。一个最简单的服务就定义好了。 54 | 55 | 56 | **Step2:编译生成`.pb.go`文件** 57 | 58 | ```sh 59 | $ cd proto/hello 60 | 61 | # 编译hello.proto 62 | $ protoc -I . --go_out=plugins=grpc:. ./hello.proto 63 | ``` 64 | 在当前目录内生成的`hello.pb.go`文件,按照`.proto`文件中的说明,包含服务端接口`HelloServer`描述,客户端接口及实现`HelloClient`,及`HelloRequest`、`HelloResponse`结构体。 65 | 66 | > 注意:不要手动编辑该文件 67 | 68 | 69 | **Step3:实现服务端接口 `server/main.go`** 70 | 71 | ```go 72 | package main 73 | 74 | import ( 75 | "fmt" 76 | "net" 77 | 78 | pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入编译生成的包 79 | "golang.org/x/net/context" 80 | "google.golang.org/grpc" 81 | "google.golang.org/grpc/grpclog" 82 | ) 83 | 84 | const ( 85 | // Address gRPC服务地址 86 | Address = "127.0.0.1:50052" 87 | ) 88 | 89 | // 定义helloService并实现约定的接口 90 | type helloService struct{} 91 | 92 | // HelloService Hello服务 93 | var HelloService = helloService{} 94 | 95 | // SayHello 实现Hello服务接口 96 | func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { 97 | resp := new(pb.HelloResponse) 98 | resp.Message = fmt.Sprintf("Hello %s.", in.Name) 99 | 100 | return resp, nil 101 | } 102 | 103 | func main() { 104 | listen, err := net.Listen("tcp", Address) 105 | if err != nil { 106 | grpclog.Fatalf("Failed to listen: %v", err) 107 | } 108 | 109 | // 实例化grpc Server 110 | s := grpc.NewServer() 111 | 112 | // 注册HelloService 113 | pb.RegisterHelloServer(s, HelloService) 114 | 115 | grpclog.Println("Listen on " + Address) 116 | s.Serve(listen) 117 | } 118 | 119 | ``` 120 | 121 | 服务端引入编译后的`proto`包,定义一个空结构用于实现约定的接口,接口描述可以查看`hello.pb.go`文件中的`HelloServer`接口描述。实例化grpc Server并注册HelloService,开始提供服务。 122 | 123 | 运行: 124 | 125 | ```sh 126 | $ go run main.go 127 | Listen on 127.0.0.1:50052 //服务端已开启并监听50052端口 128 | ``` 129 | 130 | **Step4:实现客户端调用 `client/main.go`** 131 | 132 | ```go 133 | package main 134 | 135 | import ( 136 | pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入proto包 137 | "golang.org/x/net/context" 138 | "google.golang.org/grpc" 139 | "google.golang.org/grpc/grpclog" 140 | ) 141 | 142 | const ( 143 | // Address gRPC服务地址 144 | Address = "127.0.0.1:50052" 145 | ) 146 | 147 | func main() { 148 | // 连接 149 | conn, err := grpc.Dial(Address, grpc.WithInsecure()) 150 | if err != nil { 151 | grpclog.Fatalln(err) 152 | } 153 | defer conn.Close() 154 | 155 | // 初始化客户端 156 | c := pb.NewHelloClient(conn) 157 | 158 | // 调用方法 159 | req := &pb.HelloRequest{Name: "gRPC"} 160 | res, err := c.SayHello(context.Background(), req) 161 | 162 | if err != nil { 163 | grpclog.Fatalln(err) 164 | } 165 | 166 | grpclog.Println(res.Message) 167 | } 168 | ``` 169 | 170 | 客户端初始化连接后直接调用`hello.pb.go`中实现的`SayHello`方法,即可向服务端发起请求,使用姿势就像调用本地方法一样。 171 | 172 | 173 | 运行: 174 | 175 | ```sh 176 | $ go run main.go 177 | Hello gRPC. // 接收到服务端响应 178 | ``` 179 | 180 | 如果你收到了"Hello gRPC"的回复,恭喜你已经会使用github.com/jergoo/go-grpc-example/proto/hello了。 181 | 182 | > 建议到这里仔细看一看hello.pb.go文件中的内容,对比hello.proto文件,理解protobuf中的定义转换为golang后的结构。 183 | -------------------------------------------------------------------------------- /src/protos/google/protobuf/wrappers.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | // Wrappers for primitive (non-message) types. These types are useful 32 | // for embedding primitives in the `google.protobuf.Any` type and for places 33 | // where we need to distinguish between the absence of a primitive 34 | // typed field and its default value. 35 | // 36 | // These wrappers have no meaningful use within repeated fields as they lack 37 | // the ability to detect presence on individual elements. 38 | // These wrappers have no meaningful use within a map or a oneof since 39 | // individual entries of a map or fields of a oneof can already detect presence. 40 | 41 | syntax = "proto3"; 42 | 43 | package google.protobuf; 44 | 45 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 46 | option cc_enable_arenas = true; 47 | option go_package = "google.golang.org/protobuf/types/known/wrapperspb"; 48 | option java_package = "com.google.protobuf"; 49 | option java_outer_classname = "WrappersProto"; 50 | option java_multiple_files = true; 51 | option objc_class_prefix = "GPB"; 52 | 53 | // Wrapper message for `double`. 54 | // 55 | // The JSON representation for `DoubleValue` is JSON number. 56 | message DoubleValue { 57 | // The double value. 58 | double value = 1; 59 | } 60 | 61 | // Wrapper message for `float`. 62 | // 63 | // The JSON representation for `FloatValue` is JSON number. 64 | message FloatValue { 65 | // The float value. 66 | float value = 1; 67 | } 68 | 69 | // Wrapper message for `int64`. 70 | // 71 | // The JSON representation for `Int64Value` is JSON string. 72 | message Int64Value { 73 | // The int64 value. 74 | int64 value = 1; 75 | } 76 | 77 | // Wrapper message for `uint64`. 78 | // 79 | // The JSON representation for `UInt64Value` is JSON string. 80 | message UInt64Value { 81 | // The uint64 value. 82 | uint64 value = 1; 83 | } 84 | 85 | // Wrapper message for `int32`. 86 | // 87 | // The JSON representation for `Int32Value` is JSON number. 88 | message Int32Value { 89 | // The int32 value. 90 | int32 value = 1; 91 | } 92 | 93 | // Wrapper message for `uint32`. 94 | // 95 | // The JSON representation for `UInt32Value` is JSON number. 96 | message UInt32Value { 97 | // The uint32 value. 98 | uint32 value = 1; 99 | } 100 | 101 | // Wrapper message for `bool`. 102 | // 103 | // The JSON representation for `BoolValue` is JSON `true` and `false`. 104 | message BoolValue { 105 | // The bool value. 106 | bool value = 1; 107 | } 108 | 109 | // Wrapper message for `string`. 110 | // 111 | // The JSON representation for `StringValue` is JSON string. 112 | message StringValue { 113 | // The string value. 114 | string value = 1; 115 | } 116 | 117 | // Wrapper message for `bytes`. 118 | // 119 | // The JSON representation for `BytesValue` is JSON string. 120 | message BytesValue { 121 | // The bytes value. 122 | bytes value = 1; 123 | } 124 | -------------------------------------------------------------------------------- /docs/assets/bar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | bar 4 | bar,column,cylinder,percent,progress 5 | cc-by 6 | p3ejy0 7 | 50 | -------------------------------------------------------------------------------- /src/docs/assets/bar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | bar 4 | bar,column,cylinder,percent,progress 5 | cc-by 6 | p3ejy0 7 | 50 | -------------------------------------------------------------------------------- /archive/docs/_media/bar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | bar 4 | bar,column,cylinder,percent,progress 5 | cc-by 6 | p3ejy0 7 | 50 | -------------------------------------------------------------------------------- /docs/fonts/SOURCE-CODE-PRO-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /docs/css/general.css: -------------------------------------------------------------------------------- 1 | /* Base styles and content styles */ 2 | 3 | @import 'variables.css'; 4 | 5 | :root { 6 | /* Browser default font-size is 16px, this way 1 rem = 10px */ 7 | font-size: 62.5%; 8 | } 9 | 10 | html { 11 | font-family: "Open Sans", sans-serif; 12 | color: var(--fg); 13 | background-color: var(--bg); 14 | text-size-adjust: none; 15 | -webkit-text-size-adjust: none; 16 | } 17 | 18 | body { 19 | margin: 0; 20 | font-size: 1.6rem; 21 | overflow-x: hidden; 22 | } 23 | 24 | code { 25 | font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important; 26 | font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */ 27 | } 28 | 29 | /* make long words/inline code not x overflow */ 30 | main { 31 | overflow-wrap: break-word; 32 | } 33 | 34 | /* make wide tables scroll if they overflow */ 35 | .table-wrapper { 36 | overflow-x: auto; 37 | } 38 | 39 | /* Don't change font size in headers. */ 40 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 41 | font-size: unset; 42 | } 43 | 44 | .left { float: left; } 45 | .right { float: right; } 46 | .boring { opacity: 0.6; } 47 | .hide-boring .boring { display: none; } 48 | .hidden { display: none !important; } 49 | 50 | h2, h3 { margin-top: 2.5em; } 51 | h4, h5 { margin-top: 2em; } 52 | 53 | .header + .header h3, 54 | .header + .header h4, 55 | .header + .header h5 { 56 | margin-top: 1em; 57 | } 58 | 59 | h1:target::before, 60 | h2:target::before, 61 | h3:target::before, 62 | h4:target::before, 63 | h5:target::before, 64 | h6:target::before { 65 | display: inline-block; 66 | content: "»"; 67 | margin-left: -30px; 68 | width: 30px; 69 | } 70 | 71 | /* This is broken on Safari as of version 14, but is fixed 72 | in Safari Technology Preview 117 which I think will be Safari 14.2. 73 | https://bugs.webkit.org/show_bug.cgi?id=218076 74 | */ 75 | :target { 76 | scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); 77 | } 78 | 79 | .page { 80 | outline: 0; 81 | padding: 0 var(--page-padding); 82 | margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ 83 | } 84 | .page-wrapper { 85 | box-sizing: border-box; 86 | } 87 | .js:not(.sidebar-resizing) .page-wrapper { 88 | transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ 89 | } 90 | 91 | .content { 92 | overflow-y: auto; 93 | padding: 0 5px 50px 5px; 94 | } 95 | .content main { 96 | margin-left: auto; 97 | margin-right: auto; 98 | max-width: var(--content-max-width); 99 | } 100 | .content p { line-height: 1.45em; } 101 | .content ol { line-height: 1.45em; } 102 | .content ul { line-height: 1.45em; } 103 | .content a { text-decoration: none; } 104 | .content a:hover { text-decoration: underline; } 105 | .content img, .content video { max-width: 100%; } 106 | .content .header:link, 107 | .content .header:visited { 108 | color: var(--fg); 109 | } 110 | .content .header:link, 111 | .content .header:visited:hover { 112 | text-decoration: none; 113 | } 114 | 115 | table { 116 | margin: 0 auto; 117 | border-collapse: collapse; 118 | } 119 | table td { 120 | padding: 3px 20px; 121 | border: 1px var(--table-border-color) solid; 122 | } 123 | table thead { 124 | background: var(--table-header-bg); 125 | } 126 | table thead td { 127 | font-weight: 700; 128 | border: none; 129 | } 130 | table thead th { 131 | padding: 3px 20px; 132 | } 133 | table thead tr { 134 | border: 1px var(--table-header-bg) solid; 135 | } 136 | /* Alternate background colors for rows */ 137 | table tbody tr:nth-child(2n) { 138 | background: var(--table-alternate-bg); 139 | } 140 | 141 | 142 | blockquote { 143 | margin: 20px 0; 144 | padding: 0 20px; 145 | color: var(--fg); 146 | background-color: var(--quote-bg); 147 | border-top: .1em solid var(--quote-border); 148 | border-bottom: .1em solid var(--quote-border); 149 | } 150 | 151 | 152 | :not(.footnote-definition) + .footnote-definition, 153 | .footnote-definition + :not(.footnote-definition) { 154 | margin-top: 2em; 155 | } 156 | .footnote-definition { 157 | font-size: 0.9em; 158 | margin: 0.5em 0; 159 | } 160 | .footnote-definition p { 161 | display: inline; 162 | } 163 | 164 | .tooltiptext { 165 | position: absolute; 166 | visibility: hidden; 167 | color: #fff; 168 | background-color: #333; 169 | transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ 170 | left: -8px; /* Half of the width of the icon */ 171 | top: -35px; 172 | font-size: 0.8em; 173 | text-align: center; 174 | border-radius: 6px; 175 | padding: 5px 8px; 176 | margin: 5px; 177 | z-index: 1000; 178 | } 179 | .tooltipped .tooltiptext { 180 | visibility: visible; 181 | } 182 | 183 | .chapter li.part-title { 184 | color: var(--sidebar-fg); 185 | margin: 5px 0px; 186 | font-weight: bold; 187 | } 188 | 189 | .result-no-output { 190 | font-style: italic; 191 | } 192 | -------------------------------------------------------------------------------- /src/interceptor/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "log" 7 | "time" 8 | 9 | "google.golang.org/grpc" 10 | 11 | pb "github.com/jergoo/go-grpc-tutorial/protos/ping" // 引入编译生成的包 12 | ) 13 | 14 | // Ping 单次请求-响应模式 15 | func Ping() { 16 | // 以option方式添加拦截器 17 | conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure(), grpc.WithUnaryInterceptor(clientUnaryInterceptor)) 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | defer conn.Close() 22 | 23 | // 实例化客户端并调用 24 | client := pb.NewPingPongClient(conn) 25 | res, err := client.Ping(context.Background(), &pb.PingRequest{Value: "ping"}) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | log.Println(res.Value) 30 | } 31 | 32 | // MultiPong 服务端流模式 33 | func MultiPong() { 34 | conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure()) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | defer conn.Close() 39 | 40 | // 实例化客户端并调用 41 | client := pb.NewPingPongClient(conn) 42 | stream, err := client.MultiPong(context.Background(), &pb.PingRequest{Value: "ping"}) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | // 循环接收数据流 48 | for { 49 | msg, err := stream.Recv() 50 | if err != nil { 51 | if err == io.EOF { 52 | break 53 | } 54 | log.Fatal(err) 55 | } 56 | log.Println(msg.Value) 57 | } 58 | } 59 | 60 | // MultiPing 客户端流模式 61 | func MultiPing() { 62 | conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure()) 63 | if err != nil { 64 | log.Fatal(err) 65 | } 66 | defer conn.Close() 67 | 68 | // 实例化客户端并调用 69 | client := pb.NewPingPongClient(conn) 70 | stream, err := client.MultiPing(context.Background()) 71 | if err != nil { 72 | log.Fatal(err) 73 | } 74 | 75 | // 发送数据 76 | for i := 0; i < 6; i++ { 77 | data := &pb.PingRequest{Value: "ping"} 78 | err = stream.Send(data) 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | } 83 | 84 | // 发送结束并获取服务端响应 85 | res, err := stream.CloseAndRecv() 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | 90 | log.Println(res.Value) 91 | } 92 | 93 | // MultiPingPong 双向流模式 94 | func MultiPingPong() { 95 | opts := []grpc.DialOption{ 96 | grpc.WithInsecure(), 97 | grpc.WithStreamInterceptor(clientStreamInterceptor), 98 | } 99 | conn, err := grpc.Dial("localhost:1234", opts...) 100 | if err != nil { 101 | log.Fatal(err) 102 | } 103 | defer conn.Close() 104 | 105 | // 实例化客户端并调用 106 | client := pb.NewPingPongClient(conn) 107 | stream, err := client.MultiPingPong(context.Background()) 108 | if err != nil { 109 | log.Fatal(err) 110 | } 111 | 112 | // 在另一个goroutine中接收数据 113 | c := make(chan struct{}) 114 | go func(stream pb.PingPong_MultiPingPongClient, c chan struct{}) { 115 | for { 116 | msg, err := stream.Recv() 117 | if err != nil { 118 | if err == io.EOF { 119 | break 120 | } 121 | log.Fatal(err) 122 | } 123 | log.Printf("recv:%s\n", msg.Value) 124 | } 125 | c <- struct{}{} 126 | }(stream, c) 127 | 128 | // 发送数据 129 | for i := 0; i < 2; i++ { 130 | data := &pb.PingRequest{Value: "ping"} 131 | err = stream.Send(data) 132 | if err != nil { 133 | log.Fatal(err) 134 | } 135 | log.Printf("send:%s\n", data.Value) 136 | time.Sleep(500 * time.Millisecond) 137 | } 138 | 139 | // 结束发送 140 | stream.CloseSend() 141 | // 等待接收完成 142 | <-c 143 | } 144 | 145 | // type UnaryClientInterceptor func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error 146 | 147 | // 客户端拦截器 - 记录请求和响应日志 148 | func clientUnaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { 149 | // 前置逻辑 150 | log.Printf("[Client Interceptor] send request: %s", method) 151 | 152 | // 发起请求 153 | err := invoker(ctx, method, req, reply, cc, opts...) 154 | 155 | // 后置逻辑 156 | log.Printf("[Client Interceptor] response: %s", reply) 157 | 158 | return err 159 | } 160 | 161 | // type StreamClientInterceptor func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, streamer Streamer, opts ...CallOption) (ClientStream, error) 162 | 163 | // 客户端拦截器 - 记录stream请求和响应日志 164 | func clientStreamInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { 165 | // 前置逻辑 166 | log.Printf("[Client Stream Interceptor] send request: %s", method) 167 | 168 | // 请求 169 | s, err := streamer(ctx, desc, cc, method, opts...) 170 | if err != nil { 171 | return nil, err 172 | } 173 | 174 | // 自定义类型包装 ClientStream 175 | return &customClientStream{s}, nil 176 | } 177 | 178 | type customClientStream struct { 179 | grpc.ClientStream 180 | } 181 | 182 | func (s *customClientStream) SendMsg(m interface{}) error { 183 | log.Printf("[Client Stream Interceptor] send: %T", m) 184 | return s.ClientStream.SendMsg(m) 185 | } 186 | 187 | func (s *customClientStream) RecvMsg(m interface{}) error { 188 | time.Sleep(1000 * time.Millisecond) 189 | log.Printf("[Client Stream Interceptor] recv: %T", m) 190 | return s.ClientStream.RecvMsg(m) 191 | } 192 | -------------------------------------------------------------------------------- /src/protos/google/protobuf/duration.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "google.golang.org/protobuf/types/known/durationpb"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "DurationProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // A Duration represents a signed, fixed-length span of time represented 44 | // as a count of seconds and fractions of seconds at nanosecond 45 | // resolution. It is independent of any calendar and concepts like "day" 46 | // or "month". It is related to Timestamp in that the difference between 47 | // two Timestamp values is a Duration and it can be added or subtracted 48 | // from a Timestamp. Range is approximately +-10,000 years. 49 | // 50 | // # Examples 51 | // 52 | // Example 1: Compute Duration from two Timestamps in pseudo code. 53 | // 54 | // Timestamp start = ...; 55 | // Timestamp end = ...; 56 | // Duration duration = ...; 57 | // 58 | // duration.seconds = end.seconds - start.seconds; 59 | // duration.nanos = end.nanos - start.nanos; 60 | // 61 | // if (duration.seconds < 0 && duration.nanos > 0) { 62 | // duration.seconds += 1; 63 | // duration.nanos -= 1000000000; 64 | // } else if (duration.seconds > 0 && duration.nanos < 0) { 65 | // duration.seconds -= 1; 66 | // duration.nanos += 1000000000; 67 | // } 68 | // 69 | // Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. 70 | // 71 | // Timestamp start = ...; 72 | // Duration duration = ...; 73 | // Timestamp end = ...; 74 | // 75 | // end.seconds = start.seconds + duration.seconds; 76 | // end.nanos = start.nanos + duration.nanos; 77 | // 78 | // if (end.nanos < 0) { 79 | // end.seconds -= 1; 80 | // end.nanos += 1000000000; 81 | // } else if (end.nanos >= 1000000000) { 82 | // end.seconds += 1; 83 | // end.nanos -= 1000000000; 84 | // } 85 | // 86 | // Example 3: Compute Duration from datetime.timedelta in Python. 87 | // 88 | // td = datetime.timedelta(days=3, minutes=10) 89 | // duration = Duration() 90 | // duration.FromTimedelta(td) 91 | // 92 | // # JSON Mapping 93 | // 94 | // In JSON format, the Duration type is encoded as a string rather than an 95 | // object, where the string ends in the suffix "s" (indicating seconds) and 96 | // is preceded by the number of seconds, with nanoseconds expressed as 97 | // fractional seconds. For example, 3 seconds with 0 nanoseconds should be 98 | // encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should 99 | // be expressed in JSON format as "3.000000001s", and 3 seconds and 1 100 | // microsecond should be expressed in JSON format as "3.000001s". 101 | // 102 | // 103 | message Duration { 104 | // Signed seconds of the span of time. Must be from -315,576,000,000 105 | // to +315,576,000,000 inclusive. Note: these bounds are computed from: 106 | // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years 107 | int64 seconds = 1; 108 | 109 | // Signed fractions of a second at nanosecond resolution of the span 110 | // of time. Durations less than one second are represented with a 0 111 | // `seconds` field and a positive or negative `nanos` field. For durations 112 | // of one second or more, a non-zero value for the `nanos` field must be 113 | // of the same sign as the `seconds` field. Must be from -999,999,999 114 | // to +999,999,999 inclusive. 115 | int32 nanos = 2; 116 | } 117 | -------------------------------------------------------------------------------- /src/docs/advance/auth.md: -------------------------------------------------------------------------------- 1 | # 安全认证 2 | 3 | --- 4 | 5 | 服务开发中需要考虑服务的安全性,如连接是否加密,用户请求是否有权限等,gRPC 支持基于 TLS 的认证保证连接的安全性,通知也支持基于 token 的认证方式,用于对用户做权限认证。 6 | 7 | **源码目录:** 8 | 9 | ``` 10 | |—- src/ 11 | |-- auth/ 12 | |-- keys/ // 证书目录 13 | |—— client.go // 客户端 14 | |—— server.go // 服务端 15 | |—- protos/ping/ 16 | |—— ping.proto // protobuf描述文件 17 | |—— ping.pb.go // protoc编译生成 18 | |-- ping_grpc.pb.go // protoc编译生成 19 | ``` 20 | 21 | 22 | ## TLS认证 23 | 24 | 首先需要准备服务端证书,在 keys 目录存放证书文件。 25 | 26 | ```sh 27 | $ cd src/auth/keys 28 | # 生成证书,-config 替换为对应系统的openssl配置文件目录 29 | $ openssl req -newkey rsa:2048 -x509 -nodes -sha256 -days 3650 \ 30 | -keyout server.key -new -out server.crt \ 31 | -subj /CN=grpc.server -reqexts SAN -extensions SAN \ 32 | -config <(cat /System/Library/OpenSSL/openssl.cnf \ 33 | <(printf '[SAN]\nsubjectAltName=DNS:grpc.server')) 34 | ``` 35 | 36 | 生成证书文件后,我们就可以在服务端通过 `ServerOption` 开启 TLS 了,示例如下: 37 | 38 | ```go 39 | // src/auth/server.go 40 | 41 | func main() { 42 | creds, err := credentials.NewServerTLSFromFile("keys/server.crt", "keys/server.key") 43 | if err != nil { 44 | log.Fatalf("load crt fail:%v", err) 45 | } 46 | opts := []grpc.ServerOption{ 47 | grpc.Creds(creds), 48 | } 49 | 50 | srv := grpc.NewServer(opts...) 51 | 52 | ... 53 | ``` 54 | 55 | 类似的,在客户端使用 `DialOption` 开启 TLS,示例如下: 56 | 57 | ```golang 58 | func Ping() { 59 | // 读取服务端证书,并制定对应服务名 60 | cred, err := credentials.NewClientTLSFromFile("keys/server.crt", "go-grpc-tutorial") 61 | if err != nil { 62 | log.Fatalf("load crt fail: %v", err) 63 | } 64 | 65 | // 连接配置 66 | opts := []grpc.DialOption{ 67 | grpc.WithTransportCredentials(cred), 68 | } 69 | conn, err := grpc.Dial("localhost:1234", opts...) 70 | if err != nil { 71 | log.Fatal(err) 72 | } 73 | 74 | ... 75 | ``` 76 | 77 | ## Token 认证 78 | 79 | TLS 认证是针对连接的安全加密方式,实际应用中还需要针对每个用户请求进行认证,常用的方式就是基于 token 认证,gRPC 使用 `grpc.PerRPCCredentials` 接口对此提供了支持。 80 | 81 | ```go 82 | // PerRPCCredentials defines the common interface for the credentials which need to 83 | // attach security information to every RPC (e.g., oauth2). 84 | type PerRPCCredentials interface { 85 | // GetRequestMetadata gets the current request metadata, refreshing 86 | // tokens if required. This should be called by the transport layer on 87 | // each request, and the data should be populated in headers or other 88 | // context. If a status code is returned, it will be used as the status 89 | // for the RPC. uri is the URI of the entry point for the request. 90 | // When supported by the underlying implementation, ctx can be used for 91 | // timeout and cancellation. Additionally, RequestInfo data will be 92 | // available via ctx to this call. 93 | // TODO(zhaoq): Define the set of the qualified keys instead of leaving 94 | // it as an arbitrary string. 95 | GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) 96 | // RequireTransportSecurity indicates whether the credentials requires 97 | // transport security. 98 | RequireTransportSecurity() bool 99 | } 100 | 101 | ``` 102 | 103 | 客户端示例如下: 104 | 105 | ```go 106 | // src/auth/client.go 107 | 108 | func Ping() { 109 | // 增加认证 Dial Option 110 | conn, err := grpc.Dial("localhost:1234", grpc.WithPerRPCCredentials(CustomAuth{Token: "1234567890"})) 111 | if err != nil { 112 | log.Fatal(err) 113 | } 114 | defer conn.Close() 115 | 116 | // 实例化客户端并调用 117 | client := pb.NewPingPongClient(conn) 118 | res, err := client.Ping(context.Background(), &pb.PingRequest{Value: "ping"}) 119 | if err != nil { 120 | log.Fatal(err) 121 | } 122 | log.Println(res.Value) 123 | } 124 | 125 | // CustomAuth 自定义认证类型 126 | type CustomAuth struct { 127 | Token string 128 | } 129 | 130 | // GetRequestMetadata 生成认证信息 131 | func (a CustomAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 132 | return map[string]string{ 133 | "authorization": a.Token, 134 | }, nil 135 | } 136 | 137 | // RequireTransportSecurity 是否开启 TLS 138 | func (a CustomAuth) RequireTransportSecurity() bool { 139 | return false 140 | } 141 | ``` 142 | 143 | 这里定义了一个 `CustomAuth` 类型,并实现了 `grpc.PerRPCCredentials` 接口的两个方法,通过 `grpc.WithPerRPCCredentials` 方法转换为 `DialOption` 类型初始化连接,这样每次 rpc 调用时 token 信息会通过请求的metadata 传输到服务端。 144 | 145 | 既然是通过 metadata 传输 token 信息,那么服务端认证就非常简单了,可以实现一个拦截器统一处理请求中的 token,示例如下: 146 | 147 | ```golang 148 | // src/auth/server.go 149 | ... 150 | 151 | // 服务端拦截器 - token 认证 152 | func authInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { 153 | md, ok := metadata.FromIncomingContext(ctx) 154 | if !ok { 155 | return nil, errors.New("authorization missing") 156 | } 157 | 158 | var token string 159 | if auth, ok := md["authorization"]; ok { 160 | token = auth[0] 161 | } 162 | if token != "1234567890" { 163 | return nil, grpc.Errorf(codes.Unauthenticated, "token invalid") 164 | } 165 | 166 | // 处理请求 167 | return handler(ctx, req) 168 | } 169 | 170 | // 启动server 171 | func main() { 172 | srv := grpc.NewServer(grpc.UnaryInterceptor(authInterceptor)) 173 | 174 | ... 175 | ``` 176 | 177 | 以上就是基于 token 的认证方法,还是比较简单的,实际应用中可以根据自己的业务需求生成不同类型的 token,`google.golang.org/grpc/credentials/oauth`包也对 oauth2 和 jwt 提供了支持,感兴趣可以看一下 `oauth.NewOauthAccess(token *oauth2.Token)` 和 `oauth.NewJWTAccessFromKey(jsonKey []byte)` 方法,实际上也是实现了 `grpc.PerRPCCredentials` 接口。 178 | 179 | --- 180 | -------------------------------------------------------------------------------- /archive/src/proto/hello/hello.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: hello/hello.proto 3 | 4 | /* 5 | Package hello is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | hello/hello.proto 9 | 10 | It has these top-level messages: 11 | HelloRequest 12 | HelloResponse 13 | */ 14 | package hello 15 | 16 | import proto "github.com/golang/protobuf/proto" 17 | import fmt "fmt" 18 | import math "math" 19 | 20 | import ( 21 | context "golang.org/x/net/context" 22 | grpc "google.golang.org/grpc" 23 | ) 24 | 25 | // Reference imports to suppress errors if they are not otherwise used. 26 | var _ = proto.Marshal 27 | var _ = fmt.Errorf 28 | var _ = math.Inf 29 | 30 | // This is a compile-time assertion to ensure that this generated file 31 | // is compatible with the proto package it is being compiled against. 32 | // A compilation error at this line likely means your copy of the 33 | // proto package needs to be updated. 34 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 35 | 36 | // HelloRequest 请求结构 37 | type HelloRequest struct { 38 | Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` 39 | } 40 | 41 | func (m *HelloRequest) Reset() { *m = HelloRequest{} } 42 | func (m *HelloRequest) String() string { return proto.CompactTextString(m) } 43 | func (*HelloRequest) ProtoMessage() {} 44 | func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 45 | 46 | func (m *HelloRequest) GetName() string { 47 | if m != nil { 48 | return m.Name 49 | } 50 | return "" 51 | } 52 | 53 | // HelloResponse 响应结构 54 | type HelloResponse struct { 55 | Message string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"` 56 | } 57 | 58 | func (m *HelloResponse) Reset() { *m = HelloResponse{} } 59 | func (m *HelloResponse) String() string { return proto.CompactTextString(m) } 60 | func (*HelloResponse) ProtoMessage() {} 61 | func (*HelloResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 62 | 63 | func (m *HelloResponse) GetMessage() string { 64 | if m != nil { 65 | return m.Message 66 | } 67 | return "" 68 | } 69 | 70 | func init() { 71 | proto.RegisterType((*HelloRequest)(nil), "hello.HelloRequest") 72 | proto.RegisterType((*HelloResponse)(nil), "hello.HelloResponse") 73 | } 74 | 75 | // Reference imports to suppress errors if they are not otherwise used. 76 | var _ context.Context 77 | var _ grpc.ClientConn 78 | 79 | // This is a compile-time assertion to ensure that this generated file 80 | // is compatible with the grpc package it is being compiled against. 81 | const _ = grpc.SupportPackageIsVersion4 82 | 83 | // Client API for Hello service 84 | 85 | type HelloClient interface { 86 | // 定义SayHello方法 87 | SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) 88 | } 89 | 90 | type helloClient struct { 91 | cc *grpc.ClientConn 92 | } 93 | 94 | func NewHelloClient(cc *grpc.ClientConn) HelloClient { 95 | return &helloClient{cc} 96 | } 97 | 98 | func (c *helloClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) { 99 | out := new(HelloResponse) 100 | err := grpc.Invoke(ctx, "/hello.Hello/SayHello", in, out, c.cc, opts...) 101 | if err != nil { 102 | return nil, err 103 | } 104 | return out, nil 105 | } 106 | 107 | // Server API for Hello service 108 | 109 | type HelloServer interface { 110 | // 定义SayHello方法 111 | SayHello(context.Context, *HelloRequest) (*HelloResponse, error) 112 | } 113 | 114 | func RegisterHelloServer(s *grpc.Server, srv HelloServer) { 115 | s.RegisterService(&_Hello_serviceDesc, srv) 116 | } 117 | 118 | func _Hello_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 119 | in := new(HelloRequest) 120 | if err := dec(in); err != nil { 121 | return nil, err 122 | } 123 | if interceptor == nil { 124 | return srv.(HelloServer).SayHello(ctx, in) 125 | } 126 | info := &grpc.UnaryServerInfo{ 127 | Server: srv, 128 | FullMethod: "/hello.Hello/SayHello", 129 | } 130 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 131 | return srv.(HelloServer).SayHello(ctx, req.(*HelloRequest)) 132 | } 133 | return interceptor(ctx, in, info, handler) 134 | } 135 | 136 | var _Hello_serviceDesc = grpc.ServiceDesc{ 137 | ServiceName: "hello.Hello", 138 | HandlerType: (*HelloServer)(nil), 139 | Methods: []grpc.MethodDesc{ 140 | { 141 | MethodName: "SayHello", 142 | Handler: _Hello_SayHello_Handler, 143 | }, 144 | }, 145 | Streams: []grpc.StreamDesc{}, 146 | Metadata: "hello/hello.proto", 147 | } 148 | 149 | func init() { proto.RegisterFile("hello/hello.proto", fileDescriptor0) } 150 | 151 | var fileDescriptor0 = []byte{ 152 | // 139 bytes of a gzipped FileDescriptorProto 153 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xcc, 0x48, 0xcd, 0xc9, 154 | 0xc9, 0xd7, 0x07, 0x93, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0xac, 0x60, 0x8e, 0x92, 0x12, 155 | 0x17, 0x8f, 0x07, 0x88, 0x11, 0x94, 0x5a, 0x58, 0x9a, 0x5a, 0x5c, 0x22, 0x24, 0xc4, 0xc5, 0x92, 156 | 0x97, 0x98, 0x9b, 0x2a, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x19, 0x04, 0x66, 0x2b, 0x69, 0x72, 0xf1, 157 | 0x42, 0xd5, 0x14, 0x17, 0xe4, 0xe7, 0x15, 0xa7, 0x0a, 0x49, 0x70, 0xb1, 0xe7, 0xa6, 0x16, 0x17, 158 | 0x27, 0xa6, 0xc3, 0xd4, 0xc1, 0xb8, 0x46, 0x0e, 0x5c, 0xac, 0x60, 0xa5, 0x42, 0xe6, 0x5c, 0x1c, 159 | 0xc1, 0x89, 0x95, 0x10, 0xb6, 0xb0, 0x1e, 0xc4, 0x62, 0x64, 0x8b, 0xa4, 0x44, 0x50, 0x05, 0x21, 160 | 0x26, 0x2b, 0x31, 0x38, 0xb1, 0x47, 0x41, 0x5c, 0x96, 0xc4, 0x06, 0x76, 0xa7, 0x31, 0x20, 0x00, 161 | 0x00, 0xff, 0xff, 0xc5, 0xbb, 0x84, 0x66, 0xbc, 0x00, 0x00, 0x00, 162 | } 163 | -------------------------------------------------------------------------------- /archive/docs/c2/interceptor.md: -------------------------------------------------------------------------------- 1 | # Interceptor 拦截器 2 | 3 | grpc服务端和客户端都提供了interceptor功能,功能类似middleware,很适合在这里处理验证、日志等流程。 4 | 5 | 在自定义Token认证的示例中,认证信息是由每个服务中的方法处理并认证的,如果有大量的接口方法,这种姿势就太不优雅了,每个接口实现都要先处理认证信息。这个时候interceptor就可以用来解决了这个问题,在请求被转到具体接口之前处理认证信息,一处认证,到处无忧。 6 | 在客户端,我们增加一个请求日志,记录请求相关的参数和耗时等等。修改hello_token项目实现: 7 | 8 | 9 | ## 目录结构 10 | 11 | ``` 12 | |—— hello_interceptor/ 13 | |—— client/ 14 | |—— main.go // 客户端 15 | |—— server/ 16 | |—— main.go // 服务端 17 | |—— keys/ // 证书目录 18 | |—— server.key 19 | |—— server.pem 20 | |—— proto/ 21 | |—— hello/ 22 | |—— hello.proto // proto描述文件 23 | |—— hello.pb.go // proto编译后文件 24 | ``` 25 | 26 | ## 示例代码 27 | 28 | **Step 1. 服务端interceptor:** 29 | > hello_interceptor/server/main.go 30 | 31 | ```go 32 | package main 33 | 34 | import ( 35 | "fmt" 36 | "net" 37 | 38 | pb "github.com/jergoo/go-grpc-example/proto/hello" 39 | 40 | "golang.org/x/net/context" 41 | "google.golang.org/grpc" 42 | "google.golang.org/grpc/codes" // grpc 响应状态码 43 | "google.golang.org/grpc/credentials" // grpc认证包 44 | "google.golang.org/grpc/grpclog" 45 | "google.golang.org/grpc/metadata" // grpc metadata包 46 | ) 47 | 48 | const ( 49 | // Address gRPC服务地址 50 | Address = "127.0.0.1:50052" 51 | ) 52 | 53 | // 定义helloService并实现约定的接口 54 | type helloService struct{} 55 | 56 | // HelloService Hello服务 57 | var HelloService = helloService{} 58 | 59 | // SayHello 实现Hello服务接口 60 | func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { 61 | resp := new(pb.HelloResponse) 62 | resp.Message = fmt.Sprintf("Hello %s.", in.Name) 63 | 64 | return resp, nil 65 | } 66 | 67 | func main() { 68 | listen, err := net.Listen("tcp", Address) 69 | if err != nil { 70 | grpclog.Fatalf("Failed to listen: %v", err) 71 | } 72 | 73 | var opts []grpc.ServerOption 74 | 75 | // TLS认证 76 | creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem", "../../keys/server.key") 77 | if err != nil { 78 | grpclog.Fatalf("Failed to generate credentials %v", err) 79 | } 80 | 81 | opts = append(opts, grpc.Creds(creds)) 82 | 83 | // 注册interceptor 84 | opts = append(opts, grpc.UnaryInterceptor(interceptor)) 85 | 86 | // 实例化grpc Server 87 | s := grpc.NewServer(opts...) 88 | 89 | // 注册HelloService 90 | pb.RegisterHelloServer(s, HelloService) 91 | 92 | grpclog.Println("Listen on " + Address + " with TLS + Token + Interceptor") 93 | 94 | s.Serve(listen) 95 | } 96 | 97 | // auth 验证Token 98 | func auth(ctx context.Context) error { 99 | md, ok := metadata.FromContext(ctx) 100 | if !ok { 101 | return grpc.Errorf(codes.Unauthenticated, "无Token认证信息") 102 | } 103 | 104 | var ( 105 | appid string 106 | appkey string 107 | ) 108 | 109 | if val, ok := md["appid"]; ok { 110 | appid = val[0] 111 | } 112 | 113 | if val, ok := md["appkey"]; ok { 114 | appkey = val[0] 115 | } 116 | 117 | if appid != "101010" || appkey != "i am key" { 118 | return grpc.Errorf(codes.Unauthenticated, "Token认证信息无效: appid=%s, appkey=%s", appid, appkey) 119 | } 120 | 121 | return nil 122 | } 123 | 124 | // interceptor 拦截器 125 | func interceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 126 | err := auth(ctx) 127 | if err != nil { 128 | return nil, err 129 | } 130 | // 继续处理请求 131 | return handler(ctx, req) 132 | } 133 | ``` 134 | 135 | **Step 2. 实现客户端interceptor:** 136 | 137 | > hello_intercepror/client/main.go 138 | 139 | ```golang 140 | package main 141 | 142 | import ( 143 | "time" 144 | 145 | pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入proto包 146 | 147 | "golang.org/x/net/context" 148 | "google.golang.org/grpc" 149 | "google.golang.org/grpc/credentials" // 引入grpc认证包 150 | "google.golang.org/grpc/grpclog" 151 | ) 152 | 153 | const ( 154 | // Address gRPC服务地址 155 | Address = "127.0.0.1:50052" 156 | 157 | // OpenTLS 是否开启TLS认证 158 | OpenTLS = true 159 | ) 160 | 161 | // customCredential 自定义认证 162 | type customCredential struct{} 163 | 164 | // GetRequestMetadata 实现自定义认证接口 165 | func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 166 | return map[string]string{ 167 | "appid": "101010", 168 | "appkey": "i am key", 169 | }, nil 170 | } 171 | 172 | // RequireTransportSecurity 自定义认证是否开启TLS 173 | func (c customCredential) RequireTransportSecurity() bool { 174 | return OpenTLS 175 | } 176 | 177 | func main() { 178 | var err error 179 | var opts []grpc.DialOption 180 | 181 | if OpenTLS { 182 | // TLS连接 183 | creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "server name") 184 | if err != nil { 185 | grpclog.Fatalf("Failed to create TLS credentials %v", err) 186 | } 187 | opts = append(opts, grpc.WithTransportCredentials(creds)) 188 | } else { 189 | opts = append(opts, grpc.WithInsecure()) 190 | } 191 | 192 | // 指定自定义认证 193 | opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential))) 194 | // 指定客户端interceptor 195 | opts = append(opts, grpc.WithUnaryInterceptor(interceptor)) 196 | 197 | conn, err := grpc.Dial(Address, opts...) 198 | if err != nil { 199 | grpclog.Fatalln(err) 200 | } 201 | defer conn.Close() 202 | 203 | // 初始化客户端 204 | c := pb.NewHelloClient(conn) 205 | 206 | // 调用方法 207 | req := &pb.HelloRequest{Name: "gRPC"} 208 | res, err := c.SayHello(context.Background(), req) 209 | if err != nil { 210 | grpclog.Fatalln(err) 211 | } 212 | 213 | grpclog.Println(res.Message) 214 | } 215 | 216 | // interceptor 客户端拦截器 217 | func interceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { 218 | start := time.Now() 219 | err := invoker(ctx, method, req, reply, cc, opts...) 220 | grpclog.Printf("method=%s req=%v rep=%v duration=%s error=%v\n", method, req, reply, time.Since(start), err) 221 | return err 222 | } 223 | 224 | ``` 225 | 226 | ## 运行结果 227 | 228 | ```sh 229 | $ cd hello_inteceptor/server && go run main.go 230 | Listen on 127.0.0.1:50052 with TLS + Token + Interceptor 231 | ``` 232 | 233 | ```sh 234 | $ cd hello_inteceptor/client && go run main.go 235 | method=/hello.Hello/SayHello req=name:"gRPC" rep=message:"Hello gRPC." duration=33.879699ms error= 236 | 237 | Hello gRPC. 238 | ``` 239 | 240 | 241 | **项目推荐:** [go-grpc-middleware](https://github.com/grpc-ecosystem/go-grpc-middleware) 242 | 243 | 这个项目对interceptor进行了封装,支持多个拦截器的链式组装,对于需要多种处理的地方使用起来会更方便些。 244 | 245 | -------------------------------------------------------------------------------- /src/docs/basic/stream.md: -------------------------------------------------------------------------------- 1 | # gRPC 流 2 | 3 | --- 4 | 5 | 从其名称可以理解,流就是持续不断的传输。有一些业务场景请求或者响应的数据量比较大,不适合使用普通的 RPC 调用通过一次请求-响应处理,一方面是考虑数据量大对请求响应时间的影响,另一方面业务场景的设计不一定需要一次性处理完所有数据,这时就可以使用流来分批次传输数据。gRPC支持单向流和双向流,只需要在 service 的 rpc 方法描述中通过 `stream` 关键字指定启用流特性就好了,下面通过两个示例来说明使用方法。 6 | 7 | **源码目录:** 8 | 9 | ``` 10 | |—- src/ 11 | |-- ping/ 12 | |—— client.go // 客户端 13 | |—— server.go // 服务端 14 | |—- protos/ping/ 15 | |—— ping.proto // protobuf描述文件 16 | |—— ping.pb.go // protoc编译生成 17 | |-- ping_grpc.pb.go // protoc编译生成 18 | ``` 19 | 20 | ## 单向流 21 | 22 | 单向流是指客户端和服务端只有一端开启流特性,这里的单向特指**发送数据的方向**。 23 | 24 | * 当服务端开启流时,客户端和普通 RPC 调用一样通过一次请求发送数据,服务端通过流分批次响应。 25 | * 当客户端开启流时,客户端通过流分批次发送请求数据,服务端接完所有数据后统一响应一次。 26 | 27 | ### 服务端流 28 | 29 | 定义一个 `MultiPong` 方法,在服务端开启流,功能是接收到客户端的请求后响应10次 pong 消息。 30 | 31 | ```protobuf 32 | ... 33 | 34 | service PingPong { 35 | // 服务端流模式,在响应消息前添加 stream 关键字 36 | rpc MultiPong(PingRequest) returns (stream PongResponse); 37 | } 38 | 39 | ... 40 | ``` 41 | 42 | **服务端实现**:第二个参数为 stream 对象的引用,可以通过它的 `Send` 方法发送数据。 43 | 44 | ```go 45 | // src/ping/server.go 46 | 47 | // MultiPong 服务端流模式 48 | func (s *PingPongServer) MultiPong(req *pb.PingRequest, stream pb.PingPong_MultiPongServer) error { 49 | for i := 0; i < 10; i++ { 50 | data := &pb.PongResponse{Value: "pong"} 51 | // 发送消息 52 | err := stream.Send(data) 53 | if err != nil { 54 | return err 55 | } 56 | } 57 | return nil 58 | } 59 | ``` 60 | 61 | **客户端实现**:请求方式和普通 RPC 没有区别,重点关注对响应数据流的处理,通过一个 for 循环接收数据直到结束。 62 | 63 | ```go 64 | // src/ping/client.go 65 | 66 | func MultiPong() { 67 | 68 | ... 69 | 70 | // 获得对 stream 对象的引用 71 | stream, err := client.MultiPong(context.Background(), &pb.PingRequest{Value: "ping"}) 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | 76 | // 循环接收响应数据流 77 | for { 78 | msg, err := stream.Recv() 79 | if err != nil { 80 | // 数据结束 81 | if err == io.EOF { 82 | break 83 | } 84 | log.Fatal(err) 85 | } 86 | log.Println(msg.Value) 87 | } 88 | } 89 | ``` 90 | 91 | ### 客户端流 92 | 93 | 定义一个 `MultiPing` 方法,在客户端开启流,功能是持续发送多个 ping 请求,服务端统一响应一次。 94 | 95 | ```protobuf 96 | ... 97 | 98 | service PingPong { 99 | // 客户端流模式,在请求消息前添加 stream 关键字 100 | rpc MultiPing(stream PingRequest) returns (PongResponse); 101 | } 102 | 103 | ... 104 | ``` 105 | 106 | **服务端实现**:只有一个参数为 stream 对象的引用,可以通过它的 `Recv` 方法接收数据。使用 `SendAndClose` 方法关闭流并响应,服务端可以根据需要提前关闭。 107 | 108 | ```go 109 | // src/ping/server.go 110 | 111 | // MultiPing 客户端流模式 112 | func (s *PingPongServer) MultiPing(stream pb.PingPong_MultiPingServer) error { 113 | msgs := []string{} 114 | for { 115 | // 提前结束接收消息 116 | if len(msgs) > 5 { 117 | return stream.SendAndClose(&pb.PongResponse{Value: "ping enough, max 5"}) 118 | } 119 | 120 | msg, err := stream.Recv() 121 | if err != nil { 122 | // 客户端消息结束,返回响应信息 123 | if err == io.EOF { 124 | return stream.SendAndClose(&pb.PongResponse{Value: fmt.Sprintf("got %d ping", len(msgs))}) 125 | } 126 | return err 127 | } 128 | msgs = append(msgs, msg.Value) 129 | } 130 | } 131 | ``` 132 | 133 | **客户端实现**:调用 `MultiPing` 方法时不再指定请求参数,而是通过返回的 stream 对象的 `Send` 分批发送数据。 134 | 135 | ```go 136 | // src/ping/client.go 137 | 138 | func MultiPing() { 139 | ... 140 | 141 | // 调用并得到 stream 对象 142 | stream, err := client.MultiPing(context.Background()) 143 | if err != nil { 144 | log.Fatal(err) 145 | } 146 | 147 | // 发送数据 148 | for i := 0; i < 6; i++ { 149 | data := &pb.PingRequest{Value: "ping"} 150 | err = stream.Send(data) 151 | if err != nil { 152 | log.Fatal(err) 153 | } 154 | } 155 | 156 | // 发送结束并获取服务端响应 157 | res, err := stream.CloseAndRecv() 158 | if err != nil { 159 | log.Fatal(err) 160 | } 161 | 162 | log.Println(res.Value) 163 | } 164 | ``` 165 | 166 | > 运行结果: 167 | > ```sh 168 | > // 发送5个ping 169 | > 2022/09/27 00:00:00 got 5 ping 170 | > 171 | > // 发送10个ping 172 | > 2022/09/27 00:00:00 ping enough, max 5 173 | > ``` 174 | 175 | ## 双向流 176 | 177 | 双向流是指客户端在发送数据和服务端响应数据的过程中都启用流特性,实际上单向流只是双向流的特例,有了上面的基础,双向流就很好理解了。 178 | 179 | 定义一个 `MultiPingPong` 方法,在客户端和服务端都开启流,功能是服务端每接收到两个 ping 就响应一次 pong。 180 | 181 | ```protobuf 182 | ... 183 | 184 | service PingPong { 185 | // 双向流模式 186 | rpc MultiPingPong(stream PingRequest) returns (stream PongResponse); 187 | } 188 | 189 | ... 190 | ``` 191 | 192 | **服务端实现**:同样通过 stream 的 `Recv` 和 `Send` 方法接收和发送数据。 193 | 194 | ```go 195 | // src/ping/server.go 196 | 197 | func (s *PingPongServer) MultiPingPong(stream pb.PingPong_MultiPingPongServer) error { 198 | msgs := []string{} 199 | for { 200 | // 接收消息 201 | msg, err := stream.Recv() 202 | if err != nil { 203 | if err == io.EOF { 204 | break 205 | } 206 | return err 207 | } 208 | msgs = append(msgs, msg.Value) 209 | 210 | // 每收到两个消息响应一次 211 | if len(msgs)%2 == 0 { 212 | err = stream.Send(&pb.PongResponse{Value: "pong"}) 213 | if err != nil { 214 | return err 215 | } 216 | } 217 | } 218 | return nil 219 | } 220 | ``` 221 | 222 | **客户端实现**:这里在另外一个 goroutine 里处理接收数据的逻辑来演示同时发送和接收数据。 223 | 224 | ```go 225 | // src/ping/client.go 226 | func MultiPingPong() { 227 | ... 228 | 229 | stream, err := client.MultiPingPong(context.Background()) 230 | if err != nil { 231 | log.Fatal(err) 232 | } 233 | 234 | // 在另一个goroutine中处理接收数据 235 | c := make(chan struct{}) 236 | go func(stream pb.PingPong_MultiPingPongClient, c chan struct{}) { 237 | defer func() { 238 | c <- struct{}{} 239 | }() 240 | for { 241 | msg, err := stream.Recv() 242 | if err != nil { 243 | if err == io.EOF { 244 | break 245 | } 246 | log.Fatal(err) 247 | } 248 | log.Printf("recv:%s\n", msg.Value) 249 | } 250 | }(stream, c) 251 | 252 | // 发送数据 253 | for i := 0; i < 6; i++ { 254 | data := &pb.PingRequest{Value: "ping"} 255 | err = stream.Send(data) 256 | if err != nil { 257 | log.Fatal(err) 258 | } 259 | log.Printf("send:%s\n", data.Value) 260 | 261 | // 延时一段时间发送,等待响应结果 262 | time.Sleep(500 * time.Millisecond) 263 | } 264 | 265 | // 结束发送 266 | stream.CloseSend() 267 | // 等待接收完成 268 | <-c 269 | } 270 | 271 | ``` 272 | 273 | > 运行结果: 274 | > ```sh 275 | > 2022/09/29 09:09:59 send:ping 276 | > 2022/09/29 09:10:00 send:ping 277 | > 2022/09/29 09:10:00 recv:pong 278 | > 2022/09/29 09:10:00 send:ping 279 | > 2022/09/29 09:10:01 send:ping 280 | > 2022/09/29 09:10:01 recv:pong 281 | > 2022/09/29 09:10:01 send:ping 282 | > 2022/09/29 09:10:02 send:ping 283 | > 2022/09/29 09:10:02 recv:pong 284 | > ``` 285 | 286 | --- 287 | --------------------------------------------------------------------------------