下面给你一个:
Gin/Fiber REST API
+
gRPC
+
h2c
+
同一个端口
的完整 Go 示例。
这是现代 Go 微服务非常推荐的结构。
同一个端口:
localhost:8080
同时支持:
GET /ping
/hello.v1.HelloService/SayHello
project/
├── cmd/
│ └── server/
│ └── main.go
│
├── api/
│ └── proto/
│ └── hello/v1/
│ └── hello.proto
│
├── gen/
│ └── go/
│ └── hello/v1/
│ ├── hello.pb.go
│ └── hello_grpc.pb.go
│
├── internal/
│ └── service/
│ └── hello_service.go
│
├── go.mod
└── Makefile
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go get google.golang.org/grpc
go get google.golang.org/protobuf
go get golang.org/x/net/http2
go get golang.org/x/net/http2/h2c
go get github.com/gin-gonic/gin
syntax = "proto3";
package hello.v1;
option go_package = "project/gen/go/hello/v1;hellov1";
service HelloService {
rpc SayHello(HelloRequest)
returns (HelloResponse);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
protoc \
--go_out=. \
--go-grpc_out=. \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
api/proto/hello/v1/hello.proto
package service
import (
"context"
hellov1 "project/gen/go/hello/v1"
)
type HelloService struct {
hellov1.UnimplementedHelloServiceServer
}
func (s *HelloService) SayHello(
ctx context.Context,
req *hellov1.HelloRequest,
) (*hellov1.HelloResponse, error) {
return &hellov1.HelloResponse{
Message: "Hello " + req.Name,
}, nil
}
package main
import (
"log"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"google.golang.org/grpc"
hellov1 "project/gen/go/hello/v1"
"project/internal/service"
)
func main() {
// =========================
// gRPC Server
// =========================
grpcServer := grpc.NewServer()
helloService := &service.HelloService{}
hellov1.RegisterHelloServiceServer(
grpcServer,
helloService,
)
// =========================
// REST API
// =========================
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
// =========================
// Mixed Handler
// =========================
mixedHandler := h2c.NewHandler(
http.HandlerFunc(func(
w http.ResponseWriter,
r *http.Request,
) {
// grpc request
if r.ProtoMajor == 2 &&
strings.Contains(
r.Header.Get("Content-Type"),
"application/grpc",
) {
grpcServer.ServeHTTP(w, r)
return
}
// normal REST request
router.ServeHTTP(w, r)
}),
&http2.Server{},
)
// =========================
// HTTP Server
// =========================
server := &http.Server{
Addr: ":8080",
Handler: mixedHandler,
}
log.Println("server started at :8080")
if err := server.ListenAndServe(); err != nil {
panic(err)
}
}
go run ./cmd/server
curl http://localhost:8080/ping
结果:
{
"message": "pong"
}
推荐:
安装:
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
grpcurl \
-plaintext \
-d '{"name":"Edward"}' \
localhost:8080 \
hello.v1.HelloService/SayHello
结果:
{
"message": "Hello Edward"
}
因为:
普通:
http.Server
默认:
不支持 h2c
h2c.NewHandler:
负责:
HTTP/2 cleartext upgrade
因为:
gRPC 必须:
HTTP/2
避免:
普通 HTTP/2 请求
误进入 grpc server。
生产环境一般:
Nginx/Traefik
终止 TLS
Go 内部:
h2c 明文
非常常见。
React Frontend
|
HTTPS
|
+----------------+
| Nginx/Traefik |
+----------------+
|
h2c
|
+----------------+
| Go Server |
| REST + gRPC |
+----------------+
你后面还可以:
例如:
rpc StreamLogs(LogRequest)
returns (stream LogMessage);
特别适合你:
h2c mixed handler 的核心:
同一个 http.Server
↓
判断:
是不是 gRPC 请求
↓
gRPC -> grpcServer
REST -> Gin/Fiber