Grpc+Grpc Gateway Practice II Some Complex Hello World

  golang, grpc, rpc

Hello World

InPrevious sectionWe have finished the basic configuration of the environment 2

This section will begin to write a complex Hello World, which involves a lot of knowledge. It is suggested that everyone should seriously think about the concepts in it.

Demand

Due to the bias of this practiceGrpc+Grpc GatewayOn the other hand, our needs areThe same server supportsRpcAndRestful Api, then it meanshttp2TLSAnd so on application, the function aspect is a server can accept fromgrpcAndRestful ApiTo request and respond to

First, initialize the directory

Let’s create a new one in $GOPATH first.grpc-hello-worldFolder, the initial directory directory of our project is as follows:

grpc-hello-world/
├── certs
├── client
├── cmd
├── pkg
├── proto
│   ├── google
│   │   └── api
└── server
  • certs: certificate certificate
  • client: client
  • cmd: command line
  • pkg: Third Party Common Module
  • protoprotobufSome related documents of (including.protopb.go.pb.gw.go),google/apiFor storage inannotations.protohttp.proto
  • server: server

II. Production Certificate

Support on server sideRpcAndRestful Api, need to useTLSSo we have to make a certificate first

EntercertsDirectory, generationTLSRequired public key key file

Private key

openssl genrsa -out server.key 2048

openssl ecparam -genkey -name secp384r1 -out server.key
  • openssl genrsa: generateRSAThe private key, the last parameter of the command, will specify the number of bits to generate the key. If it is not specified, the default is 512
  • openssl ecparam: generateECCPrivate key, command for elliptic curve key parameter generation and operation, in this paperECCWhat the curve chooses issecp384r1

Self-signed public key

openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650
  • openssl req: Generate a self-signed certificate.-newRefers to generate certificate request,-sha256Refers to the use ofsha256Encryption,-keySpecify the private key file,-x509Refers to the output certificate,-days 3650Is the validity period, after which the certificate owner information is entered

Fill in the information

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 []:

Third,proto

write

1、google.api

We seeprotoThere aregoogle/apiDirectory, it usedgoogleTwo officially providedapiDescription file, mainly forgrpc-gatewayThehttpThe transformation provides support and definesProtocol BufferExpandedHTTP Option

annotations.protoDocuments:

// 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.protoDocuments:

// 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

This subsection will be preparedDemoThe.protoFile, we’re atprotoNew under directoryhello.protoFile, write file content:

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;
}

Inhello.protoIn the document, reference is made togoogle/api/annotations.protoTo achieve supportHTTP OptionThe effect of

  • Defines aserviceRPC serviceHelloWorldWhich defines aHTTP OptionThePOSTMethods,HTTPThe response path is/hello_world
  • DefinitionmessageTypeHelloWorldRequestHelloWorldResponseFor responding to requests and returning results

Compile

After writing.protoAfter the file, we need to compile it so that we canserverUsed in

EnterprotoDirectory, execute the following command

# 编译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

Generated after executionhello.pb.goAndhello.gw.pb.go, respectively, forgrpcAndgrpc-gatewayFunctional support for

IV. Command Line Modulecmd

Introduction

This section we write command line module, why do you want to separate, is tocmdAndserverThe two are decoupled to avoid confusion.

We adoptCobraTo accomplish this function,CobraIt is not only a library for creating powerful modern CLI applications, but also a program for generating application programs and command files. The following functions are provided:

  • Simple subcommand mode
  • Fully posix-compatible command line mode (including short and long versions)
  • Nested subcommands
  • Global, Local and Cascadedflags
  • UseCobraIt is easy to generate applications and commands usingcobra create appnameAndcobra add cmdname
  • Intelligent prompt
  • Automatically generate help information for commands and flags
  • Automatically generate detailed help information-h,--helpWait
  • Automatically generated bash auto-completion function
  • Automatically generate manuals for applications
  • Command alias
  • Flexibility to define your own help, usage, etc.
  • Optional andviperTightly integrated apps

writeserver

In preparationcmdYou need to use it firstserverTo test the correlation, so this step we write firstserver.goFor testing

InserverNew under moduleserver.goFile, write test content:

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
}

writecmd

IncmdNew under moduleroot.goFile, write content:

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)
    }
}

Newserver.goFile, write content:

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)
}

We aregrpc-hello-world/Directory, create a new filemain.go, write content:

package main

import (
    "grpc-hello-world/cmd"
)

func main() {
    cmd.Execute()
}

explain

To useCobraAccording toCobraStandards to be createdmain.goAnd onerootCmdFile, and we have sub-orders.server

1、rootCmd
rootCmdRepresents a basic command without any subcommands

2、&cobra.Command

  • UseCommandThe use of.UseIs a line usage message
  • ShortShortYeshelpA brief description displayed in the command output
  • Run: operation: typical actual working function. Most commands will only achieve this; In addition,PreRunPreRunEPostRunPostRunEAnd so on different periods of operation command, but less use, specific use can also check again

