gRPC之gRPC转换HTTP

news2024/11/6 21:36:51

1、gRPC转换HTTP

我们通常把RPC用作内部通信,而使用Restful Api进行外部通信。为了避免写两套应用,我们使用grpc-

gateway gRPC转成HTTP。服务接收到HTTP请求后,grpc-gateway把它转成gRPC进行处理,然后以JSON

形式返回数据。本篇代码最终转成的Restful Api支持bearer token验证、数据验证,并添加swagger文档。

1.1 编写proto

// simple.proto
// 协议为proto3
syntax = "proto3";
package proto;
option go_package = "./simple;proto";

import "validator.proto";
import "google/api/annotations.proto";

message InnerMessage {
    // some_integer can only be in range (1, 100).
    int32 some_integer = 1 [(validator.field) = {int_gt: 0, int_lt: 100}];
    // some_float can only be in range (0;1).
    double some_float = 2 [(validator.field) = {float_gte: 0, float_lte: 1}];
}

message OuterMessage {
    // important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax).
    string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}];
    // proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage.
    InnerMessage inner = 2 [(validator.field) = {msg_exists : true}];
}

service Simple{
    rpc Route (InnerMessage) returns (OuterMessage){
        option (google.api.http) ={
            post:"/v1/example/route"
            body:"*"
        };
    }
}

可以看到,proto变化不大,只是添加了API的路由路径:

option (google.api.http) ={
	post:"/v1/example/route"
    body:"*"
};

annotations.proto文件的内容:

// ./proto/google/api/annotations.proto
syntax = "proto3";

package google.api;

option go_package = "google/api;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 {

    HttpRule http = 72295728;

}

http.proto文件的内容:

// ./proto/google/api/http.proto
syntax = "proto3";

package google.api;

option go_package = "google/api;google_api";

option cc_enable_arenas = true;
option java_multiple_files = true;
option java_outer_classname = "HttpProto";
option java_package = "com.google.api";

message Http {

    repeated HttpRule rules = 1;
}

message HttpRule {

    string selector = 1;

    oneof pattern {
        string get = 2;

        string put = 3;

        string post = 4;

        string delete = 5;

        string patch = 6;

        CustomHttpPattern custom = 8;
    }

    string body = 7;

    repeated HttpRule additional_bindings = 11;
}

message CustomHttpPattern {

    string kind = 1;

    string path = 2;
}

1.2 编译proto

$ cd proto

