golang gRPC转换HTTP对外提供服务

1,496 阅读1分钟

实现了一个协议转换的网关,对应github上的项目github.com/grpc-ecosys… ,这个网关负责接收客户端请求,然后决定直接转发给grpc服务还是转给http服务,当然,http服务也需要请求grpc服务获取响应,然后转为json响应给客户端。结构如图:

grpc-rest-gateway.png

安装工具

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

目录结构

├─grpcgateway
│  ├─echohttp
│  │  ├─client
│  │  │      main.go
│  │  │
│  │  ├─server
│  │  │      main.go
│  │  │
│  │  └─server_http
│  │          main.go
│  │
│  └─protos
│      ├─echo
│      │      service.pb.go
│      │      service.pb.gw.go
│      │      service.proto
│      │      service_grpc.pb.go
│      │
│      └─google
│          └─api
│                  annotations.pb.go
│                  annotations.proto
│                  http.pb.go
│                  http.proto

demo

$ cd protos

1.annotations.proto 和 http.proto 从 github.com/googleapis/… 拷贝到本地

2.echo/service.proto:

syntax = "proto3";

option go_package = "./echo";

package echo;

import "google/api/annotations.proto";

service EchoService {
    rpc Echo(EchoMessage) returns (EchoMessage) {
        option (google.api.http) = {
            post: "/v1/echo"
            body: "*"
        };
    }
}

message EchoMessage {
    string value = 1;
}

3.编译proto

编译google.api

$ protoc -I . --go_out=paths=source_relative:. --go_opt=Mgoogle/protobuf/descriptor.proto=github.com/golang/protobuf/protoc-gen-go/descriptor:.  google/api/*.proto

编译echo.proto

$ protoc -I . --go_out=. --go_opt=Mgoogle/api/annotations.proto=./google/api:. --go-grpc_out=. --go-grpc_opt=Mgoogle/api/annotations.proto=./google/api:. echo/*.proto

编译echo.proto gateway

$ protoc --grpc-gateway_out=logtostderr=true:. echo/*.proto

4.实现服务端和客户端 server/main.go

package main

import (
	"fmt"
	"net"

	pb "zpool/grpcgateway/protos/echo"

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

const (
	Address = "127.0.0.1:50052"
)

type echoService struct {
	pb.UnimplementedEchoServiceServer
}

func (*echoService) Echo(ctx context.Context, in *pb.EchoMessage) (*pb.EchoMessage, error) {
	return &pb.EchoMessage{
		Value: in.Value,
	}, nil
}

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

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

	// 注册echoService
	pb.RegisterEchoServiceServer(s, &echoService{})

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

client/main.go

package main

import (
	"fmt"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/grpclog"

	pb "zpool/grpcgateway/protos/echo"
)

const (
	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.NewEchoServiceClient(conn)

	// 调用方法
	req := &pb.EchoMessage{Value: "gRPC"}
	res, err := c.Echo(context.Background(), req)

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

	fmt.Println(res.Value)
}

5.server_http/main.go

package main

import (
	"fmt"
	"net/http"

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

	gw "zpool/grpcgateway/protos/echo"
)

func main() {
	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.RegisterEchoServiceHandlerFromEndpoint(ctx, mux, endpoint, opts)
	if err != nil {
		grpclog.Fatalf("Register handler err:%v\n", err)
	}

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

6.Test

grpc请求:

go run server/main.go
go run client/main.go

http请求:

go run server/main.go
go run server_http/main.go

curl -X POST -k http://localhost:8080/v1/echo  -d '{"value": "gRPC-HTTP is working!"}'