搞懂 gRPC 支持 HTTP 進行雙協議通信
gRPC 是一種高性能、跨語言的 RPC 框架,其核心優勢包括:
1)基於 HTTP/2 協議實現多路複用和低延遲通信,顯著提升傳輸效率;
2)通過 Protocol Buffers 提供強類型接口定義和高效的二進制序列化,減少數據體積;
3)支持 雙向流式通信(如客戶端 / 服務端流),靈活應對複雜交互場景;
4)自動生成多語言客戶端和服務端代碼,簡化開發並保障一致性;
5)內置 認證、負載均衡、重試和超時機制,天然適配微服務架構,是構建高併發、分佈式系統的理想選擇。
爲什麼需要將 gRPC 以 HTTP 形式提供接口?
在微服務架構中,gRPC 憑藉其高性能、強類型接口和雙向流式通信等特性,成爲服務間內部通信的首選協議。然而,直接對外暴露 gRPC 接口往往面臨挑戰,尤其是在需要與瀏覽器、移動端或第三方系統交互時。此時,**同時支持 HTTP 協議(如 RESTful API)**成爲關鍵需求,將 gRPC 服務通過 HTTP(如 RESTful API)對外提供,主要有以下便利性:
1)跨平臺兼容性:HTTP/1.1 + JSON 是 Web、移動端、IoT 設備的通用標準,瀏覽器原生支持,無需引入 gRPC 客戶端庫。
2)簡化客戶端調用:前端開發者可直接用 fetch 或 axios 調用接口,無需生成 gRPC 客戶端代碼。
3)生態集成:兼容現有工具鏈(如 API 網關、監控、日誌、Postman 調試)。
4)漸進式遷移:允許舊系統逐步遷移到 gRPC,無需一次性重構。
如何實現雙協議支持?
將 gRPC 服務同時暴露爲 HTTP 接口,本質是通過協議轉換層或代碼生成工具實現兩種協議之間的映射。常見的有以下幾種典型實現方式:
方式一:協議轉換層(反向代理)
通過中間代理將 HTTP 請求轉換爲 gRPC 調用,常見工具有 gRPC-Gateway 和 Envoy Proxy。核心流程:
1)在 Protobuf 文件中通過註解定義 HTTP 路由(如 RESTful 路徑、方法)。
2)生成反向代理代碼,監聽 HTTP 請求並轉發至 gRPC 服務。
3)代理層處理協議差異(如 JSON ↔ Protobuf 的編解碼)。
示例(gRPC-Gateway):
// 定義 gRPC 服務時添加 HTTP 註解
service UserService {
rpc GetUser(GetUserRequest) returns (User) {
option (google.api.http) = {
get: "/v1/users/{user_id}"
};
}
}
message GetUserRequest { string user_id = 1; }
message User { string name = 1; uint32 age = 2; }
生成代理代碼後,HTTP 請求 GET /v1/users/123 會被轉換爲 GetUser(user_id="123") 的 gRPC 調用。
方式二:雙協議服務端
部分框架(如 go-zero、.NET Core gRPC-HTTP API)允許服務端同時監聽 gRPC 和 HTTP 端口,並自動處理協議轉換。核心流程:
1)使用同一套接口定義(Protobuf 或代碼優先)。
2)框架生成兩種協議的處理邏輯,共享業務實現。
3)服務端並行處理 gRPC 和 HTTP 請求。
示例(go-zero):
// 定義 REST 路由和 gRPC 服務
// greet.api 文件
service greet {
@handler GreetHandler
get /greet (Request) returns (Response)
}
// 自動生成 gRPC 和 HTTP 服務代碼
goctl api go -api greet.api -dir .
方式三:基礎設施層轉換
通過 API 網關(如 Kong、Envoy)動態轉換協議,無需修改服務代碼。核心流程:
1)網關接收 HTTP 請求,根據配置路由到 gRPC 服務。
2)網關處理協議轉換(如 JSON → Protobuf)和負載均衡。
示例(Envoy gRPC-JSON Transcoding):
# Envoy 配置
http_filters:
- name: envoy.filters.http.grpc_json_transcoder
config:
proto_descriptor: "path/to/descriptor.pb"
services: ["user.UserService"]
gRPC 支持 HTTP 協議實戰
這篇文章我們就來分享一下使用 gRPC API Gateway 插件,通過反向代理實現雙協議的支持,大致會分爲以下幾個步驟:
1)定義 RPC 接口:引入 gRPC API Gateway 模塊定義 RPC 和 HTTP 接口
2)編譯中間文件:使用 buf 工具編譯 protobuf 文件
3)編碼實現接口:編寫 Go 代碼實現 RPC 接口,並同步支持 HTTP
定義 RPC 接口
首先我們按照 buf 工具的約定定義配置文件,名爲 buf.yaml:
version: v1
deps:
- "buf.build/meshapi/grpc-api-gateway"
接下來定義 protobuf 文件,與常規 gRPC 接口定義不同的地方主要有兩處:
第一是引入了三方的 protobuf 文件:
import "meshapi/gateway/annotations.proto";
第二是利用引入的 protobuf 文件定義 HTTP 接口,具體如下:
syntax = "proto3";
package main;
import "meshapi/gateway/annotations.proto";
option go_package = "/main";
service HelloService {
rpc SayHello(HelloRequest) returns (HelloResponse) {
option (meshapi.gateway.http) = {
post: "/hello",
body: "*"
};
}
}
message HelloRequest {
string msg = 1;
}
message HelloResponse {
string msg = 1;
}
使用 buf 工具編譯 protobuf 文件
buf 工具 是一個專爲 Protocol Buffers(Protobuf) 設計的現代化開發工具鏈,旨在優化 Protobuf 生態系統的開發體驗。它通過提供代碼生成、依賴管理、代碼質量檢查(Linting)、格式化、版本控制等功能,簡化了 Protobuf 文件的開發、維護和協作流程。
下載和安裝方式參考:https://buf.build/docs/cli/installation/
優勢對比傳統 Protobuf 工具:
編譯命令:
更新模塊依賴:
buf mod update
編譯 protobuf 文件:
buf generate
編譯後我們會看見項目目錄會增加很多相關文件。
編寫 Go 代碼實現 RPC 接口
在 Go 代碼中我們定義 HelloService 結構體,實現 SayHello 方法,用於處理 gRPC 請求,SayHello 方法接收一個 HelloRequest 請求,返回包含問候信息的 HelloResponse。
主函數啓動 gRPC 服務器,在 40000 端口監聽 TCP 連接,創建 gRPC 服務器實例,註冊 HelloService 服務,在 goroutine 中啓動 gRPC 服務器,創建新的 HTTP ServeMux,註冊 HTTP 到 gRPC 的轉換處理器,在 4000 端口啓動 HTTP 網關服務器,具體代碼如下:
package main
import (
"context"
"fmt"
"log"
"net"
"net/http"
"github.com/meshapi/grpc-api-gateway/gateway"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
type HelloService struct {
UnimplementedHelloServiceServer
}
func (u *HelloService) SayHello(ctx context.Context, req *HelloRequest) (*HelloResponse, error) {
log.Printf("Received request: %+v", req)
return &HelloResponse{Msg: fmt.Sprintf("Hi %s", req.GetMsg())}, nil
}
func main() {
// Start the gRPC server
listener, err := net.Listen("tcp", ":40000")
if err != nil {
log.Fatalf("Failed to bind: %s", err)
}
server := grpc.NewServer()
RegisterHelloServiceServer(server, &HelloService{})
gofunc() {
log.Printf("Starting gRPC server on port 40000...")
if err := server.Serve(listener); err != nil {
log.Fatalf("Failed to start gRPC server: %s", err)
}
}()
// Set up the HTTP gateway
connection, err := grpc.NewClient(":40000", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Failed to dial gRPC server: %s", err)
}
restGateway := gateway.NewServeMux()
RegisterHelloServiceHandlerClient(context.Background(), restGateway, NewHelloServiceClient(connection))
log.Printf("Starting HTTP gateway on port 4000...")
if err := http.ListenAndServe(":4000", restGateway); err != nil {
log.Fatalf("Failed to start HTTP gateway: %s", err)
}
}
調用接口
package main
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"io"
"log"
"net/http"
"strings"
"testing"
)
func TestCallRpcApi(t *testing.T) {
connection, err := grpc.NewClient(":40000", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Failed to dial gRPC server: %s", err)
}
client := NewHelloServiceClient(connection)
resp, err := client.SayHello(context.TODO(), &HelloRequest{Msg: "YanTongXue"})
if err != nil {
log.Fatalf("Failed to call SayHello: %s", err)
}
log.Printf("Response: %s", resp.GetMsg())
}
func TestCallHttpApi(t *testing.T) {
// 創建HTTP客戶端
client := &http.Client{}
// 創建請求體
requestBody := strings.NewReader(`{"msg":"YanTongXue"}`)
// 創建HTTP請求
req, err := http.NewRequest("POST", "http://localhost:4000/hello", requestBody)
if err != nil {
log.Fatalf("Failed to create HTTP request: %s", err)
}
req.Header.Set("Content-Type", "application/json")
// 發送請求
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Failed to send HTTP request: %s", err)
}
defer resp.Body.Close()
// 讀取響應
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Failed to read response body: %s", err)
}
// 打印響應
log.Printf("HTTP Response: %s", string(body))
}
啓動服務後運行兩個 Test 函數就能成功調用 RPC 接口和 HTTP 接口了。
小總結
爲 gRPC 接口同時支持 HTTP 協議,本質上是在高性能與廣泛兼容性之間尋求平衡。通過協議轉換層、雙協議框架或基礎設施網關,開發者可以在保留 gRPC 內部通信優勢的同時,對外提供易用的 HTTP 接口。這一方案尤其適合需要兼顧微服務效率與開放生態的場景,例如:
-
對外提供開放 API 的 SaaS 平臺;
-
需要與瀏覽器、移動端深度交互的應用;
-
漸進式遷移至雲原生架構的傳統系統。
未來,隨着 HTTP/3 和 gRPC-Web 的普及,跨協議支持將更加高效,但 “雙協議適配” 仍是微服務設計中的重要模式。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/eqxw9WliuxwXr2GnGZDKIw