1. gRPC提供HTTP服务

1.1 存在的意义

在某些场景下单纯的RPC服务不能满足提供的服务需求的话,还是需要提供HTTP服务作为补充,gRPC一样可以提供HTTP服务。

  • 注意:gRPC提供的HTTP接口是基于HTTP 2.0

1.2 代码示例

package main

import (
	"fmt"
	"gomicro-quickstart/grpc_server/service"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"log"
	"net/http"
)

func main() {
	// 1. 引用证书
	tls, err := credentials.NewServerTLSFromFile("grpc_server/keys/server.crt", "grpc_server/keys/server_no_password.key")
	if err != nil {
		log.Fatal("服务端获取证书失败: ", err)
	}

	// 2. new一个grpc的server,并且加入证书
	rpcServer := grpc.NewServer(grpc.Creds(tls))

	// 3. 将刚刚我们新建的ProdService注册进去
	service.RegisterProdServiceServer(rpcServer, new(service.ProdService))

    // 4. 新建一个路由,并传入rpcServer
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		fmt.Println(request)
		rpcServer.ServeHTTP(writer, request)
	})

	// 5. 定义httpServer,监听8082
	httpServer := http.Server{
		Addr:    ":8082",
		Handler: mux,
	}

    // 6. 以https形式监听httpServer
	httpServer.ListenAndServeTLS("grpc_server/keys/server.crt", "grpc_server/keys/server_no_password.key")
}

1.3 使用postman尝试调用

运行上述的代码,然后postman访问8082端口,提示访问这个接口需要http/2协议

go语言gRPC系列(三) - 使用grpc-gateway同时提供HTTP和gRPC服务-LMLPHP

1.4 gRPC客户端代码调用

针对上一节的客户端调用的代码,我们不需要修改即可以直接访问

我们服务端代码因为打印出了,http request的内容

go语言gRPC系列(三) - 使用grpc-gateway同时提供HTTP和gRPC服务-LMLPHP

所以我们查看一下通过客户端调用,会打印出什么,可以看到

  • 请求的路径是/service.ProdService/GetProductStock,是{服务名}/{方法名}的格式
  • 协议是:http/2

go语言gRPC系列(三) - 使用grpc-gateway同时提供HTTP和gRPC服务-LMLPHP

2. 使用grpc-gateway同时提供HTTP和gRPC服务

2.1 前言

某些场景下需要同时要提供REST API服务gRPC服务,维护两个版本的服务显然不太合理,所以grpc-gateway诞生了。

原理:通过protobuf的自定义option实现了一个网关,服务端同时开启gRPCHTTP 1.1服务,HTTP服务接收客户端请求后转换为grpc请求数据,获取响应后转为json数据返回给客户端。

按照官方的结构说明如图

go语言gRPC系列(三) - 使用grpc-gateway同时提供HTTP和gRPC服务-LMLPHP

2.2 安装

执行安装以下三个

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
go get -u github.com/golang/protobuf/protoc-gen-go

2.3 目录结构

这里用到了google官方Api中的两个proto描述文件,直接拷贝不要做修改,里面定义了protocol buffer扩展的HTTP option,为grpc的http转换提供支持。

go语言gRPC系列(三) - 使用grpc-gateway同时提供HTTP和gRPC服务-LMLPHP

|—— hello_http/
    |—— client/
        |—— main.go   // 客户端
    |—— server/
        |—— main.go   // GRPC服务端
    |—— server_http/
        |—— main.go   // HTTP服务端
|—— proto/
    |—— google       // googleApi http-proto定义
        |—— api
            |—— annotations.proto
            |—— annotations.pb.go
            |—— http.proto
            |—— http.pb.go
    |—— hello_http/
        |—— hello_http.proto   // proto描述文件
        |—— hello_http.pb.go   // proto编译后文件
        |—— hello_http_pb.gw.go // gateway编译后文件

2.4 示例代码

2.4.1 编写proto描述文件:proto/hello_http.proto