3、rootCmd.AddCommandAddCommandTo this parent command (rootCmdAdd one or more commands

4、serverCmd.Flags().StringVarP()

Generally speaking, we need to be ininit()Definition in functionflagsAnd processing configuration toserverCmd.Flags().StringVarP(&server.ServerPort, "port", "p", "50052", "server port")For example, we have defined aflag, the value is stored in the&server.ServerPortIn, the long command is--port, the short command is-pThe default value is50052The command is described asserver port. This method of calling becomesLocal Flags

Let’s extend it. If it is troublesome to set every sub-command, we can adopt it.Persistent Flags

rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")

Role:

flagIs sustainable, which means thatflagWill be assigned to the command it is assigned to and each command under that command. For global tags, use the tag as a persistent tag on the root.

In addition,Local Flag on Parent CommandsBind Flags with ConfigRequired flagsWait, wait, wait, waittransferJust know

test

Go back togrpc-hello-world/Execute under directorygo run main.go server, see if the output is (the default value should be at this time):

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

carry outgo run main.go server --port=8000 --cert-pem=test-pem --cert-key=test-key --cert-name=test-name, verify that the command line parameters are correct:

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

If everything is correct, congratulationscmdThe module was written correctly, and the next part starts our key chapters!

Five, the server moduleserver

writehello.go

InserverNew file under directoryhello.go, write file content:

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
}

We createdhelloServiceAnd its methodSayHelloWorld, corresponding to.protoTherpc SayHelloWorld, this method requires 2 parameters:ctx context.ContextUsed to accept context parameters,r *pb.HelloWorldRequestFor acceptanceprotobufTheRequestParameters (corresponding.protoThemessage HelloWorldRequest)

:: Preparationserver.go

This small chapter, we write the most important part of the server-side program, involves a large number ofgrpcgrpc-gatewayAnd the application of some network knowledge

1. InpkgNew belowutilDirectory, Newgrpc.goFile, write content:

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)
        }
    })
}

GrpcHandlerFuncThe function is used to determine whether the request originated fromRpcClient orRestful ApiAccording to different requests, register differentServeHTTPServices;r.ProtoMajor == 2It also means that the request must be based onHTTP/2

2. InpkginferiorutilDirectory, newtls.goFile, write content:

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},
    }
}

GetTLSConfigThe function is used to obtain theTLSConfiguration, inside, we readserver.keyAndserver.pemThis type of certificate document

  • tls.X509KeyPair: from a pairPEMThe public key/private key pair is resolved in the encoded data. Success returns a public/private key pair
  • http2.NextProtoTLSNextProtoTLSDuring the negotiationsNPN/ALPNProtocol forTLS settings for HTTP/2
  • tls.Certificate: Returns one or more certificates, which we resolve in essencePEMCalledX509KeyPairThe function declaration of isfunc X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error)The return value isCertificate

Generally speaking, this function is used to process the data obtained from the certificate certificate file (PEM) and finallytls.ConfigAs aHTTP2Use parameters for

3. RevisionserverUnder the directoryserver.goFile, which is the core file in our service, writes:

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,
    }
}

serverProcess analysis

We will divide this large block of code into the following parts to understand it

一、启动监听

net.Listen("tcp", EndPoint)Used to monitor local network address notifications, its prototype functionfunc Listen(network, address string) (Listener, error)

Parameters:networkMust be passed intcptcp4tcp6unixunixpacketIfaddressNull or 0 automatically selects a port number
Return Value: By looking at the source code, we can know that its return value isListener, structural prototype:

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

According to the analysis,Lastnet.ListenIt returns the structure of a listener to the next action, allowing it to perform the next action.Which can perform three types of operations

  • Accept: Accept the wait and return the next connection toListener
  • Close: offListener
  • Addr: returnListenerThe network address of
二、获取TLS

viautil.GetTLSConfigAnalytic resulttls.ConfigTohttp.ServerServingTLSConfigConfiguration item usage

三、创建内部服务

createInternalServerFunction is the core flow part of the whole server

The program usesHTT2HTTPSThat is, support is needed.TLSSo at startupgrpc.NewServerBefore, we have to register the authentication middleware

And what we got earliertlsConfigOnly forHTTPUse, thereforeFirst stepWe want to creategrpcTheTLSCertification certificate

1. CreategrpcTheTLSCertification certificate

Add referencegoogle.golang.org/grpc/credentialsThe third party package of, it implementsgrpcThe various credentials supported by the library, which encapsulate all the states required by the client to authenticate with the server and make various assertions, such as regarding the client’s identity, role, or whether to authorize a particular call.

We callNewServerTLSFromFileTo achieve our goal, it can input the certificate file and the server’s key fileConstruct TLS certificate certificate certificate

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
}

