目录
grpc安装
安装protobuf
go hello 示例
如何即提供 grpc 又提供 RESTfull 接口?
安装
编写proto文件
生成rpc对应go文件
gw.go
运行
试试看命令
swagger
OpenAPI规范
文档已经生成了,通过 swagger-ui 展现
运行
借助 go-bindata 把 swagger 和 json 文件 转换为一份 go 源码
mkdir -p $GOPATH/src/google.golang.org
cd $GOPATH/src/google.golang.org
git clone https://github.com/grpc/grpc-go.git grpc
安装依赖包, 注意路径必须完全对的上
cd $GOPATH/src/golang.org/x
git clone https://github.com/golang/net.git
git clone https://github.com/golang/text.git
cd $GOPATH/src/google.golang.org
git clone https://github.com/google/go-genproto.git
mv go-genproto/ genproto
按照github提示
For non-C++ users, the simplest way to install the protocol compiler is to
download a pre-built binary from our release page:
[https://github.com/google/protobuf/releases]
centos 下的话
要先安装下依赖库 sudo yum install libatomic
然后把下载到的 bin include 拷贝到合适的目录就行
我下了个 all的 protobuf-all-3.6.0.tar.gz
还以为已经编译好了,没想是份源码
那就练练手,源码构建个出来吧
切换到src目录下 打开 README.md
sudo apt-get install autoconf automake libtool curl make g++ unzip
貌似我原来就装好了
$ ./configure
$ make
$ make check
$ sudo make install
$ sudo ldconfig # refresh shared library cache.
make check 非常慢 耐心等待
装好后可以看看版本号
~/go/gopath $ protoc --version
libprotoc 3.6.0
安装 protoc-gen-go
go get github.com/golang/protobuf
go install github.com/golang/protobuf/protoc-gen-go/
提供2个接口 一个SayHello 一个 Ls (ls 当前目录)
syntax = "proto3";
package hello;
// The greeting service definition.
service Hello {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
然后新建 hello.go
//go:generate protoc -I . --go_out=plugins=grpc:. hello.proto
package hello
之后 go generate 生成 hello.pb.go go接口文件
这个是 ser.go
package main
import (
log "github.com/sirupsen/logrus"
"net"
"golang.org/x/net/context"
"google.golang.org/grpc"
pb "hellogw"
)
const (
port = ":50051"
)
type server struct {}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatal("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterHelloServer(s, &server{})
s.Serve(lis)
}
下面是 client.go
ppackage main
import (
log "github.com/sirupsen/logrus"
"os"
"golang.org/x/net/context"
"google.golang.org/grpc"
pb "hellogw"
)
const (
address = "localhost:50051"
defaultName = "world wjs"
)
func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatal("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewHelloClient(conn)
name := defaultName
if len(os.Args) >1 {
name = os.Args[1]
}
r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
if err != nil {
log.Fatal("could not greet: %v", err)
}
log.Printf("%s", r.Message)
}
---------------------------------------------------------------华丽的分割线---------------------------------------------------------------
借助 https://github.com/grpc-ecosystem/grpc-gateway,简单的说就是有一个网关服务器负责转化和代理转发。
go get -u -v github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u -v github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
go get -u -v github.com/golang/protobuf/protoc-gen-go
hellogateway.proto
syntax = "proto3";
package hello;
import "google/api/annotations.proto";
// The greeting service definition.
service Hello {
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/v1/example/echo"
body: "*"
};
}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
关键性改动如下
import "google/api/annotations.proto";
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/v1/example/echo"
body: "*"
};
}
生成 hello.go 文件,内容如下。 这个文件啥代码没有,就给 go generate 留了个命令:执行 gen.sh
~/go/gopath/src/hellogw $ cat hello.go
//go:generate sh gen.sh
package hello
编写 gen.sh
#!/usr/bin/env bash
# Since GOPATH can be a path, we can't just use it as a variable. Split it up
# to the various paths, and append the subpaths.
GOSUBPATHS="/src:/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis"
GOPATHLIST=""
OIFS=$IFS
IFS=':'
for GOBASEPATH in $GOPATH; do
for GOSUBPATH in $GOSUBPATHS; do
if [ -e ${GOBASEPATH}${GOSUBPATH} ]; then
GOPATHLIST="${GOPATHLIST} -I${GOBASEPATH}${GOSUBPATH}"
fi
done
done
IFS=$OIFS
# generate the gRPC code
protoc -I/usr/local/include -I. ${GOPATHLIST} --go_out=plugins=grpc:. \
hellogateway.proto
# generate the JSON interface code
protoc -I/usr/local/include -I. ${GOPATHLIST} --grpc-gateway_out=logtostderr=true:. \
hellogateway.proto
# generate the swagger definitions
# protoc -I/usr/local/include -I. ${GOPATHLIST} --swagger_out=logtostderr=true:./swagger \
# hellogateway.proto
# merge the swagger code into one file
# go run swagger/main.go swagger > ../static/swagger/api.swagger.json
命令行下执行 go generate 即可生成
~/go/gopath/src/hellogw $ go generate
~/go/gopath/src/hellogw $ ls
gen.sh hello.go hellogateway.pb.go hellogateway.proto
hellogateway.pb.gw.go
package main
import (
"net/http"
"github.com/golang/glog"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc"
gw "hellogw"
)
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
err := gw.RegisterHelloHandlerFromEndpoint(ctx, mux, "localhost:50051", opts)
if err != nil {
return err
}
return http.ListenAndServe(":8080", mux)
}
func main() {
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}
go run ser.go &
go run gw.go
curl -X POST -k http://localhost:8080/v1/example/echo -d '{"name": " world wjs"}'
{"message":"Hello world wjs"}
当然也可以写个小小的go程序
package main
import (
"net/http"
"encoding/json"
"bytes"
log "github.com/sirupsen/logrus"
pb "hellogw"
)
const (
url = "http://localhost:8080/v1/example/echo"
)
func SayHello() error {
msg := pb.HelloRequest{Name:"wjs"}
js, err := json.Marshal(msg)
if err != nil {
return err
}
log.Printf("%q", js)
req,_ := http.NewRequest("POST", url, bytes.NewReader(js))
res,err := http.DefaultClient.Do(req)
defer res.Body.Close()
reply := pb.HelloReply{}
err = json.NewDecoder(res.Body).Decode(&reply)
log.Print(reply.Message, err)
return err
}
func main() {
SayHello()
}
前面的方法要运行2个服务器,好麻烦啊
我把它们写到一个
package main
import (
log "github.com/sirupsen/logrus"
"net"
"net/http"
"github.com/golang/glog"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc"
pb "hellogw"
)
const (
port = ":50051"
)
type server struct{}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
err := pb.RegisterHelloHandlerFromEndpoint(ctx, mux, "localhost"+port, opts)
if err != nil {
return err
}
return http.ListenAndServe(":8080", mux)
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatal("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterHelloServer(s, &server{})
go s.Serve(lis)
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}
8080 端口过于通用,可能被别的程序干扰,遇到莫名问题的时候 换成 8777 之类的试试
以下介绍 是到 Grpc+Grpc Gateway实践三 Swagger了解一下 抄的
Swagger
是全球最大的OpenAPI
规范(OAS)API开发工具框架,支持从设计和文档到测试和部署的整个API生命周期的开发
Swagger
是目前最受欢迎的RESTful Api
文档生成工具之一,主要的原因如下
同时grpc-gateway
也支持Swagger
OpenAPI
规范OpenAPI
规范是Linux
基金会的一个项目,试图通过定义一种用来描述API格式或API定义的语言,来规范RESTful
服务开发过程。OpenAPI
规范帮助我们描述一个API的基本信息,比如:
目前V2.0版本的OpenAPI规范(也就是SwaggerV2.0规范)已经发布并开源在github上。该文档写的非常好,结构清晰,方便随时查阅。
注:OpenAPI
规范的介绍引用自原文
安装 swagger, 文章前面部分似乎已经装好了
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
修改 gen.sh (参考文章前面部分的 gen.sh), 在尾部添加
# generate the swagger definitions
protoc -I/usr/local/include -I. ${GOPATHLIST} --swagger_out=logtostderr=true:. \
hellogateway.proto
生成 swagger 文档
在 gen.sh 目录下 执行
go generate
会在同目录下生成 hellogateway.swagger.json
下载
https://github.com/swagger-api/swagger-ui/releases
我下了个当前最新版本
其实也可以到 https://github.com/swagger-api/swagger-ui/tree/master/dist 里拿。
下载后,我在工程目录里新建了 swagger 目录,把下载好的这些文件都放了进去,并把 hellogateway.swagger.json 也放了进去
~/gol/grpc_rest/swagger $ ls
favicon-16x16.png swagger-ui-bundle.js.map
favicon-32x32.png swagger-ui-standalone-preset.js
hellogateway.swagger.json swagger-ui-standalone-preset.js.map
index.html swagger-ui.css
index.html~ swagger-ui.css.map
oauth2-redirect.html swagger-ui.js
swagger-ui-bundle.js swagger-ui.js.map
编辑 index.html
把 url 那行改成我们刚刚生成的 json 文件
url: "hellogateway.swagger.json",
修改 gw.go
package main
import (
log "github.com/sirupsen/logrus"
"net"
"net/http"
"github.com/golang/glog"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc"
pb "hellogw"
)
const (
rpcPort = ":50051"
swaggerPort = ":8100"
)
type server struct{}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
func serveSwaggerUI(mux *http.ServeMux) {
dir := "swagger"
mux.Handle("/api/", http.StripPrefix("/api/", http.FileServer(http.Dir(dir))))
log.Printf("Serving %s on HTTP port: %s\n", dir, swaggerPort)
}
func swaggerServer() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
gwmux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
err := pb.RegisterHelloHandlerFromEndpoint(ctx, gwmux, "localhost"+rpcPort, opts)
if err != nil {
return err
}
mux := http.NewServeMux()
mux.Handle("/", gwmux)
serveSwaggerUI(mux)
return http.ListenAndServe(swaggerPort, mux)
}
func main() {
lis, err := net.Listen("tcp", rpcPort)
if err != nil {
log.Fatal("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterHelloServer(s, &server{})
go s.Serve(lis)
defer glog.Flush()
swaggerServer()
}
go run gw.go
在浏览器里打开, 把 ip 换成自己的。 本地的话就用 localhost
http://192.168.255.129:8100/api/
大功告成
go-bindata 用法
gw.go 头部加入 调整下里面的路径
//go:generate go-bindata -prefix static/ -pkg static -o internal/static/static_gen.go static/...
修改 gw.go 的 serveSwaggerUI 函数
func serveSwaggerUI(mux *http.ServeMux) {
fileServer := http.FileServer(&assetfs.AssetFS{
Asset: static.Asset,
AssetDir: static.AssetDir,
Prefix: "swagger",
})
prefix := "/api/"
mux.Handle(prefix, http.StripPrefix(prefix, fileServer))
}
这部分写得比较简洁,下面链接有完整工程
https://download.csdn.net/download/wangjunsheng/11584429
参考 grpc 和 restfull 共用一个端口
//go:generate go-bindata -prefix static/ -pkg static -o internal/static/static_gen.go static/...
package main
import (
log "github.com/sirupsen/logrus"
"io/ioutil"
"crypto/tls"
"crypto/x509"
"net/http"
"strings"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc"
"github.com/elazarl/go-bindata-assetfs"
"github.com/tmc/grpc-websocket-proxy/wsproxy"
"google.golang.org/grpc/credentials"
pb "learn-swagger/api"
"learn-swagger/internal/static"
)
const (
port = ":8100"
crtFile = "server.crt"
keyFile = "server.key"
)
type server struct{}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
func serveSwaggerUI(mux *http.ServeMux) {
fileServer := http.FileServer(&assetfs.AssetFS{
Asset: static.Asset,
AssetDir: static.AssetDir,
Prefix: "swagger",
})
prefix := "/api/"
mux.Handle(prefix, http.StripPrefix(prefix, fileServer))
}
func getApiHandle(ctx context.Context) (http.Handler, error) {
rpcmux := runtime.NewServeMux()
b, err := ioutil.ReadFile(crtFile)
if err != nil {
return nil, err
}
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM(b) {
return nil, err
}
opts := []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
// given the grpc-gateway is always connecting to localhost, does
// InsecureSkipVerify=true cause any security issues?
InsecureSkipVerify: true,
RootCAs: cp,
}))}
err = pb.RegisterHelloHandlerFromEndpoint(ctx, rpcmux, "localhost"+port, opts)
if err != nil {
return nil, err
}
mux := http.NewServeMux()
mux.Handle("/", rpcmux)
serveSwaggerUI(mux)
return mux,nil
}
// 自己生成的证书会被显示不安全证书
// 试试导入证书的时候, 导入2次,一次在个人下面,一次在受信任的颁发机构下
func main() {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
api := grpc.NewServer()
pb.RegisterHelloServer(api, &server{})
var apiHttp http.Handler
handler := http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
api.ServeHTTP(w, r)
} else {
if apiHttp == nil {
w.WriteHeader(http.StatusNotImplemented)
return
}
apiHttp.ServeHTTP(w, r)
}
})
apimux,_ := getApiHandle(ctx)
apiHttp = wsproxy.WebsocketProxy(apimux)
err := http.ListenAndServeTLS(port, crtFile, keyFile, handler)
if err != nil {
log.Error(err)
}
}