# 编译google.api
$ protoc -I . --go_out=plugins=grpc:. google/api/*.proto

# 编译simple.proto
$ protoc -I . --go_out=plugins=grpc:. simple/*.proto

# 编译simple.proto gateway
$ protoc --grpc-gateway_out=logtostderr=true:. simple/simple.proto

# 编译simple.proto validator
$ protoc --govalidators_out=. --go_out=plugins=grpc:. simple/simple.proto

以上完成proto编译,接着修改服务端代码。

1.3 项目结构

# 项目结构
$ tree demo/
demo/
├── cert
│   ├── ca.crt
│   ├── ca.csr
│   ├── ca.key
│   ├── ca.srl
│   ├── client
│   │   ├── client.csr
│   │   ├── client.key
│   │   └── client.pem
│   ├── openssl.cnf
│   └── server
│       ├── server.csr
│       ├── server.key
│       └── server.pem
├── client
│   ├── auth
│   │   └── auth.go
│   └── client.go
├── go.mod
├── go.sum
├── proto
│   ├── google
│   │   └── api
│   │       ├── annotations.pb.go
│   │       ├── annotations.proto
│   │       ├── http.pb.go
│   │       └── http.proto
│   ├── simple
│   │   ├── simple.pb.go
│   │   ├── simple.pb.gw.go
│   │   ├── simple.proto
│   │   └── simple.validator.pb.go
│   └── validator.proto
└── server
    ├── gateway
    │   └── gateway.go
    ├── log
    │   └── debug.log
    ├── middleware
    │   ├── auth
    │   │   └── auth.go
    │   ├── cred
    │   │   └── cred.go
    │   ├── recovery
    │   │   └── recovery.go
    │   └── zap
    │       └── zap.go
    └── server.go

17 directories, 31 files
1.3.1 gateway.go
package gateway

import (
	"context"
	"crypto/tls"
	pb "demo/proto/simple"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"golang.org/x/net/http2"
	"golang.org/x/net/http2/h2c"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

// ProvideHTTP 把gRPC服务转成HTTP服务,让gRPC同时支持HTTP
func ProvideHTTP(endpoint string, grpcServer *grpc.Server) *http.Server {
	ctx := context.Background()
	//获取证书
	creds, err := credentials.NewClientTLSFromFile("../cert/server/server.pem", "test.example.com")
	if err != nil {
		log.Fatalf("Failed to create TLS credentials %v", err)
	}
	//添加证书
	dopts := []grpc.DialOption{grpc.WithTransportCredentials(creds)}
	//新建gwmux,它是grpc-gateway的请求复用器。它将http请求与模式匹配,并调用相应的处理程序。
	gwmux := runtime.NewServeMux()
	//将服务的http处理程序注册到gwmux。处理程序通过endpoint转发请求到grpc端点
	err = pb.RegisterSimpleHandlerFromEndpoint(ctx, gwmux, endpoint, dopts)
	if err != nil {
		log.Fatalf("Register Endpoint err: %v", err)
	}
	//新建mux,它是http的请求复用器
	mux := http.NewServeMux()
	//注册gwmux
	mux.Handle("/", gwmux)
	log.Println(endpoint + " HTTP.Listing with TLS and token...")
	return &http.Server{
		Addr:      endpoint,
		Handler:   grpcHandlerFunc(grpcServer, mux),
		TLSConfig: getTLSConfig(),
	}
}

// grpcHandlerFunc 根据不同的请求重定向到指定的Handler处理
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
	return h2c.NewHandler(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)
		}
	}), &http2.Server{})
}

// getTLSConfig获取TLS配置
func getTLSConfig() *tls.Config {
	cert, _ := ioutil.ReadFile("../cert/server/server.pem")
	key, _ := ioutil.ReadFile("../cert/server/server.key")
	var demoKeyPair *tls.Certificate
	pair, err := tls.X509KeyPair(cert, key)
	if err != nil {
		log.Fatalf("TLS KeyPair err: %v\n", err)
	}
	demoKeyPair = &pair
	return &tls.Config{
		Certificates: []tls.Certificate{*demoKeyPair},
		NextProtos:   []string{http2.NextProtoTLS}, // HTTP2 TLS支持
	}
}
1.3.2 server.go
package main

import (
	"context"
	"crypto/tls"
	pb "demo/proto/simple"
	"demo/server/gateway"
	"demo/server/middleware/auth"
	"demo/server/middleware/cred"
	"demo/server/middleware/recovery"
	"demo/server/middleware/zap"
	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
	grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
	grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
	grpc_validator "github.com/grpc-ecosystem/go-grpc-middleware/validator"
	"google.golang.org/grpc"
	"log"
	"net"
)

// SimpleService 定义我们的服务
type SimpleService struct{}

// Route 实现Route方法
func (s *SimpleService) Route(ctx context.Context, req *pb.InnerMessage) (*pb.OuterMessage, error) {
	res := pb.OuterMessage{
		ImportantString: "hello grpc validator",
		Inner:           req,
	}
	return &res, nil
}

const (
	// Address 监听地址
	Address string = "127.0.0.1:8000"
	// Network 网络通信协议
	Network string = "tcp"
)

func main() {
	// 监听本地端口
	listener, err := net.Listen(Network, Address)
	if err != nil {
		log.Fatalf("net.Listen err: %v", err)
	}
	// 新建gRPC服务器实例
	grpcServer := grpc.NewServer(
		cred.TLSInterceptor(),
		grpc.StreamInterceptor(
			grpc_middleware.ChainStreamServer(
				grpc_validator.StreamServerInterceptor(),
				grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()),
				grpc_auth.StreamServerInterceptor(auth.AuthInterceptor),
				grpc_recovery.StreamServerInterceptor(recovery.RecoveryInterceptor()),
			)),
		grpc.UnaryInterceptor(
			grpc_middleware.ChainUnaryServer(
				grpc_validator.UnaryServerInterceptor(),
				grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()),
				grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor),
				grpc_recovery.UnaryServerInterceptor(recovery.RecoveryInterceptor()),
			)),
	)
	// 在gRPC服务器注册我们的服务
	pb.RegisterSimpleServer(grpcServer, &SimpleService{})
	log.Println(Address + " net.Listing with TLS and token...")
	//使用gateway把grpcServer转成httpServer
	httpServer := gateway.ProvideHTTP(Address, grpcServer)
	//用服务器 Serve() 方法以及我们的端口信息区实现阻塞等待,直到进程被杀死或者 Stop() 被调用
	if err = httpServer.Serve(tls.NewListener(listener, httpServer.TLSConfig)); err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
1.3.3 auth.go
package auth

import (
	"context"
	"errors"
	grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
)

// TokenInfo 用户信息
type TokenInfo struct {
	ID    string
	Roles []string
}

// AuthInterceptor 认证拦截器,对以authorization为头部,形式为`bearer token`的Token进行验证
func AuthInterceptor(ctx context.Context) (context.Context, error) {
	token, err := grpc_auth.AuthFromMD(ctx, "bearer")
	if err != nil {
		return nil, err
	}
	tokenInfo, err := parseToken(token)
	if err != nil {
		return nil, grpc.Errorf(codes.Unauthenticated, " %v", err)
	}
	//使用context.WithValue添加了值后,可以用Value(key)方法获取值
	newCtx := context.WithValue(ctx, tokenInfo.ID, tokenInfo)
	//log.Println(newCtx.Value(tokenInfo.ID))
	return newCtx, nil
}

//解析token,并进行验证
func parseToken(token string) (TokenInfo, error) {
	var tokenInfo TokenInfo
	if token == "grpc.auth.token" {
		tokenInfo.ID = "1"
		tokenInfo.Roles = []string{"admin"}
		return tokenInfo, nil
	}
	return tokenInfo, errors.New("Token无效: bearer " + token)
}

//从token中获取用户唯一标识
func userClaimFromToken(tokenInfo TokenInfo) string {
	return tokenInfo.ID
}
1.3.4 cred.go
package cred

import (
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"log"
)

// TLSInterceptor TLS证书认证
func TLSInterceptor() grpc.ServerOption {
	// 从输入证书文件和密钥文件为服务端构造TLS凭证
	creds, err := credentials.NewServerTLSFromFile("../cert/server/server.pem", "../cert/server/server.key")
	if err != nil {
		log.Fatalf("Failed to generate credentials %v", err)
	}
	return grpc.Creds(creds)
}
1.3.5 recovery.go
package recovery

import (
	grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
)

// RecoveryInterceptor panic时返回Unknown错误吗
func RecoveryInterceptor() grpc_recovery.Option {
	return grpc_recovery.WithRecoveryHandler(func(p interface{}) (err error) {
		return grpc.Errorf(codes.Unknown, "panic triggered: %v", p)
	})
}
1.3.6 zap.go
package zap

import (
	grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

// ZapInterceptor返回zap.logger实例(把日志写到文件中)
func ZapInterceptor() *zap.Logger {
	w := zapcore.AddSync(&lumberjack.Logger{
		Filename:  "log/debug.log",
		MaxSize:   1024, //MB
		LocalTime: true,
	})
	config := zap.NewProductionEncoderConfig()
	config.EncodeTime = zapcore.ISO8601TimeEncoder
	core := zapcore.NewCore(
		zapcore.NewJSONEncoder(config),
		w,
		zap.NewAtomicLevel(),
	)
	logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
	grpc_zap.ReplaceGrpcLogger(logger)
	return logger
}

// ZapInterceptor 返回zap.logger实例(把日志输出到控制台)
// func ZapInterceptor() *zap.Logger {
// 	logger, err := zap.NewDevelopment()
// 	if err != nil {
// 		log.Fatalf("failed to initialize zap logger: %v", err)
// 	}
// 	grpc_zap.ReplaceGrpcLogger(logger)
// 	return logger
// }
1.3.7 client.go
package main

import (
	"context"
	"demo/client/auth"
	pb "demo/proto/simple"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"log"
)

// Address 连接地址
const Address string = ":8000"

var grpcClient pb.SimpleClient

func main() {
	//从输入的证书文件中为客户端构造TLS凭证
	creds, err := credentials.NewClientTLSFromFile("../cert/server/server.pem", "test.example.com")
	if err != nil {
		log.Fatalf("Failed to create TLS credentials %v", err)
	}
	//构建Token
	token := auth.Token{
		Value: "bearer grpc.auth.token",
	}
	// 连接服务器
	conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))
	if err != nil {
		log.Fatalf("net.Connect err: %v", err)
	}
	defer conn.Close()
	// 建立gRPC连接
	grpcClient = pb.NewSimpleClient(conn)
	route()
}

// route 调用服务端Route方法
func route() {
	// 创建发送结构体
	req := pb.InnerMessage{
		SomeInteger: 99,
		SomeFloat:   1,
	}
	// 调用我们的服务(Route方法)
	// 同时传入了一个 context.Context ,在有需要时可以让我们改变RPC的行为,比如超时/取消一个正在运行的RPC
	res, err := grpcClient.Route(context.Background(), &req)
	if err != nil {
		log.Fatalf("Call Route err: %v", err)
	}
	// 打印返回值
	log.Println(res)
}
1.3.8 auth.go
package auth

import (
	"context"
)

// Token token认证
type Token struct {
	Value string
}

const headerAuthorize string = "authorization"

// GetRequestMetadata 获取当前请求认证所需的元数据
func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{headerAuthorize: t.Value}, nil
}

// RequireTransportSecurity 是否需要基于 TLS 认证进行安全传输
func (t *Token) RequireTransportSecurity() bool {
	return true
}

1.4 测试

[root@zsx server]# go run server.go
2023/02/12 13:31:42 127.0.0.1:8000 net.Listing with TLS and token...
2023/02/12 13:31:42 127.0.0.1:8000 HTTP.Listing with TLS and token...
[root@zsx client]# go run client.go
2023/02/12 13:31:50 important_string:"hello grpc validator" inner:{some_integer:99 some_float:1}

1.5 发送http请求

[root@zsx client]# curl -X POST -k https://127.0.0.1:8000/v1/example/route --header 'Authorization: Bearer grpc.auth.token' -d '{"some_integer": 10,"some_float": 1}'
{"important_string":"hello grpc validator","inner":{"some_integer":10,"some_float":1}}

会进行输入的校验:

[root@zsx client]# curl -X POST -k https://127.0.0.1:8000/v1/example/route --header 'Authorization: Bearer grpc.auth.token' -d '{"some_integer": 200,"some_float": 1}'
{"error":"invalid field SomeInteger: value '200' must be less than '100'","code":3,"message":"invalid field SomeInteger: value '200' must be less than '100'"}

会进行auth的验证:

[root@zsx client]# curl -X POST -k https://127.0.0.1:8000/v1/example/route -d '{"some_integer": 10,"some_float": 1}'
{"error":"Request unauthenticated with bearer","code":16,"message":"Request unauthenticated with bearer"}

在上面可以看到,我们的gRPC服务已经同时支持RPCHTTP请求了,而且API接口支持bearer token验证和数

据验证。为了方便对接,我们把API接口生成swagger文档。

1.6 生成swagger文档

1.6.1 生成swagger文档(simple.swagger.json)

1、安装protoc-gen-swagger

$ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
$ go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger

2、编译生成simple.swagger.json

$ cd proto
$ protoc --swagger_out=logtostderr=true:. simple/simple.proto
1.6.2 把swagger-ui转成Go代码,备用

1、下载swagger-ui

https://github.com/swagger-api/swagger-ui下载包,解压把dist目录下的所有文件拷贝我们项目的

server/swagger/swagger-ui/目录下。

$ tree ./server/swagger/swagger-ui/
./server/swagger/swagger-ui/
├── favicon-16x16.png
├── favicon-32x32.png
├── index.css
├── index.html
├── oauth2-redirect.html
├── swagger-initializer.js
├── swagger-ui-bundle.js
├── swagger-ui-bundle.js.map
├── swagger-ui.css
├── swagger-ui.css.map
├── swagger-ui-es-bundle-core.js
├── swagger-ui-es-bundle-core.js.map
├── swagger-ui-es-bundle.js
├── swagger-ui-es-bundle.js.map
├── swagger-ui.js
├── swagger-ui.js.map
├── swagger-ui-standalone-preset.js
└── swagger-ui-standalone-preset.js.map

0 directories, 18 files

2、把Swagger UI转换为Go代码

安装go-bindata

$ go get -u github.com/jteeuwen/go-bindata/...
$ go install github.com/jteeuwen/go-bindata/...

回到server/所在目录,运行指令把Swagger UI转成Go代码:

$ go-bindata --nocompress -pkg swagger -o swagger/datafile.go swagger/swagger-ui/...

swagger目录下回生成datafile.go文件。

1.6.3 对外提供swagger-ui

1、在swagger/目录下新建swagger.go文件

package swagger

import (
	assetfs "github.com/elazarl/go-bindata-assetfs"
	"log"
	"net/http"
	"path"
	"strings"
)

// ServeSwaggerFile把proto文件夹中的swagger.json文件暴露出去
func ServeSwaggerFile(w http.ResponseWriter, r *http.Request) {
	if !strings.HasSuffix(r.URL.Path, "swagger.json") {
		log.Printf("Not Found: %s", r.URL.Path)
		http.NotFound(w, r)
		return
	}
	p := strings.TrimPrefix(r.URL.Path, "/swagger/")
	// "../proto/"为.swagger.json所在目录
	p = path.Join("../proto/simple/", p)
	log.Printf("Serving swagger-file: %s", p)
	http.ServeFile(w, r, p)
}

// ServeSwaggerUI 对外提供swagger-ui
func ServeSwaggerUI(mux *http.ServeMux) {
	fileServer := http.FileServer(&assetfs.AssetFS{
		Asset:    Asset,
		AssetDir: AssetDir,
		Prefix:   "swagger/swagger-ui", //swagger-ui文件夹所在目录
	})
	prefix := "/swagger-ui/"
	mux.Handle(prefix, http.StripPrefix(prefix, fileServer))
}

2、注册swagger

gateway.go中添加如下代码:

//新建mux,它是http的请求复用器
mux := http.NewServeMux()
//注册gwmux
mux.Handle("/", gwmux)
//注册swagger
mux.HandleFunc("/swagger/", swagger.ServeSwaggerFile)
swagger.ServeSwaggerUI(mux)

3、到这里我们已经完成了swagger文档的添加工作了,重新运行server.go

[root@zsx server]# go run server.go
2023/02/12 16:59:27 127.0.0.1:8000 net.Listing with TLS and token...
2023/02/12 16:59:27 127.0.0.1:8000 HTTP.Listing with TLS and token...

由于谷歌浏览器不能使用自己制作的TLS证书,所以我们用火狐浏览器进行测试。

用火狐浏览器打开:https://127.0.0.1:8000/swagger-ui/

在这里插入图片描述

在最上面地址栏输入:https://127.0.0.1:8000/swagger/simple.swagger.json

在这里插入图片描述

然后就可以看到swagger生成的API文档了:

在这里插入图片描述

还有个问题,我们使用了bearer token进行接口验证的,怎么把bearer token也添加到swagger中呢?

grpc-gatewayGitHub上的这个Issues找到解决办法

https://github.com/grpc-ecosystem/grpc-gateway/issues/1089

1.6.4 在swagger中配置bearer token

1、修改simple.proto文件

// 协议为proto3
syntax = "proto3";
package proto;
option go_package = "./simple;proto";

import "validator.proto";
import "google/api/annotations.proto";
import "protoc-gen-swagger/options/annotations.proto";

message InnerMessage {
    // some_integer can only be in range (1, 100).
    int32 some_integer = 1 [(validator.field) = {int_gt: 0, int_lt: 100}];
    // some_float can only be in range (0;1).
    double some_float = 2 [(validator.field) = {float_gte: 0, float_lte: 1}];
}

message OuterMessage {
    // important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax).
    string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}];
    // proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage.
    InnerMessage inner = 2 [(validator.field) = {msg_exists : true}];
}

option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
    security_definitions: {
        security: {
            key: "bearer"
            value: {
                type: TYPE_API_KEY
                in: IN_HEADER
                name: "Authorization"
                description: "Authentication token, prefixed by Bearer: Bearer <token>"
            }
        }
    }

    security: {
        security_requirement: {
            key: "bearer"
        }
    }

    info: {
        title: "grpc gateway sample";
        version: "1.0";
        license: {
            name: "MIT";
        };
    }

    schemes: HTTPS
};

service Simple{
    rpc Route (InnerMessage) returns (OuterMessage){
        option (google.api.http) ={
            post:"/v1/example/route"
            body:"*"
        };
        // 禁用bearer token
        // option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {
        //   security: { } // Disable security key
        // };
    }
}

2、重新编译生成simple.swagger.json

$ cd proto
$ protoc --swagger_out=logtostderr=true:. simple/simple.proto
1.6.5 验证测试

1、添加bearer token

在这里插入图片描述

填写token:

在这里插入图片描述

2、调用接口,正确返回数据

填写数据:

在这里插入图片描述

进行测试:

在这里插入图片描述

3、传递不合规则的数据,返回违反数据验证逻辑错误

填写数据:

在这里插入图片描述

进行测试:

在这里插入图片描述

# 项目结构
$ tree demo/
demo/
├── cert
│   ├── ca.crt
│   ├── ca.csr
│   ├── ca.key
│   ├── ca.srl
│   ├── client
│   │   ├── client.csr
│   │   ├── client.key
│   │   └── client.pem
│   ├── openssl.cnf
│   └── server
│       ├── server.csr
│       ├── server.key
│       └── server.pem
├── client
│   ├── auth
│   │   └── auth.go
│   └── client.go
├── go.mod
├── go.sum
├── proto
│   ├── google
│   │   └── api
│   │       ├── annotations.pb.go
│   │       ├── annotations.proto
│   │       ├── http.pb.go
│   │       └── http.proto
│   ├── protoc-gen-swagger
│   │   └── options
│   │       ├── annotations.proto
│   │       └── openapiv2.proto
│   ├── simple
│   │   ├── simple.pb.go
│   │   ├── simple.pb.gw.go
│   │   ├── simple.proto
│   │   ├── simple.swagger.json
│   │   └── simple.validator.pb.go
│   └── validator.proto
└── server
    ├── gateway
    │   └── gateway.go
    ├── log
    │   └── debug.log
    ├── middleware
    │   ├── auth
    │   │   └── auth.go
    │   ├── cred
    │   │   └── cred.go
    │   ├── recovery
    │   │   └── recovery.go
    │   └── zap
    │       └── zap.go
    ├── server.go
    └── swagger
        ├── datafile.go
        ├── swagger.go
        └── swagger-ui
            ├── favicon-16x16.png
            ├── favicon-32x32.png
            ├── index.css
            ├── index.html
            ├── oauth2-redirect.html
            ├── swagger-initializer.js
            ├── swagger-ui-bundle.js
            ├── swagger-ui-bundle.js.map
            ├── swagger-ui.css
            ├── swagger-ui.css.map
            ├── swagger-ui-es-bundle-core.js
            ├── swagger-ui-es-bundle-core.js.map
            ├── swagger-ui-es-bundle.js
            ├── swagger-ui-es-bundle.js.map
            ├── swagger-ui.js
            ├── swagger-ui.js.map
            ├── swagger-ui-standalone-preset.js
            └── swagger-ui-standalone-preset.js.map

21 directories, 54 files

1.7 总结

本篇介绍了如何使用grpc-gatewaygRPC同时支持HTTP,最终转成的Restful Api支持bearer token

证、数据验证。同时生成swagger文档,方便API接口对接。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1115110.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FOC程序算法编写

1、FOC多路PWM驱动 在BLDC电机控制上&#xff0c;6路PWM 控制模式比3路PWM更自由&#xff0c;因为6个半桥式晶体管的每一个都可以单独控制。 &#xff08;1&#xff09;3PWM &#xff08;2&#xff09;6PWM 2、死区时间 开关元器件的和严格意义并不是相同的。IGBT&#xff0c;…

驱动编写LED灯

demo.c #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/uaccess.h> #include<linux/io.h> #include "head.h" unsigned int major; char kbuf[128] {}; // 定义三个指针指向映射后的虚拟…

vue3.0 + element plus upload文件上传

提供了&#xff0c;文件超过指定个数&#xff0c;页面做了隐藏的效果 记录一下

HTTPS、SSL/TLS,HTTPS运行过程,RSA加密算法,AES加密算法

1、为什么网站要使用安全证书 我们所处的网络环境是复杂多样的&#xff0c;大致分为两类&#xff0c;一类是可信的网络服务商&#xff0c;比如直接连的电信运营商的网络&#xff0c;网线&#xff0c;4G&#xff0c;5G&#xff1b;另一类是不可信的网络&#xff0c;比如WIFI&am…

libpcap之零拷贝mmap

一、用户空间 在通过socket(AF_PACKET,…)创建fd后&#xff0c;进行接收队列的建立 //pcap-linux.c static int pcap_activate_linux(pcap_t *handle) { ...ret setup_mmapped(handle, &status); ... }1.1 设置默认ring bufer size static int setup_mmapped(pcap_t *h…

点云处理【六】(点云分割)

点云分割 第一章 点云数据采集 1. 点云分割 点云数据中包含目标物体&#xff0c;点云分割算法即将物体分割出来。 2 分割算法 2.1 RANSAC(随机采样一致性)方法 基于随机采样一致性的分割的步骤如下&#xff1a; 1.从一个样本集S中&#xff0c;随机抽取n个样本&#xff0c;…

下拉选择框监听el-option的方式

<el-select v-model"form.expenseType" placeholder"请选择费用类型" clearable filterable size"small"><el-option v-for"item in expenseNameList" :key"item.value" :label"item.label" :value"…

发现学习的新契机——广东开放大学电大搜题服务

广东开放大学一直以来致力于为广大学子提供优质的教育资源和学习支持&#xff0c;而最新推出的电大搜题微信公众号更进一步满足了学生们对于学习资料的需求。这一全新的学习辅助工具将为学子们带来便捷、高效的学习体验。 无论是传统的广播电视大学学生&#xff0c;还是广东开…

首届中国虚拟艺术巡展 NFT Showcase 圆满落幕!

由 Web3 营销先锋 Beep Crypto 精心策划&#xff0c;于 10 月 5 日至 10 月 17 日在广州潮流策展空间 YCC! 天宜隆重呈现。 本届展览以“虚拟时尚”为主题&#xff0c;融汇了众多的数字艺术展品&#xff0c;包括诸如 RTFKT X Nike、Gucci X 10KTF、Tiffany X Cryptopunks、Mee…

python二次开发Solidworks:读取立方体的高度

在SW中新建一个零件文档&#xff0c;建立一个立方体&#xff0c;长度和宽度自定义&#xff0c;高度100mm&#xff0c;下面通过python实现读取该立方体的高度&#xff1a; import win32com.client as win32 import pythoncomswApp win32.Dispatch(sldworks.application) swApp.…

使用telegram机器人发送通知

文章目录 背景1 创建机器人2 与机器人的会话3 调用API让机器人发送消息 背景 在训练深度学习模型时&#xff0c;除了粗略估计外&#xff0c;很难预测训练何时结束。此外&#xff0c;我们可能还想随时随地查看训练情况&#xff0c;如果每次都需要登录回服务器的话并不方便。因此…

RK3568笔记四:基于TensorFlow花卉图像分类部署

若该文为原创文章&#xff0c;转载请注明原文出处。 基于正点原子的ATK-DLRK3568部署测试。 花卉图像分类任务&#xff0c;使用使用 tf.keras.Sequential 模型&#xff0c;简单构建模型&#xff0c;然后转换成 RKNN 模型部署到ATK-DLRK3568板子上。 在 PC 使用 Windows 系统…

项目环境配置

一、后端环境搭建-前后端联调 1.1 nginx反向代理 1.2 nginx负载均衡配置 二、完善登录功能 处理密码明文&#xff0c;防止密码被别人获取破坏系统 MD5加密是单向的不可逆的 &#xff0c;不能由加密后的换算加密前的 //密码比对password DigestUtils.md5DigestAsHex ( pa…

二叉树,堆排序及TopK问题

要讲二叉树的概念&#xff0c;就要先讲树的概念。 树是什么呢&#xff1f; 树其实是一种储存数据的结构&#xff0c;因为他的结构倒过来和生活中的树很相似所以才被称之为树。 这是一颗多叉树&#xff0c;从最顶端的节点可以找到下边的几个节点&#xff0c;下边的节点又可以找…

Apollo:前端开发者的全栈探索之旅

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

【C语言必知必会 | 第四篇】一文带你精通顺序结构

引言 C语言是一门面向过程的、抽象化的通用程序设计语言&#xff0c;广泛应用于底层开发。它在编程语言中具有举足轻重的地位。 此文为【C语言必知必会】系列第四篇&#xff0c;进行C语言顺序结构的专项练习&#xff0c;结合专题优质题目&#xff0c;带领读者从0开始&#xff0…

Halcon手眼标定

手眼标定&#xff08;参考&#xff1a;B站王佳琪老师) 这里说的手眼标定中的手指的是机械手或者电机运动的轴&#xff0c;眼表示摄像头 就是两个空间坐标系的转换&#xff0c;这个转换需要一个转换矩阵&#xff0c;那么转换矩阵需要根据两个坐标系的对应的九个点来通过vec_to…

如何用记事本制作一个简陋的小网页(3)——注册信息表

目录 前提须知&#xff1a; 一、表格建立之前&#xff1a; 二、表格的建立&#xff1a; 三、信息表的内容填充&#xff1a; 1.昵称 和 电话 &#xff1a; 2.密码&#xff1a; 3.性别&#xff1a; 4. 爱好&#xff1a; 5.民族&#xff1a; 6. 出生日期&#xff1a; 7.…

Python用selenium实现自动登录和下单的项目实战

本文主要介绍了Python用selenium实现自动登录和下单的项目实战&#xff0c;文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值&#xff0c;需要的朋友们下面随着小编来一起学习学习吧− 前言 学python对selenium应该不陌生吧 Selenium…