2. Setupgrpc ServerOption

In order togrpc.Creds(creds)For example, its prototype isfunc Creds(c credentials.TransportCredentials) ServerOptionWhich returnsServerOptionWhich sets credentials for the server connection

3. CreategrpcServer side

Function prototype:

func NewServer(opt ...ServerOption) *Server

We have created here agrpcThe server has not yet started to accept the request.

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

4. RegistrationgrpcService

pb.RegisterHelloWorldServer(grpcServer, NewHelloService())

5. Creategrpc-gatewayAssociated component

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: Returns a non-empty empty context. It has not been logged off, has no value, and has no expiration date. It is usually used by the main function, initialization, and testing, and is used as the for incoming requests.Top level context
  • credentials.NewClientTLSFromFile: Construct TLS credentials from the client’s input certificate file
  • grpc.WithTransportCredentials: Configure a connection-level security credential (example:TLSSSL), the return value istype DialOption
  • grpc.DialOptionDialOptionOption configuration how do we set up the connectionDialOptionComposition, determine the content of its set connection)

6. CreateHTTP NewServeMuxAnd registrationgrpc-gatewayLogic

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: returns a newServeMux, its internal mapping is empty;ServeMuxYesgrpc-gatewayA request multiplexer for. It willhttpThe request matches the pattern and calls the corresponding handler
  • RegisterHelloWorldHandlerFromEndpoint: such as function name, registerHelloWorldServingHTTP HandletogrpcEndpoint
  • http.NewServeMuxAssign and return a new ServeMux.
  • mux.Handle: Registers a handler for a given mode

(Looking at the program with doubt) WhygwmuxCan be put inmux.HandleChinese?

First, let’s look at their prototype

(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()TheHandleMethod

func (mux *ServeMux) Handle(pattern string, handler Handler)

Through analysis, we can know that bothNewServeMuxIt is the final return.serveMux,HandlerThe only methods exported in areServeHTTPThe function is to respond to HTTP requests

Let’s go backHandle interfaceWe can draw the conclusion that any structure, as long as it is realizedServeHTTPMethod, this structure can be calledHandle,ServeMuxWill use thisHandlercallServeHTTPMethod handles the request, which iscustomHandler

And here we aregrpc-gatewayRegistered inHTTP HandlerSeamless implantation intonet/httpTheHandleIn the method

Add: IngoAs long as any structure in implements the same method as the interface, it is equivalent to implementing the interface

7. Registration of Specific Services

if err := pb.RegisterHelloWorldHandlerFromEndpoint(ctx, gwmux, EndPoint, dopts); err != nil {
    log.Println("Failed to register gw server: %v\n", err)
}

In this code, we have used the

  • context
  • gateway-grpcRequest multiplexer for
  • Service network address
  • Configured security credentials

RegisteredHelloWorldThis service

四、创建tls.NewListener
func NewListener(inner net.Listener, config *Config) net.Listener {
    l := new(listener)
    l.Listener = inner
    l.config = config
    return l
}

NewListenerA will be createdListenerWhich takes two arguments, the first from insideListenerThe second parameter istls.Config(must contain at least one certificate)

五、服务开始接受请求

In the end we callsrv.Serve(tls.NewListener(conn, tlsConfig)), you can know that it ishttp.ServerThe method of, and need a.ListenerAs a parameter, thenServeWhat has been done internally?

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)
    }
}

At a glance, it creates acontext.Background()Context object and calls theListenerTheAcceptThe method starts accepting external requests and uses them after obtaining connection datanewConnCreate a connection object and use it at the end.goroutineMethod to process connection requests to achieve its purpose

Add: ForHTTP/2Support in callingServeBefore, should besrv.TLSConfigInitializes to the providedListenerTLS configuration for. Ifsrv.TLSConfigNon-zero, and inConfig.NextProtosDoes not contain a string inh2, it is not enabledHTTP/2Support

VI. Verification Function

Writing Test Client

Ingrpc-hello-world/Under the new directoryclient, newclient.goDocument, New Content:

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)
}

Since the client is only for testing purposes, it is simple and should have been classified intocobraUnder the control of the, configuration management and so on should be controllable

After reading this article, you can try to classify the test clients

Start server

Go back togrpc-hello-world/Directory, start the servergo run main.go server, success will only return

2018/02/26 17:19:36 gRPC and https listen on: 50052

Execute test client

Go back toclientDirectory, start the clientgo run client.go, success returns

2018/02/26 17:22:57 Grpc

Execute test Restful Api

curl -X POST -k https://localhost:50052/hello_world -d '{"referer": "restful_api"}'

Return if successful{"message":"restful_api"}


Final directory structure

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

At this point this section is over, recommend itjergooThe article, you have time to look at

In addition, this section involves a lot of knowledge between components, which is worth pondering and is of great significance.

References

Sample code