Go微服务 gRPC - TLS 使用rpc-gatway 不同端口同时提供rpc和http服务

2022-02-25

为什么要这么做:

不管时内部另外一个服务还是外部第三方服务,如果调用者也使用了rpc,可以调用写好的服务端。

如果调用者没有使用rpc而使用了http RESTFUL API ,那就要使用rpc-gatway提供http服务了

简而言之:一个商品详情服务接口即可以提供rpc也可以支持http RESUTFUL API (rpc和api不同的启动端口)

一 安装

go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2
go get google.golang.org/grpc/cmd/protoc-gen-go-grpc

二 COPY

拷贝google到服务端项目目录下的pbfiles中

三 修改.proto文件

syntax = "proto3";

package services;

import "google/api/annotations.proto"; 

option go_package = "../services";

message ProdRequest {
  int32 prod_id = 1;
}

message ProdResponse {
  int32 prod_stock = 1;
}

service ProdService {
  rpc GetProdStock (ProdRequest) returns (ProdResponse) {
    option (google.api.http) = {
      get: "/v1/prod/{prod_id}"
    };
  }
}

执行以下命令:

protoc --go_out=plugins=grpc:../services Prod.proto 
protoc --grpc-gateway_out=logtostderr=true:../services Prod.proto  # 生成一个 xxx.pb.gw.go文件

四 将客户端项目下的client.key和client.crt复制到本项目的keys目录下

五 封装服务端和客户端证书配置

helper/CertHelper.go

package helper

import (
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"log"

	"google.golang.org/grpc/credentials"
)

//GetServerCred 获取服务端证书配置
func GetServerCred() credentials.TransportCredentials {
	// 公钥中读取解析公钥、私钥
	cert, err := tls.LoadX509KeyPair("keys/server.crt", "keys/server.key")
	if err != nil {
		log.Fatal("LoadX509KeyPair error", err)
	}
	// 创建证书池
	certPool := x509.NewCertPool()
	ca, err := ioutil.ReadFile("keys/ca.crt")
	if err != nil {
		log.Fatal("read ca pem error ", err)
	}

	// 解析证书
	if ok := certPool.AppendCertsFromPEM(ca); !ok {
		log.Fatal("AppendCertsFromPEM error ")
	}

	cred := credentials.NewTLS(&tls.Config{
		Certificates: []tls.Certificate{cert},
		ClientAuth:   tls.RequireAndVerifyClientCert,
		ClientCAs:    certPool,
	})

	return cred
}

//GetClientCred 获取客户端证书配置
func GetClientCred() credentials.TransportCredentials {
	pair, err := tls.LoadX509KeyPair("keys/client.crt", "keys/client.key")
	if err != nil {
		log.Fatal("LoadX509KeyPair error ", err)
	}
	certPool := x509.NewCertPool()
	ca, err := ioutil.ReadFile("keys/ca.crt")
	if err != nil {
		log.Fatal("ReadFile ca.crt error ", err)
	}

	if ok := certPool.AppendCertsFromPEM(ca); !ok {
		log.Fatal("certPool.AppendCertsFromPEM error ")
	}

	cred := credentials.NewTLS(&tls.Config{
		Certificates: []tls.Certificate{pair},
		ServerName:   "cc.io",
		RootCAs:      certPool,
	})

	return cred
}

GetClientCred校验客户端http RESTFUL API 使用

server.go

package main

import (
	"fmt"
	"net/http"

	"gorpc.jtthink.co/helper"

	"google.golang.org/grpc"
	"gorpc.jtthink.co/services"
)

func main() {

	rpcServer := grpc.NewServer(grpc.Creds(helper.GetServerCred()))

	services.RegisterProdServiceServer(rpcServer, new(services.ProdService))


	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Println(r.Proto)
		fmt.Println(r.Header)
		fmt.Println(r)
		rpcServer.ServeHTTP(w, r)
	})

	httpServer := &http.Server{
		Addr:    ":8081",
		Handler: mux,
	}

	httpServer.ListenAndServeTLS("keys/server.crt", "keys/server.key")
}

httpServer.go

package main

import (
	"context"
	"log"
	"net/http"

	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"google.golang.org/grpc"
	"gorpc.jtthink.co/helper"
	"gorpc.jtthink.co/services"
)

func main() {

	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)

	defer cancel()

	mux := runtime.NewServeMux()

	//客户端请求时验证
	opts := []grpc.DialOption{grpc.WithTransportCredentials(helper.GetClientCred())}
	err := services.RegisterProdServiceHandlerFromEndpoint(
		ctx,
		mux,
		"localhost:8081",
		opts,
		)

	if err != nil {
		log.Fatal(err)
	}

	httpServer := &http.Server{
		Addr:    ":8080",
		Handler: mux,
	}
	httpServer.ListenAndServe()
}

依次启动

go run server.go
go run client.go # grpccli项目下

结果

➜ go run client.go
20

启动httpServer

go run httpServer.go

浏览器访问http://localhost:8080/v1/prod/123

image-20220305200710760

问题1: import “google/api/annotations.proto”; 引入爆红 不影响使用,暂为解决idea爆红的原因

为题2:

cannot use mux (type *“github.com/grpc-ecosystem/grpc-gateway/runtime”.ServeMux) as type *“github.com/grpc-ecosystem/grpc-gateway/v2/runtime”.ServeMux in argument to services.RegisterProdServiceHandlerFromEndpoint

向services.RegisterProdServiceHandlerFromEndpoint传的参数一摸一样,看报错信息分析一下就能知道问题的原因。引入的版本不一致。

将github.com/grpc-ecosystem/grpc-gateway/runtime改为github.com/grpc-ecosystem/grpc-gateway/v2/runtime即可