SayHello方法定义中增加了http option, POST方式,路由为/example/echo

syntax = "proto3";

package hello_http;
option go_package = "hello_http";

import "google/api/annotations.proto";

// 定义Hello服务
service HelloHTTP {
    // 定义SayHello方法
    rpc SayHello(HelloHTTPRequest) returns (HelloHTTPResponse) {
        // http option
        option (google.api.http) = {
            post: "/example/echo"
            body: "*"
        };
    }
}

// HelloRequest 请求结构
message HelloHTTPRequest {
    string name = 1;
}

// HelloResponse 响应结构
message HelloHTTPResponse {
    string message = 1;
}

2.4.2 编译proto

$ cd 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
$ protoc -I . --go_out=plugins=grpc,Mgoogle/api/annotations.proto=github.com/jergoo/go-grpc-example/proto/google/api:. hello_http/*.proto

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

2.4.3 实现HTTP服务端

package main

import (
    "net/http"

    "github.com/grpc-ecosystem/grpc-gateway/runtime"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/grpclog"

    gw "github.com/jergoo/go-grpc-example/proto/hello_http"
)

func main() {
    // 1. 定义一个context
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    // grpc服务地址
    endpoint := "127.0.0.1:50052"
    mux := runtime.NewServeMux()
    opts := []grpc.DialOption{grpc.WithInsecure()}

    // HTTP转grpc
    err := gw.RegisterHelloHTTPHandlerFromEndpoint(ctx, mux, endpoint, opts)
    if err != nil {
        grpclog.Fatalf("Register handler err:%v\n", err)
    }

    grpclog.Println("HTTP Listen on 8080")
    http.ListenAndServe(":8080", mux)
}

2.4.4 实现gRPC服务端

package main

import (
    "fmt"
    "net"
    "net/http"

    pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入编译生成的包

    "golang.org/x/net/context"
    "golang.org/x/net/trace"
    "google.golang.org/grpc"
    "google.golang.org/grpc/grpclog"
)

const (
    // Address gRPC服务地址
    Address = "127.0.0.1:50052"
)

// 定义helloService并实现约定的接口
type helloService struct{}

// HelloService Hello服务
var HelloService = helloService{}

// SayHello 实现Hello服务接口
func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
    resp := new(pb.HelloResponse)
    resp.Message = fmt.Sprintf("Hello %s.", in.Name)

    return resp, nil
}

func main() {
    listen, err := net.Listen("tcp", Address)
    if err != nil {
        grpclog.Fatalf("failed to listen: %v", err)
    }

    // 实例化grpc Server
    s := grpc.NewServer()

    // 注册HelloService
    pb.RegisterHelloServer(s, HelloService)

    grpclog.Println("Listen on " + Address)
    s.Serve(listen)
}

2.4.5 实现客户端

package main

import (
    pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入proto包
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/grpclog"
)

const (
    // Address gRPC服务地址
    Address = "127.0.0.1:50052"
)

func main() {
    // 连接
    conn, err := grpc.Dial(Address, grpc.WithInsecure())
    if err != nil {
        grpclog.Fatalln(err)
    }
    defer conn.Close()

    // 初始化客户端
    c := pb.NewHelloClient(conn)

    // 调用方法
    req := &pb.HelloRequest{Name: "gRPC"}
    res, err := c.SayHello(context.Background(), req)

    if err != nil {
        grpclog.Fatalln(err)
    }

    grpclog.Println(res.Message)
}

2.5 运行并调用

依次开启gRPC服务端和HTTP服务端

$ cd hello_http/server && go run main.go
Listen on 127.0.0.1:50052

$ cd hello_http/server_http && go run main.go
HTTP Listen on 8080

然后调用gRPC的客户端

$ cd hello_http/client && go run main.go
Hello gRPC.

# HTTP 请求
$ curl -X POST -k http://localhost:8080/example/echo -d '{"name": "gRPC-HTTP is working!"}'
{"message":"Hello gRPC-HTTP is working!."}
08-16 07:09