Hello World

在上一节中我们已经完成了对环境的基本配置

这节将开始编写一个复杂的Hello World,涉及到许多的知识,建议大家认真思考其中的概念

需求

由于本实践偏向Grpc+Grpc Gateway的方面,我们的需求是同一个服务端支持RpcRestful Api,那么就意味着http2TLS等等的应用,功能方面就是一个服务端能够接受来自grpcRestful Api的请求并响应

一、初始化目录

我们先在$GOPATH中新建grpc-hello-world文件夹,我们项目的初始目录目录如下:

grpc-hello-world/
├── certs
├── client
├── cmd
├── pkg
├── proto
│   ├── google
│   │   └── api
└── server
  • certs:证书凭证
  • client:客户端
  • cmd:命令行
  • pkg:第三方公共模块
  • protoprotobuf的一些相关文件(含.protopb.go.pb.gw.go),google/api中用于存放annotations.protohttp.proto
  • server:服务端

二、制作证书

在服务端支持RpcRestful Api,需要用到TLS,因此我们要先制作证书

进入certs目录,生成TLS所需的公钥密钥文件

私钥

openssl genrsa -out server.key 2048openssl ecparam -genkey -name secp384r1 -out server.key
  • openssl genrsa:生成RSA私钥,命令的最后一个参数,将指定生成密钥的位数,如果没有指定,默认512
  • openssl 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

编写

1、 google.api

我们看到proto目录中有google/api目录,它用到了google官方提供的两个api描述文件,主要是针对grpc-gatewayhttp转换提供支持,定义了Protocol Buffer所扩展的HTTP Option

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;
}
  1. 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的效果

  • 定义了一个serviceRPC服务HelloWorld,在其内部定义了一个HTTP OptionPOST方法,HTTP响应路径为/hello_world
  • 定义message类型HelloWorldRequestHelloWorldResponse,用于响应请求和返回结果

编译

在编写完.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.gohello.gw.pb.go,分别针对grpcgrpc-gateway的功能支持

四、命令行模块 cmd

介绍

这一小节我们编写命令行模块,为什么要独立出来呢,是为了将cmdserver两者解耦,避免混淆在一起。

我们采用 Cobra 来完成这项功能,Cobra既是创建强大的现代CLI应用程序的库,也是生成应用程序和命令文件的程序。提供了以下功能:

  • 简易的子命令行模式
  • 完全兼容posix的命令行模式(包括短和长版本)
  • 嵌套的子命令
  • 全局、本地和级联flags
  • 使用Cobra很容易的生成应用程序和命令,使用cobra create appnamecobra add cmdname
  • 智能提示
  • 自动生成commands和flags的帮助信息
  • 自动生成详细的help信息-h--help等等
  • 自动生成的bash自动完成功能
  • 为应用程序自动生成手册
  • 命令别名
  • 定义您自己的帮助、用法等的灵活性。
  • 可选与viper紧密集成的apps

编写server

在编写cmd时需要先用server进行测试关联,因此这一步我们先写server.go用于测试

server模块下 新建server.go文件,写入测试内容:

package serverimport ("log"
)var (ServerPort stringCertName stringCertPemPath stringCertKeyPath string
)func Serve() (err error){log.Println(ServerPort)log.Println(CertName)log.Println(CertPemPath)log.Println(CertKeyPath)return nil
}

编写cmd

cmd模块下 新建root.go文件,写入内容:

package cmdimport ("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 cmdimport ("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 mainimport ("grpc-hello-world/cmd"
)func main() {cmd.Execute()
}

讲解

要使用Cobra,按照Cobra标准要创建main.go和一个rootCmd文件,另外我们有子命令server

1、rootCmd
rootCmd表示在没有任何子命令的情况下的基本命令

2、&cobra.Command

  • UseCommand的用法,Use是一个行用法消息
  • ShortShorthelp命令输出中显示的简短描述
  • Run:运行:典型的实际工作功能。大多数命令只会实现这一点;另外还有PreRunPreRunEPostRunPostRunE等等不同时期的运行命令,但比较少用,具体使用时再查看亦可

3、rootCmd.AddCommandAddCommand向这父命令(rootCmd)添加一个或多个命令

4、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 CommandsBind Flags with ConfigRequired 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 serverimport ("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,对应.protorpc SayHelloWorld,这个方法需要有2个参数:ctx context.Context用于接受上下文参数、r *pb.HelloWorldRequest用于接受protobufRequest参数(对应.protomessage HelloWorldRequest

*编写server.go

这一小章节,我们编写最为重要的服务端程序部分,涉及到大量的grpcgrpc-gateway及一些网络知识的应用

1、在pkg下新建util目录,新建grpc.go文件,写入内容:

package utilimport ("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

2、在pkg下的util目录下,新建tls.go文件,写入内容:

package utilimport ("crypto/tls""io/ioutil""log""golang.org/x/net/http2"
)func GetTLSConfig(certPemPath, certKeyPath string) *tls.Config {var certKeyPair *tls.Certificatecert, _ := 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 = &pairreturn &tls.Config{Certificates: []tls.Certificate{*certKeyPair},NextProtos:   []string{http2.NextProtoTLS},}
}

GetTLSConfig函数是用于获取TLS配置,在内部,我们读取了server.keyserver.pem这类证书凭证文件

  • tls.X509KeyPair:从一对PEM编码的数据中解析公钥/私钥对。成功则返回公钥/私钥对
  • http2.NextProtoTLSNextProtoTLS是谈判期间的NPN/ALPN协议,用于HTTP/2的TLS设置
  • tls.Certificate:返回一个或多个证书,实质我们解析PEM调用的X509KeyPair的函数声明就是func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error),返回值就是Certificate

总的来说该函数是用于处理从证书凭证文件(PEM),最终获取tls.Config作为HTTP2的使用参数

3、修改server目录下的server.go文件,该文件是我们服务里的核心文件,写入内容:

package serverimport ("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 stringCertName stringCertPemPath stringCertKeyPath stringEndPoint string
)func Serve() (err error){EndPoint = ":" + ServerPortconn, 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 servercreds, 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 pbpb.RegisterHelloWorldServer(grpcServer, NewHelloService())// gw serverctx := 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 pbif 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必须传入tcptcp4tcp6unixunixpacket,若address为空或为0则会自动选择一个端口号
返回值:通过查看源码我们可以得知其返回值为Listener,结构体原型:

type Listener interface {Accept() (Conn, error)Close() errorAddr() Addr
}

通过分析得知,最后net.Listen会返回一个监听器的结构体,返回给接下来的动作,让其执行下一步的操作,它可以执行三类操作

  • Accept:接受等待并将下一个连接返回给Listener
  • Close:关闭Listener
  • Addr:返回Listener的网络地址
二、获取TLS

通过util.GetTLSConfig解析得到tls.Config,传达给http.Server服务的TLSConfig配置项使用

三、创建内部服务

createInternalServer函数,是整个服务端的核心流转部分

程序采用的是HTT2HTTPS也就是需要支持TLS,因此在启动grpc.NewServer前,我们要将认证的中间件注册进去

而前面所获取的tlsConfig仅能给HTTP使用,因此第一步我们要创建grpcTLS认证凭证

1、创建grpcTLS认证凭证

新增引用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的TransportCredentialsreturn NewTLS(&tls.Config{Certificates: []tls.Certificate{cert}}), nil
}

2、设置grpc ServerOption

grpc.Creds(creds)为例,其原型为func Creds(c credentials.TransportCredentials) ServerOption,该函数返回ServerOption,它为服务器连接设置凭据

3、创建grpc服务端

函数原型:

func NewServer(opt ...ServerOption) *Server

我们在此处创建了一个没有注册服务的grpc服务端,还没有开始接受请求

grpcServer := grpc.NewServer(opts...)

4、注册grpc服务

pb.RegisterHelloWorldServer(grpcServer, NewHelloService())

5、创建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:配置一个连接级别的安全凭据(例:TLSSSL),返回值为type DialOption
  • grpc.DialOptionDialOption选项配置我们如何设置连接(其内部具体由多个的DialOption组成,决定其设置连接的内容)

6、创建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,它的内部映射是空的;ServeMuxgrpc-gateway的一个请求多路复用器。它将http请求与模式匹配,并调用相应的处理程序
  • RegisterHelloWorldHandlerFromEndpoint:如函数名,注册HelloWorld服务的HTTP Handlegrpc端点
  • 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都是最终返回serveMuxHandler中导出的方法仅有ServeHTTP,功能是用于响应HTTP请求

我们回到Handle interface中,可以得出结论就是任何结构体,只要实现了ServeHTTP方法,这个结构就可以称为HandleServeMux会使用该Handler调用ServeHTTP方法处理请求,这也就是自定义Handler

而我们这里正是将grpc-gateway中注册好的HTTP Handler无缝的植入到net/httpHandle方法中

补充:在go中任何结构体只要实现了与接口相同的方法,就等同于实现了接口

7、注册具体服务

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 = innerl.config = configreturn 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 16220ctx := context.WithValue(baseCtx, ServerContextKey, srv)for {rw, e := l.Accept()...c := srv.newConn(rw)c.setState(c.rwc, StateNew) // before Serve can returngo c.serve(ctx)}
}

粗略的看,它创建了一个context.Background()上下文对象,并调用ListenerAccept方法开始接受外部请求,在获取到连接数据后使用newConn创建连接对象,在最后使用goroutine的方式处理连接请求,达到其目的

补充:对于HTTP/2支持,在调用Serve之前,应将srv.TLSConfig初始化为提供的Listener的TLS配置。如果srv.TLSConfig非零,并且在Config.NextProtos中不包含字符串h2,则不启用HTTP/2支持

六、验证功能

编写测试客户端

grpc-hello-world/下新建目录client,新建client.go文件,新增内容:

package mainimport ("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

执行测试Restful Api

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的文章,大家有时间可以看看

另外本节涉及了许多组件间的知识,值得大家细细的回味,非常有意义!

参考

示例代码

  • grpc-hello-world

Grpc+Grpc Gateway实践二 有些复杂的Hello World相关推荐

  1. Grpc+Grpc Gateway实践一 介绍与环境安装

    介绍与环境安装 假定我们有一个项目需求,希望用Rpc作为内部API的通讯,同时也想对外提供Restful Api,写两套又太繁琐不符合 于是我们想到了Grpc以及Grpc Gateway,这就是我们所 ...

  2. Asp.NetCore程序发布到CentOs(含安装部署netcore)--最佳实践(二)

    原文:Asp.NetCore程序发布到CentOs(含安装部署netcore)--最佳实践(二) Asp.NetCore程序发布到CentOs(含安装部署netcore)--最佳实践(一) 接上一篇 ...

  3. 软件工程专业(互联网应用开发与优化方向)软件工程实践二环节教学大纲

    软件工程专业(互联网应用开发与优化方向)软件工程实践二环节教学大纲 培训课程 Phthon Web开发 实训公司 XXX 总周数 3周

  4. P4语言编程快速开始 实践二

    参考:P4语言编程快速开始 上一篇系列博客:P4语言编程快速开始 实践二 Demo 2 本Demo所做的修改及实现的功能: 为simple_router添加一个计数器(counter),该计数器附加( ...

  5. linux grpc,grpc linux下的编译使用-Go语言中文社区

    1. 一些工具安装 $ apt-get install build-essential autoconf libtool pkg-config $ apt-get install libgflags- ...

  6. ASP.NET MVC5 网站开发实践(二) Member区域 - 用户部分(2)用户登录、注销

    上次实现了用户注册,这次来实现用户登录,用到IAuthenticationManager的SignOut.SignIn方法和基于声明的标识.最后修改用户注册代码实现注册成功后直接登录. 目录: ASP ...

  7. Spring cloud Gateway(二) 一个Http请求的流程解析

    Spring cloud Gateway(二) 一个Http请求的流程解析 简介     通过一个简单示例,debug出Spring Cloud Gateway的一个HTTP请求的处理流程 思路整理 ...

  8. Nginx反向代理与负载均衡应用实践(二)

    Nginx反向代理与负载均衡应用实践(二) 链接:https://pan.baidu.com/s/1xB20bnuanh0Avs4kwRpSXQ 提取码:migq 复制这段内容后打开百度网盘手机App ...

  9. SOLR7实践(二)--DIH配置及使用

    DIH配置及使用@SOLR7实践(二) DIH配置及使用 网上已经有很多相关文章了,但是实践中还是遇到了些问题,备忘一下. 一.配置solrconfig.xml 编辑/var/solr/data/ne ...

最新文章

  1. C#.NET 中的类型转换
  2. Python入门习题9.数码管时间
  3. 4月21日会议总结(整理—祁子梁)
  4. java按条件查询结果为空_mybatis中查询结果为空时不同返回类型对应返回值问题...
  5. Seata 与 Nacos Config配置中心整合_03
  6. wdm设备驱动程序开发pdf_DWDM是什么 DWDM相关设备功能介绍【图文】
  7. pyqt打包成linux可执行程序,PyQtopencv图像处理(5):python程序打包成可执行文件...
  8. Java线程专栏文章汇总(转)
  9. PHM算法与智能分析技术
  10. [禅悟人生]将所学转化成修行
  11. SAP学习记__物料管理(MM)模块__采购入库冲销、退货
  12. ★★iPhone越狱后必装软件和一些心得教程,送给广大的小白们,希望你们能从小白变成“小黑”★★
  13. 云输入法linux版,ubuntu云输入法ibus cloud pinyin
  14. 如何解决No EPCS layout data --- looking for section [EPCS-XXXXXX]
  15. 查询给定区域内曲面平均高程
  16. MySQL 报错1055
  17. HDU 4911 Inversion - 疯狂的癫子 解题心得
  18. codables一种更好的方式快速解析数据
  19. Java获取下周、本周、上月、本月第一天最后一天
  20. 八皇后问题理解与解决

热门文章

  1. 【转载】谈谈我对Java中CallBack的理解
  2. PHP写时复制, 变量复制和对象复制不同!!!
  3. POJ1185:火炮(减少国家)
  4. jQuery插件:超酷的多列网格式拖放插件gridster.js
  5. 关于CSS3的filter(滤镜) 属性
  6. ksql中定义的本体在dataModel中不存在
  7. 1.1 Friday the Thirteenth
  8. ASP.NET C# 货币转换函数 中文大写金额 英文金额
  9. 不是不去爱,爱了也是一种伤害!
  10. SDWebImage中文说明