go语言gRPC系列(三) - 使用grpc-gateway同时提供HTTP和gRPC服务
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协议

1.4 gRPC客户端代码调用
针对上一节的客户端调用的代码,我们不需要修改即可以直接访问
即直接调用protoc产生的go文件中的方法
我们服务端代码因为打印出了,http request的内容

所以我们查看一下通过客户端调用,会打印出什么,可以看到
- 请求的路径是
/service.ProdService/GetProductStock,是{服务名}/{方法名}的格式 - 协议是:http/2

2. 使用grpc-gateway同时提供HTTP和gRPC服务
2.1 前言
某些场景下需要同时要提供REST API服务和gRPC服务,维护两个版本的服务显然不太合理,所以grpc-gateway诞生了。
原理:通过protobuf的自定义option实现了一个网关,服务端同时开启gRPC和HTTP 1.1服务,HTTP服务接收客户端请求后转换为grpc请求数据,获取响应后转为json数据返回给客户端。
按照官方的结构说明如图:

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转换提供支持。

|—— 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!."}
go语言gRPC系列(三) - 使用grpc-gateway同时提供HTTP和gRPC服务的更多相关文章
- 【数据结构(C语言版)系列三】 队列
队列的定义 队列是一种先进先出的线性表,它只允许在表的一端进行插入,而在另一端删除元素.这和我们日常生活中的排队是一致的,最早进入队列的元素最早离开.在队列中,允许插入的一端叫做队尾(rear),允许 ...
- Go语言入门系列(四)之map的使用
本系列前面的文章: Go语言入门系列(一)之Go的安装和使用 Go语言入门系列(二)之基础语法总结 Go语言入门系列(三)之数组和切片 1. 声明 map是一种映射,可以将键(key)映射到值(val ...
- Go语言入门系列(五)之指针和结构体的使用
Go语言入门系列前面的文章: Go语言入门系列(二)之基础语法总结 Go语言入门系列(三)之数组和切片 Go语言入门系列(四)之map的使用 1. 指针 如果你使用过C或C++,那你肯定对指针这个概念 ...
- Go语言入门系列(六)之再探函数
Go语言入门系列前面的文章: Go语言入门系列(三)之数组和切片 Go语言入门系列(四)之map的使用 Go语言入门系列(五)之指针和结构体的使用 在Go语言入门系列(二)之基础语法总结这篇文章中已经 ...
- go语言gRPC系列(二) - 为gRPC添加证书
1. 前言 2. 生成自签证书 2.1 MAC生成自签证书的教程链接: 2.2 Windows生成自签证书的教程 3. 改造服务端使用自签证书 3.1 复制证书至代码下 3.2 改造代码添加证书认证 ...
- 初识google多语言通信框架gRPC系列(一)概述
gRPC概述 3/26/2016 9:16:08 AM 目录 一.概述 二.编译gRPC 三.C#中使用gRPC 四.C++中使用gRPC 一直在寻找多平台多语言的通信框架,微软的WCF框架很强大和灵 ...
- 初识google多语言通信框架gRPC系列(二)编译gRPC
目录 一.概述 二.编译gRPC 三.C#中使用gRPC 四.C++中使用gRPC 无论通过哪种语言调用gRPC,都必须要编译gRPC,因为生成proto访问类时,除了产生标准的数据定义类之外,还需要 ...
- 使用 PySide2 开发 Maya 插件系列三:qt语言国际化(internationalization)
使用 PySide2 开发 Maya 插件系列三:qt语言国际化(internationalization) 前言: 这是 qt for python 的语言国际化,基于 UI 的,python 也有 ...
- grpc(三)之grpc客户端使用连接池
本文使用commons-pool2来实现连接池应用 1.定义一个产生连接池的工厂,需要继承BasePooledObjectFactory,其用处是生产和销毁连接池中保存的对象.根据需求,现在池子里保存 ...
随机推荐
- python多进程之multiprocessing
什么是多进程? 简单的理解:单板上运行的一个程序就是一个进程.进程是操作系统分配资源的最小单位,不同的进程之间资源不共享,进程间通信需要使用特定的方式.python提供了自带的multiprocess ...
- C++语法小记---自己实现Thread类
自己实现Thread类 在C++的类中,普通成员函数不能作为pthread_create的线程函数,如果要作为pthread_create中的线程函数,必须是static ! 参考:https://b ...
- 题解 洛谷 P3710 【方方方的数据结构】
因为有撤销操作,所以修改操作可能会只会存在一段时间,因此把时间看作一维,被修改的序列看作一维. 可以把操作都离线下来,对于每个修改操作,就是在二维平面上对一个矩形进行修改,询问操作,就是查询单点权值. ...
- 题解 洛谷 P4899 【[IOI2018] werewolf 狼人】
先考虑狼形,其只能走编号小于\(R\)的点.若将每条边赋边权为其两端点编号的较大值,然后按最小生成树的顺序构建\(Kruskal\)重构树. 那么从原图的一个点\(x\)在树上倍增,到达满足要求且深度 ...
- vue的自定义指令。directive
在vue中有很多vue自带的指令,比如v-heml.v-for.v-if,v-on.v-bind.v-else.v-show. 但是这些指令还不够我们使用的.就有了directive这个对象. 这个使 ...
- 大数据篇:一文读懂@数据仓库(PPT文字版)
大数据篇:一文读懂@数据仓库 1 网络词汇总结 1.1 数据中台 数据中台是聚合和治理跨域数据,将数据抽象封装成服务,提供给前台以业务价值的逻辑概念. 数据中台是一套可持续"让企业的数据用起 ...
- hostapd阅读(openwrt)-3
从官网下载相对而言比较干净的源码版本http://w1.fi/hostapd/,然后将其移植到openwrt下,方便在源码阅读时候进行调试编译,移植的过程总结如下心得. 1. openwrt编译与cl ...
- string类型 C++
(C++递归预习到了string类型,这个是处理字符串的一个非常好用的东西,在C里面没有.今天来学习一下) 顺便推荐一个很不错的网站:http://c.biancheng.net/view/400.h ...
- Raft协议理解
raft协议最关键的部分是领导选举和日志复制 日志复制 日志匹配原则:如果两个日志在相同索引位置的entry的任期号相同,那么这两个日志从头到这个索引位置之前完全相同. 日志匹配原则可以解释为如下两条 ...
- springMVC 获取request参数
持续补充............ GET 常用的:@PathVariable @RequestParam request.getParameter POST 常用的:@ResponseBody ...