在上一節中咱們已經完成了對環境的基本配置html
這節將開始編寫一個複雜的Hello World,涉及到許多的知識,建議你們認真思考其中的概念java
原文地址:Hello Worldgit
因爲本實踐偏向Grpc
+Grpc Gateway
的方面,咱們的需求是同一個服務端支持Rpc
和Restful Api
,那麼就意味着http2
、TLS
等等的應用,功能方面就是一個服務端可以接受來自grpc
和Restful Api
的請求並響應github
咱們先在$GOPATH中新建grpc-hello-world
文件夾,咱們項目的初始目錄目錄以下:golang
grpc-hello-world/ ├── certs ├── client ├── cmd ├── pkg ├── proto │ ├── google │ │ └── api └── server
certs
:證書憑證client
:客戶端cmd
:命令行pkg
:第三方公共模塊proto
:protobuf
的一些相關文件(含.proto
、pb.go
、.pb.gw.go
),google/api
中用於存放annotations.proto
、http.proto
server
:服務端在服務端支持Rpc
和Restful Api
,須要用到TLS
,所以咱們要先製做證書express
進入certs
目錄,生成TLS
所需的公鑰密鑰文件apache
openssl genrsa -out server.key 2048 openssl ecparam -genkey -name secp384r1 -out server.key
openssl genrsa
:生成RSA
私鑰,命令的最後一個參數,將指定生成密鑰的位數,若是沒有指定,默認512openssl ecparam
:生成ECC
私鑰,命令爲橢圓曲線密鑰參數生成及操做,本文中ECC
曲線選擇的是secp384r1
openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650
openssl req
:生成自簽名證書,-new
指生成證書請求、-sha256
指使用sha256
加密、-key
指定私鑰文件、-x509
指輸出證書、-days 3650
爲有效期,此後則輸入證書擁有者信息Country Name (2 letter code) [XX]: State or Province Name (full name) []: Locality Name (eg, city) [Default City]: Organization Name (eg, company) [Default Company Ltd]: Organizational Unit Name (eg, section) []: Common Name (eg, your name or your server's hostname) []:grpc server name Email Address []:
proto
一、 google.api
segmentfault
咱們看到proto
目錄中有google/api
目錄,它用到了google
官方提供的兩個api
描述文件,主要是針對grpc-gateway
的http
轉換提供支持,定義了Protocol Buffer
所擴展的HTTP Option
api
annotations.proto
文件:安全
// Copyright (c) 2015, Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; package google.api; import "google/api/http.proto"; import "google/protobuf/descriptor.proto"; option java_multiple_files = true; option java_outer_classname = "AnnotationsProto"; option java_package = "com.google.api"; extend google.protobuf.MethodOptions { // See `HttpRule`. HttpRule http = 72295728; }
http.proto
文件:
// Copyright 2016 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; package google.api; option cc_enable_arenas = true; option java_multiple_files = true; option java_outer_classname = "HttpProto"; option java_package = "com.google.api"; // Defines the HTTP configuration for a service. It contains a list of // [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method // to one or more HTTP REST API methods. message Http { // A list of HTTP rules for configuring the HTTP REST API methods. repeated HttpRule rules = 1; } // Use CustomHttpPattern to specify any HTTP method that is not included in the // `pattern` field, such as HEAD, or "*" to leave the HTTP method unspecified for // a given URL path rule. The wild-card rule is useful for services that provide // content to Web (HTML) clients. message HttpRule { // Selects methods to which this rule applies. // // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. string selector = 1; // Determines the URL pattern is matched by this rules. This pattern can be // used with any of the {get|put|post|delete|patch} methods. A custom method // can be defined using the 'custom' field. oneof pattern { // Used for listing and getting information about resources. string get = 2; // Used for updating a resource. string put = 3; // Used for creating a resource. string post = 4; // Used for deleting a resource. string delete = 5; // Used for updating a resource. string patch = 6; // Custom pattern is used for defining custom verbs. CustomHttpPattern custom = 8; } // The name of the request field whose value is mapped to the HTTP body, or // `*` for mapping all fields not captured by the path pattern to the HTTP // body. NOTE: the referred field must not be a repeated field. string body = 7; // Additional HTTP bindings for the selector. Nested bindings must // not contain an `additional_bindings` field themselves (that is, // the nesting may only be one level deep). repeated HttpRule additional_bindings = 11; } // A custom pattern is used for defining custom HTTP verb. message CustomHttpPattern { // The name of this custom HTTP verb. string kind = 1; // The path matched by this custom verb. string path = 2; }
hello.proto
這一小節將編寫Demo
的.proto
文件,咱們在proto
目錄下新建hello.proto
文件,寫入文件內容:
syntax = "proto3"; package proto; import "google/api/annotations.proto"; service HelloWorld { rpc SayHelloWorld(HelloWorldRequest) returns (HelloWorldResponse) { option (google.api.http) = { post: "/hello_world" body: "*" }; } } message HelloWorldRequest { string referer = 1; } message HelloWorldResponse { string message = 1; }
在hello.proto
文件中,引用了google/api/annotations.proto
,達到支持HTTP Option
的效果
service
RPC服務HelloWorld
,在其內部定義了一個HTTP Option
的POST
方法,HTTP
響應路徑爲/hello_world
message
類型HelloWorldRequest
、HelloWorldResponse
,用於響應請求和返回結果在編寫完.proto
文件後,咱們須要對其進行編譯,就可以在server
中使用
進入proto
目錄,執行如下命令
# 編譯google.api protoc -I . --go_out=plugins=grpc,Mgoogle/protobuf/descriptor.proto=github.com/golang/protobuf/protoc-gen-go/descriptor:. google/api/*.proto #編譯hello_http.proto爲hello_http.pb.proto protoc -I . --go_out=plugins=grpc,Mgoogle/api/annotations.proto=grpc-hello-world/proto/google/api:. ./hello.proto #編譯hello_http.proto爲hello_http.pb.gw.proto protoc --grpc-gateway_out=logtostderr=true:. ./hello.proto
執行完畢後將生成hello.pb.go
和hello.gw.pb.go
,分別針對grpc
和grpc-gateway
的功能支持
cmd
這一小節咱們編寫命令行模塊,爲何要獨立出來呢,是爲了將cmd
和server
二者解耦,避免混淆在一塊兒。
咱們採用 Cobra 來完成這項功能,Cobra
既是建立強大的現代CLI應用程序的庫,也是生成應用程序和命令文件的程序。提供瞭如下功能:
flags
Cobra
很容易的生成應用程序和命令,使用cobra create appname
和cobra add cmdname
-h
,--help
等等server
在編寫cmd
時須要先用server
進行測試關聯,所以這一步咱們先寫server.go
用於測試
在server
模塊下 新建server.go
文件,寫入測試內容:
package server import ( "log" ) var ( ServerPort string CertName string CertPemPath string CertKeyPath string ) func Serve() (err error){ log.Println(ServerPort) log.Println(CertName) log.Println(CertPemPath) log.Println(CertKeyPath) return nil }
cmd
在cmd
模塊下 新建root.go
文件,寫入內容:
package cmd import ( "fmt" "os" "github.com/spf13/cobra" ) var rootCmd = &cobra.Command{ Use: "grpc", Short: "Run the gRPC hello-world server", } func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(-1) } }
新建server.go
文件,寫入內容:
package cmd import ( "log" "github.com/spf13/cobra" "grpc-hello-world/server" ) var serverCmd = &cobra.Command{ Use: "server", Short: "Run the gRPC hello-world server", Run: func(cmd *cobra.Command, args []string) { defer func() { if err := recover(); err != nil { log.Println("Recover error : %v", err) } }() server.Serve() }, } func init() { serverCmd.Flags().StringVarP(&server.ServerPort, "port", "p", "50052", "server port") serverCmd.Flags().StringVarP(&server.CertPemPath, "cert-pem", "", "./certs/server.pem", "cert pem path") serverCmd.Flags().StringVarP(&server.CertKeyPath, "cert-key", "", "./certs/server.key", "cert key path") serverCmd.Flags().StringVarP(&server.CertName, "cert-name", "", "grpc server name", "server's hostname") rootCmd.AddCommand(serverCmd) }
咱們在grpc-hello-world/
目錄下,新建文件main.go
,寫入內容:
package main import ( "grpc-hello-world/cmd" ) func main() { cmd.Execute() }
要使用Cobra
,按照Cobra
標準要建立main.go
和一個rootCmd
文件,另外咱們有子命令server
一、rootCmd
:rootCmd
表示在沒有任何子命令的狀況下的基本命令
二、&cobra.Command
:
Use
:Command
的用法,Use
是一個行用法消息Short
:Short
是help
命令輸出中顯示的簡短描述Run
:運行:典型的實際工做功能。大多數命令只會實現這一點;另外還有PreRun
、PreRunE
、PostRun
、PostRunE
等等不一樣時期的運行命令,但比較少用,具體使用時再查看亦可三、rootCmd.AddCommand
:AddCommand
向這父命令(rootCmd
)添加一個或多個命令
四、serverCmd.Flags().StringVarP()
:
通常來講,咱們須要在init()
函數中定義flags
和處理配置,以serverCmd.Flags().StringVarP(&server.ServerPort, "port", "p", "50052", "server port")
爲例,咱們定義了一個flag
,值存儲在&server.ServerPort
中,長命令爲--port
,短命令爲-p
,,默認值爲50052
,命令的描述爲server port
。這一種調用方式成爲Local Flags
咱們延伸一下,若是以爲每個子命令都要設一遍以爲很麻煩,咱們能夠採用Persistent Flags
:
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
做用:
flag
是能夠持久的,這意味着該flag
將被分配給它所分配的命令以及該命令下的每一個命令。對於全局標記,將標記做爲根上的持久標誌。
另外還有Local Flag on Parent Commands
、Bind Flags with Config
、Required flags
等等,使用到再 傳送 瞭解便可
回到grpc-hello-world/
目錄下執行go run main.go server
,查看輸出是否爲(此時應爲默認值):
2018/02/25 23:23:21 50052 2018/02/25 23:23:21 dev 2018/02/25 23:23:21 ./certs/server.pem 2018/02/25 23:23:21 ./certs/server.key
執行go run main.go server --port=8000 --cert-pem=test-pem --cert-key=test-key --cert-name=test-name
,檢驗命令行參數是否正確:
2018/02/25 23:24:56 8000 2018/02/25 23:24:56 test-name 2018/02/25 23:24:56 test-pem 2018/02/25 23:24:56 test-key
若都無誤,那麼恭喜你cmd
模塊的編寫正確了,下一部分開始咱們的重點章節!
server
hello.go
在server
目錄下新建文件hello.go
,寫入文件內容:
package server import ( "golang.org/x/net/context" pb "grpc-hello-world/proto" ) type helloService struct{} func NewHelloService() *helloService { return &helloService{} } func (h helloService) SayHelloWorld(ctx context.Context, r *pb.HelloWorldRequest) (*pb.HelloWorldResponse, error) { return &pb.HelloWorldResponse{ Message : "test", }, nil }
咱們建立了helloService
及其方法SayHelloWorld
,對應.proto
的rpc SayHelloWorld
,這個方法須要有2個參數:ctx context.Context
用於接受上下文參數、r *pb.HelloWorldRequest
用於接受protobuf
的Request
參數(對應.proto
的message HelloWorldRequest
)
server.go
這一小章節,咱們編寫最爲重要的服務端程序部分,涉及到大量的grpc
、grpc-gateway
及一些網絡知識的應用
一、在pkg
下新建util
目錄,新建grpc.go
文件,寫入內容:
package util import ( "net/http" "strings" "google.golang.org/grpc" ) func GrpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler { if otherHandler == nil { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { grpcServer.ServeHTTP(w, r) }) } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") { grpcServer.ServeHTTP(w, r) } else { otherHandler.ServeHTTP(w, r) } }) }
GrpcHandlerFunc
函數是用於判斷請求是來源於Rpc
客戶端仍是Restful Api
的請求,根據不一樣的請求註冊不一樣的ServeHTTP
服務;r.ProtoMajor == 2
也表明着請求必須基於HTTP/2
二、在pkg
下的util
目錄下,新建tls.go
文件,寫入內容:
package util import ( "crypto/tls" "io/ioutil" "log" "golang.org/x/net/http2" ) func GetTLSConfig(certPemPath, certKeyPath string) *tls.Config { var certKeyPair *tls.Certificate cert, _ := ioutil.ReadFile(certPemPath) key, _ := ioutil.ReadFile(certKeyPath) pair, err := tls.X509KeyPair(cert, key) if err != nil { log.Println("TLS KeyPair err: %v\n", err) } certKeyPair = &pair return &tls.Config{ Certificates: []tls.Certificate{*certKeyPair}, NextProtos: []string{http2.NextProtoTLS}, } }
GetTLSConfig
函數是用於獲取TLS
配置,在內部,咱們讀取了server.key
和server.pem
這類證書憑證文件
tls.X509KeyPair
:從一對PEM
編碼的數據中解析公鑰/私鑰對。成功則返回公鑰/私鑰對http2.NextProtoTLS
:NextProtoTLS
是談判期間的NPN/ALPN
協議,用於HTTP/2的TLS設置 tls.Certificate
:返回一個或多個證書,實質咱們解析PEM
調用的X509KeyPair
的函數聲明就是func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error)
,返回值就是Certificate
總的來講該函數是用於處理從證書憑證文件(PEM),最終獲取tls.Config
做爲HTTP2
的使用參數
三、修改server
目錄下的server.go
文件,該文件是咱們服務裏的核心文件,寫入內容:
package server import ( "crypto/tls" "net" "net/http" "log" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "github.com/grpc-ecosystem/grpc-gateway/runtime" pb "grpc-hello-world/proto" "grpc-hello-world/pkg/util" ) var ( ServerPort string CertName string CertPemPath string CertKeyPath string EndPoint string ) func Serve() (err error){ EndPoint = ":" + ServerPort conn, err := net.Listen("tcp", EndPoint) if err != nil { log.Printf("TCP Listen err:%v\n", err) } tlsConfig := util.GetTLSConfig(CertPemPath, CertKeyPath) srv := createInternalServer(conn, tlsConfig) log.Printf("gRPC and https listen on: %s\n", ServerPort) if err = srv.Serve(tls.NewListener(conn, tlsConfig)); err != nil { log.Printf("ListenAndServe: %v\n", err) } return err } func createInternalServer(conn net.Listener, tlsConfig *tls.Config) (*http.Server) { var opts []grpc.ServerOption // grpc server creds, err := credentials.NewServerTLSFromFile(CertPemPath, CertKeyPath) if err != nil { log.Printf("Failed to create server TLS credentials %v", err) } opts = append(opts, grpc.Creds(creds)) grpcServer := grpc.NewServer(opts...) // register grpc pb pb.RegisterHelloWorldServer(grpcServer, NewHelloService()) // gw server ctx := context.Background() dcreds, err := credentials.NewClientTLSFromFile(CertPemPath, CertName) if err != nil { log.Printf("Failed to create client TLS credentials %v", err) } dopts := []grpc.DialOption{grpc.WithTransportCredentials(dcreds)} gwmux := runtime.NewServeMux() // register grpc-gateway pb if err := pb.RegisterHelloWorldHandlerFromEndpoint(ctx, gwmux, EndPoint, dopts); err != nil { log.Printf("Failed to register gw server: %v\n", err) } // http服務 mux := http.NewServeMux() mux.Handle("/", gwmux) return &http.Server{ Addr: EndPoint, Handler: util.GrpcHandlerFunc(grpcServer, mux), TLSConfig: tlsConfig, } }
server
流程剖析咱們將這一大塊代碼,分紅如下幾個部分來理解
net.Listen("tcp", EndPoint)
用於監聽本地的網絡地址通知,它的函數原型func Listen(network, address string) (Listener, error)
參數:network
必須傳入tcp
、tcp4
、tcp6
、unix
、unixpacket
,若address
爲空或爲0則會自動選擇一個端口號
返回值:經過查看源碼咱們能夠得知其返回值爲Listener
,結構體原型:
type Listener interface { Accept() (Conn, error) Close() error Addr() Addr }
經過分析得知,最後net.Listen
會返回一個監聽器的結構體,返回給接下來的動做,讓其執行下一步的操做,它能夠執行三類操做
Accept
:接受等待並將下一個鏈接返回給Listener
Close
:關閉Listener
Addr
:返回Listener
的網絡地址TLS
經過util.GetTLSConfig
解析獲得tls.Config
,傳達給http.Server
服務的TLSConfig
配置項使用
createInternalServer
函數,是整個服務端的核心流轉部分
程序採用的是HTT2
、HTTPS
也就是須要支持TLS
,所以在啓動grpc.NewServer
前,咱們要將認證的中間件註冊進去
而前面所獲取的tlsConfig
僅能給HTTP
使用,所以第一步咱們要建立grpc
的TLS
認證憑證
一、建立grpc
的TLS
認證憑證
新增引用google.golang.org/grpc/credentials
的第三方包,它實現了grpc
庫支持的各類憑證,該憑證封裝了客戶機須要的全部狀態,以便與服務器進行身份驗證並進行各類斷言,例如關於客戶機的身份,角色或是否受權進行特定的呼叫
咱們調用NewServerTLSFromFile
來達到咱們的目的,它可以從輸入證書文件和服務器的密鑰文件構造TLS證書憑證
func NewServerTLSFromFile(certFile, keyFile string) (TransportCredentials, error) { //LoadX509KeyPair讀取並解析來自一對文件的公鑰/私鑰對 cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return nil, err } //NewTLS使用tls.Config來構建基於TLS的TransportCredentials return NewTLS(&tls.Config{Certificates: []tls.Certificate{cert}}), nil }
二、設置grpc ServerOption
以grpc.Creds(creds)
爲例,其原型爲func Creds(c credentials.TransportCredentials) ServerOption
,該函數返回ServerOption
,它爲服務器鏈接設置憑據
三、建立grpc
服務端
函數原型:
func NewServer(opt ...ServerOption) *Server
咱們在此處建立了一個沒有註冊服務的grpc
服務端,尚未開始接受請求
grpcServer := grpc.NewServer(opts...)
四、註冊grpc
服務
pb.RegisterHelloWorldServer(grpcServer, NewHelloService())
五、建立grpc-gateway
關聯組件
ctx := context.Background() dcreds, err := credentials.NewClientTLSFromFile(CertPemPath, CertName) if err != nil { log.Println("Failed to create client TLS credentials %v", err) } dopts := []grpc.DialOption{grpc.WithTransportCredentials(dcreds)}
context.Background
:返回一個非空的空上下文。它沒有被註銷,沒有值,沒有過時時間。它一般由主函數、初始化和測試使用,並做爲傳入請求的頂級上下文 credentials.NewClientTLSFromFile
:從客戶機的輸入證書文件構造TLS憑證grpc.WithTransportCredentials
:配置一個鏈接級別的安全憑據(例:TLS
、SSL
),返回值爲type DialOption
grpc.DialOption
:DialOption
選項配置咱們如何設置鏈接(其內部具體由多個的DialOption
組成,決定其設置鏈接的內容)六、建立HTTP NewServeMux
及註冊grpc-gateway
邏輯
gwmux := runtime.NewServeMux() // register grpc-gateway pb if err := pb.RegisterHelloWorldHandlerFromEndpoint(ctx, gwmux, EndPoint, dopts); err != nil { log.Println("Failed to register gw server: %v\n", err) } // http服務 mux := http.NewServeMux() mux.Handle("/", gwmux)
runtime.NewServeMux
:返回一個新的ServeMux
,它的內部映射是空的;ServeMux
是grpc-gateway
的一個請求多路複用器。它將http
請求與模式匹配,並調用相應的處理程序RegisterHelloWorldHandlerFromEndpoint
:如函數名,註冊HelloWorld
服務的HTTP Handle
到grpc
端點http.NewServeMux
:分配並返回一個新的ServeMux
mux.Handle
:爲給定模式註冊處理程序(帶着疑問去看程序)爲何gwmux
能夠放入mux.Handle
中?
首先咱們看看它們的原型是怎麼樣的
(1)http.NewServeMux()
func NewServeMux() *ServeMux { return new(ServeMux) }
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
(2)runtime.NewServeMux
?
func NewServeMux(opts ...ServeMuxOption) *ServeMux { serveMux := &ServeMux{ handlers: make(map[string][]handler), forwardResponseOptions: make([]func(context.Context, http.ResponseWriter, proto.Message) error, 0), marshalers: makeMarshalerMIMERegistry(), } ... return serveMux }
(3)http.NewServeMux()
的Handle
方法
func (mux *ServeMux) Handle(pattern string, handler Handler)
經過分析可得知,二者NewServeMux
都是最終返回serveMux
,Handler
中導出的方法僅有ServeHTTP
,功能是用於響應HTTP請求
咱們回到Handle interface
中,能夠得出結論就是任何結構體,只要實現了ServeHTTP
方法,這個結構就能夠稱爲Handle
,ServeMux
會使用該Handler
調用ServeHTTP
方法處理請求,這也就是自定義Handler
而咱們這裏正是將grpc-gateway
中註冊好的HTTP Handler
無縫的植入到net/http
的Handle
方法中
補充:在go
中任何結構體只要實現了與接口相同的方法,就等同於實現了接口
七、註冊具體服務
if err := pb.RegisterHelloWorldHandlerFromEndpoint(ctx, gwmux, EndPoint, dopts); err != nil { log.Println("Failed to register gw server: %v\n", err) }
在這段代碼中,咱們利用了前幾小節的
gateway-grpc
的請求多路複用器註冊了HelloWorld
這一個服務
tls.NewListener
func NewListener(inner net.Listener, config *Config) net.Listener { l := new(listener) l.Listener = inner l.config = config return l }
NewListener
將會建立一個Listener
,它接受兩個參數,第一個是來自內部Listener
的監聽器,第二個參數是tls.Config
(必須包含至少一個證書)
在最後咱們調用srv.Serve(tls.NewListener(conn, tlsConfig))
,能夠得知它是http.Server
的方法,而且須要一個Listener
做爲參數,那麼Serve
內部作了些什麼事呢?
func (srv *Server) Serve(l net.Listener) error { defer l.Close() ... baseCtx := context.Background() // base is always background, per Issue 16220 ctx := context.WithValue(baseCtx, ServerContextKey, srv) for { rw, e := l.Accept() ... c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return go c.serve(ctx) } }
粗略的看,它建立了一個context.Background()
上下文對象,並調用Listener
的Accept
方法開始接受外部請求,在獲取到鏈接數據後使用newConn
建立鏈接對象,在最後使用goroutine
的方式處理鏈接請求,達到其目的
補充:對於HTTP/2
支持,在調用Serve
以前,應將srv.TLSConfig
初始化爲提供的Listener
的TLS配置。若是srv.TLSConfig
非零,而且在Config.NextProtos
中不包含字符串h2
,則不啓用HTTP/2
支持
在grpc-hello-world/
下新建目錄client
,新建client.go
文件,新增內容:
package main import ( "log" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" pb "grpc-hello-world/proto" ) func main() { creds, err := credentials.NewClientTLSFromFile("../certs/server.pem", "dev") if err != nil { log.Println("Failed to create TLS credentials %v", err) } conn, err := grpc.Dial(":50052", grpc.WithTransportCredentials(creds)) defer conn.Close() if err != nil { log.Println(err) } c := pb.NewHelloWorldClient(conn) context := context.Background() body := &pb.HelloWorldRequest{ Referer : "Grpc", } r, err := c.SayHelloWorld(context, body) if err != nil { log.Println(err) } log.Println(r.Message) }
因爲客戶端只是展現測試用,就簡單的來了,本來它理應歸類到cobra
的管控下,配置管理等等都應可控化
在看這篇文章的你,能夠試試將測試客戶端歸類好
回到grpc-hello-world/
目錄下,啓動服務端go run main.go server
,成功則僅返回
2018/02/26 17:19:36 gRPC and https listen on: 50052
回到client
目錄下,啓動客戶端go run client.go
,成功則返回
2018/02/26 17:22:57 Grpc
curl -X POST -k https://localhost:50052/hello_world -d '{"referer": "restful_api"}'
成功則返回{"message":"restful_api"}
grpc-hello-world ├── certs │ ├── server.key │ └── server.pem ├── client │ └── client.go ├── cmd │ ├── root.go │ └── server.go ├── main.go ├── pkg │ └── util │ ├── grpc.go │ └── tls.go ├── proto │ ├── google │ │ └── api │ │ ├── annotations.pb.go │ │ ├── annotations.proto │ │ ├── http.pb.go │ │ └── http.proto │ ├── hello.pb.go │ ├── hello.pb.gw.go │ └── hello.proto └── server ├── hello.go └── server.go
至此本節就結束了,推薦一下jergoo
的文章,你們有時間能夠看看
另外本節涉及了許多組件間的知識,值得你們細細的回味,很是有意義!