Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
文是一份詳盡且深入的指南,旨在幫助讀者理解并實現將 gRPC 通過 Maven 集成到 SpringBoot 項目中的全過程。文章首先以高度概括的方式探討了 gRPC 的理論基礎,為讀者提供了對其核心概念的清晰認識。隨后,我們將轉向更為具體的實踐層面,展示 gRPC 在實際應用中的多種實現方式。
雖然全文篇幅較長,但我們強烈建議您耐心閱讀,以確保您能夠全面掌握 gRPC 的技術細節和應用場景。通過本文的學習,您將不僅能夠理解 gRPC 的工作原理,還能在您的 SpringBoot 項目中成功集成和應用這一強大的通信協議,從而提升系統的性能和效率。
gRPC 是最初由 Google 開發的開源遠程過程調用 (RPC) 框架。它是云原生計算基金會 (CNCF) 的一部分,旨在實現微服務架構中的服務之間高效、穩健的通信。以下是 gRPC 的一些關鍵功能和概念:
1. 協議緩沖區(Protobuf):
2.基于HTTP/2:
3、四種服務方式:
在這個實際的實現中,我們將看到一元實現它等于發送單個請求和響應。我們現在知道 gRPC 使用ProtocolBuffer來定義服務。首先讓我們從定義接口定義開始,然后讓我們看看項目結構和腳手架。下面的項目結構中的每個模塊都是一個 Maven 模塊。
proto-service模塊負責保存與 proto 文件相關的所有內容,并將它們編譯成 gRPC 相關的存根和接口。
讓我們在src/main/proto文件夾中創建一個Product.proto文件并復制以下內容。
/**
* @author vaslabs(M K Pavan Kumar)
* @medium (https://medium.com/@manthapavankumar11)
*/
syntax="proto3";
option java_multiple_files=true;
package com.vaslabs.proto;
message Product {
int32 product_id=1;
string name=2;
string description=4;
float price=3;
int32 category_id=5;
}
message ProductList{
repeated Product product=1;
}
message Category {
int32 category_id=1;
string name=2;
}
service ProductService {
//unary - synchronous
//request-response stype [not streaming]
rpc getProductById(Product) returns(Product){}
rpc getProductByCategoryId(Category) returns(ProductList){}
}
提供的 proto 文件采用 Protocol Buffers 版本 3 ( proto3 ) 的語法編寫,是與產品和類別相關的 gRPC 服務的結構化定義。以下是其組成部分的簡要說明:
文件頭:
選項:
包裹聲明:
消息定義:
服務定義:
RPC 類型:
讓我們使用 maven 插件運行“mvncompile”來編譯并獲取 gRPC 風格生成的源代碼以獲取源代碼。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.vaslabs</groupId>
<artifactId>springboot-grpc-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>proto-service</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.56.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.54.2</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>
com.google.protobuf:protoc:3.17.3:exe:${os.detected.classifier}
</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>
io.grpc:protoc-gen-grpc-java:1.53.0:exe:${os.detected.classifier}
</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
這是保存原型服務的實現的服務,接收原型請求并將原型響應發送給調用它的客戶端。該服務由grpc-spring-boot-starter提供支持
將以下內容復制到該模塊的 pom.xml 中。觀察 pom 文件,我們已將proto-service模塊添加為依賴項,因為我們將在此模塊中實現 proto-services。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.vaslabs</groupId>
<artifactId>springboot-grpc-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>product-service</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.vaslabs</groupId>
<artifactId>proto-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
讓我們在src/main/java中創建一個ProductServiceImpl.java類。觀察該類擴展了由protoc生成的基類ProductServiceGrpc.ProductServiceImplBase
package org.vaslabs;
import com.vaslabs.proto.Category;
import com.vaslabs.proto.Product;
import com.vaslabs.proto.ProductList;
import com.vaslabs.proto.ProductServiceGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
import java.util.List;
@GrpcService
public class ProductServiceImpl extends ProductServiceGrpc.ProductServiceImplBase {
@Override
public void getProductById(Product request, StreamObserver<Product> responseObserver) {
InMemoryData.getProducts()
.stream()
.filter(product -> product.getProductId()==request.getProductId())
.findFirst()
.ifPresent(responseObserver::onNext);
responseObserver.onCompleted();
}
@Override
public void getProductByCategoryId(Category request, StreamObserver<ProductList> responseObserver) {
List<Product> products=InMemoryData.getProducts()
.stream()
.filter(product -> product.getCategoryId()==request.getCategoryId())
.toList();
ProductList productList=ProductList.newBuilder().addAllProduct(products).build();
responseObserver.onNext(productList);
responseObserver.onCompleted();
}
}
使用主類,我們可以啟動服務ProductServiceApplication.java ,如下所示。
package org.vaslabs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProductServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProductServiceApplication.class, args);
}
}
如果要更改默認的 gRPC 服務端口,請在src/main/resources/application.yaml中保留以下內容。
grpc:
server:
port: 9090
該模塊是一個純Spring Boot Web客戶端,它將以Http形式偵聽Web請求,并使用proto請求與產品服務通信并接收proto響應,然后轉換為DTO并將其作為Http響應發送給調用客戶端。
讓我們將以下內容復制到pom.xml,該模塊由grpc-client-spring-boot-starter提供支持,以發出啟用 grpc 的請求和響應。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.vaslabs</groupId>
<artifactId>springboot-grpc-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>product-client</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.vaslabs</groupId>
<artifactId>proto-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
讓我們創建一個服務,它將使用生成的存根與 grpc 服務進行通信,如下所示。 src/main/java/org/vaslabs/service/ProductServiceGRPC.java
package org.vaslabs.service;
import com.vaslabs.proto.Category;
import com.vaslabs.proto.Product;
import com.vaslabs.proto.ProductList;
import com.vaslabs.proto.ProductServiceGrpc;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.stereotype.Service;
import java.util.List;
import static org.vaslabs.service.helpers.DtoMappingHelper.mapProductListToProductDTO;
import static org.vaslabs.service.helpers.DtoMappingHelper.mapProductToProductDTO;
@Service
public class ProductServiceRPC {
@GrpcClient("grpc-product-service")
ProductServiceGrpc.ProductServiceBlockingStub productServiceBlockingStub;
public org.vaslabs.dto.Product getProductById(int id) {
Product product=Product.newBuilder().setProductId(id).build();
Product response=productServiceBlockingStub.getProductById(product);
return mapProductToProductDTO(response);
}
public List<org.vaslabs.dto.Product> getProductByCategoryId(int id) {
Category category=Category.newBuilder().setCategoryId(id).build();
ProductList response=productServiceBlockingStub.getProductByCategoryId(category);
return mapProductListToProductDTO(response);
}
}
一旦收到原始響應,我們將使用以下輔助方法將其轉換為 DTO。 **/helpers/DtoMappingHelper.java
package org.vaslabs.service.helpers;
import com.vaslabs.proto.ProductList;
import org.vaslabs.dto.Product;
import java.util.ArrayList;
import java.util.List;
public class DtoMappingHelper {
public static List<org.vaslabs.dto.Product> mapProductListToProductDTO(ProductList productList) {
List<Product> products=new ArrayList<>();
productList.getProductList().forEach(product -> {
Product product1=getProduct();
product1.setId(product.getProductId());
product1.setCategoryId(product.getCategoryId());
product1.setName(product.getName());
product1.setDescription(product.getDescription());
product1.setPrice(product.getPrice());
products.add(product1);
});
return products;
}
public static org.vaslabs.dto.Product mapProductToProductDTO(com.vaslabs.proto.Product product) {
Product product1=getProduct();
product1.setId(product.getProductId());
product1.setCategoryId(product.getCategoryId());
product1.setName(product.getName());
product1.setDescription(product.getDescription());
product1.setPrice(product.getPrice());
return product1;
}
private static Product getProduct(){
return new Product();
}
}
**/dto/Product.java
package org.vaslabs.dto;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@Data
@Setter
@Getter
public class Product {
private int id;
private int categoryId;
private String name;
private String description;
private float price;
}
讓我們創建一個名為**/ProductController.java的控制器,如下所示。
package org.vaslabs.controller;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.vaslabs.dto.Product;
import org.vaslabs.service.ProductServiceRPC;
import java.util.List;
@RestController
public class ProductController {
private final ProductServiceRPC productServiceRPC;
public ProductController(ProductServiceRPC productServiceRPC) {
this.productServiceRPC=productServiceRPC;
}
@GetMapping("/product/{id}")
public ResponseEntity<Product> getProductById(@PathVariable String id){
return ResponseEntity.ok().body(productServiceRPC.getProductById(Integer.parseInt(id)));
}
@GetMapping(value="/product/category/{id}", produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Product>> getProductByCategoryId(@PathVariable String id){
return ResponseEntity.ok().body(productServiceRPC.getProductByCategoryId(Integer.parseInt(id)));
}
}
在此模塊的資源文件夾中,請將以下內容添加到application.yaml文件中
grpc:
client:
grpc-product-service:
address: static://localhost:9090
negotiationType: plaintext
為此,存根將創建一個名為grpc-product-service通道來與服務進行通信。
分別運行產品服務和產品客戶端,如果下面的所有內容應該是您應該看到的輸出。
我使用 HTTPie 來測試服務。
在微服務架構的背景下,采用 gRPC 和 Protobuf 進行服務間通信已被證明是提升效率、穩健性和跨語言互操作性的關鍵策略。gRPC 以其基于 HTTP/2 的通信機制和對多種編程語言的廣泛支持,極大地促進了高性能和低延遲的服務交互,這與微服務架構的分布式特性完美契合。
Protobuf(Protocol Buffers)通過提供一種緊湊的二進制序列化格式,進一步強化了這一優勢,確保服務之間交換的數據不僅體積小,而且序列化和反序列化的速度極快。這種高效的序列化機制使得數據傳輸更為迅速,同時也減少了網絡帶寬的占用,對于構建現代、高效且有效的微服務生態系統至關重要。
綜上所述,gRPC 與 Protobuf 的結合,不僅為微服務架構提供了一種強大的通信工具,還為構建高性能、低延遲的分布式系統奠定了堅實的基礎。這種組合無疑成為了推動微服務生態系統發展的強大動力。
來源:https://spring4all.com/forum-post/7462.html
隨著.net core3.0的正式發布,gRPC服務被集成到了VS2019。本文主要演示如何對gRPC的服務進行認證授權。
目前.net core使用最廣的認證授權組件是基于OAuth2.0協議的IdentityServer4。而gRPC可以與ASP.NET Core Authentication一起使用來實現認證授權功能。本文將創建3個應用程序來完成gRPC的認證授權演示過程。
<PackageReference Include="IdentityServer4" Version="3.0.1" />
IdentityServer4相關配置,因為是演示所以很簡單,生產場景大家根據實際情況配置。
namespace Ids4.Server
{
public class Config
{
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
};
}
public static IEnumerable<ApiResource> GetApis()
{
return new List<ApiResource>
{
new ApiResource("api", "Demo API")
{
ApiSecrets={ new Secret("secret".Sha256()) }
}
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId="client",
ClientSecrets={ new Secret("secret".Sha256()) },
AllowedGrantTypes=GrantTypes.ClientCredentials,
AllowedScopes={ "api" },
},
};
}
}
}
services.AddIdentityServer().AddInMemoryApiResources(Config.GetApis())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients())
.AddDeveloperSigningCredential(persistKey: false);
app.UseIdentityServer();
POST /connect/token HTTP/1.1
Host: localhost:5000
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=client1&client_secret=secret
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IlVyMmxuM2EwNGhWaGdDdWZTVTNtZVEiLCJ0eXAiOiJhdCtqd3QifQ.eyJuYmYiOjE1NzEzMDkwMTMsImV4cCI6MTU3MTMxMjYxMywiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoiYXBpIiwiY2xpZW50X2lkIjoiY2xpZW50Iiwic2NvcGUiOlsiYXBpIl19.X4pg9_FbPbWZl814XC0NYWTslfhMG4aXWEyXLrXhIojPJaL7Qvq9ieDF4S7x0psRcClwbwCg81hTrG3j2Cmcl0nzj_Ic7UY8MfN0dvAuy_fJdUf76TX0oOpir3SxgC8gnfaKyEoWmmbIyvwicWbKp9PP-EeTxG6-oMYn6PO22cwRVHDD28ZdEAq2DEkATOh9XPavoi9vGZhPQ1nviKL1K6tcYUGXSQbhWI9ISEqnTHqMX1xA_gcDIAplGvquXmtXdgyTsRoGolEtzDAYVH4sGUb1SpYx2nc8bgl6Qw27fhe0Uy9MR70kQMcEkCTdXLivjYjkuI9_quUyJHzdi5KgnQ",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "api"
}
本篇不對IdentityServer4做更多的講解,大家可以參考官方文檔了解更多。
<PackageReference Include="Google.Protobuf" Version="3.10.0" />
<PackageReference Include="Grpc.Net.Client" Version="2.23.2" />
<PackageReference Include="Grpc.Tools" Version="2.24.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
這3個核心包是客戶端必備的,其中grpc.tools幫我們把proto文件轉化成C#代碼。
也可以直接使用在項目文件里面代碼設置如下:
<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>
var channel=GrpcChannel.ForAddress("https://localhost:5001");
var client=new Greeter.GreeterClient(channel);
var response=client.SayHello(new HelloRequest { Name="World" });
Console.WriteLine(response.Message);
啟動gRPC服務端,在啟動gRPC客戶端控制臺打印hello word表示成功。
identityServer接入gRPC是非常容易,和傳統webapi差不多。
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
services.AddGrpc(x=> x.EnableDetailedErrors=false);
services.AddAuthorization();
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options=>
{
options.Authority="http://localhost:5000";
options.RequireHttpsMetadata=false;
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints=>
{
endpoints.MapGrpcService<GreeterService>();
endpoints.MapGet("/", async context=>
{
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});
[Authorize]
public class GreeterService : Greeter.GreeterBase
{
}
這個時候我們啟動Grpc.Client訪問Grpc.Server服務
發現報錯401。說明此服務需要攜帶令牌才能訪問。
//獲取token可以直接使用HttpClient來獲取,這里使用IdentityModel來獲取token
var httpClient=new HttpClient();
var disco=await httpClient.GetDiscoveryDocumentAsync("http://localhost:5000");
if (!disco.IsError)
{
var token=await httpClient.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest()
{
Address=disco.TokenEndpoint,
ClientId="client",
ClientSecret="secret"
});
var tokenValue="Bearer " + token.AccessToken;
var metadata=new Metadata
{
{ "Authorization", tokenValue }
};
var callOptions=new CallOptions(metadata);
var channel=GrpcChannel.ForAddress("https://localhost:5001");
var client=new Greeter.GreeterClient(channel);
var response=client.SayHello(new HelloRequest { Name="World" }, callOptions);
Console.WriteLine(response.Message);
}
執行程序返回hello world表示成功。
傳統調用webapi把token放到Header頭的Authorization屬性里面,grpc是放到Metadata里面,調用方法的時候傳入CallOptions。使用上大同小異。
目前gRPC各個語言的支持都已經很完善,因為跨語言,性能更高的特性非常適合做內網的通信。筆者也將繼續對gRPC進行跟進,會嘗試將部分的內部服務改造成gRPC,關于gRPC的相關問題也可以留言大家一起討論。 源代碼地址:https://github.com/longxianghui/grpc_ientityserver
原文地址:https://www.cnblogs.com/longxianghui/p/11719190.html
在移動端平臺開發中,為了增加代碼復用,降低開發成本,通常會需要采用跨平臺的開發技術,花椒也不例外。本次新的單品開發,由于時間緊,人員有限,經過調研選型,最終確定了 flutter 方案(具體選型過程不在本文討論之內)。
為了讓客戶端更專注業務實現,降低接口聯調測試成本,我們選用了 gRPC 方案。gRPC是一個高性能、通用的開源 RPC 框架,由 Google 開發并基于 HTTP/2 協議標準而設計,基于 ProtoBuf(Protocol Buffers)序列化協議開發,且支持當前主流開發語言。gRPC通過定義一個服務并指定一個可以遠程調用的帶有參數和返回類型的的方法,使客戶端可以直接調用不同機器上的服務應用的方法,就像是本地對象一樣。在服務端,服務實現這個接口并且運行 gRPC 服務處理客戶端調用。在客戶端,有一個stub提供和服務端相同的方法。
特點
gRPC-Web 為前端瀏覽器提供了 Javascript 庫用來訪問 gRPC 服務,但是需要通過 Envoy 提供代理服務。相比 JSON 的方式對前端不夠友好,同時也增加了服務端的部署成本。因此在這次項目中前端未使用 gRPC 服務,而是由 gRPC-Gateway 提供代理的 RESTful 接口。
grpc-gateway 是 protoc 的一個插件,它能讀取 gRPC 的服務定義并生成反向代理服務器,將 RESTful 的 JSON 請求轉換為 gRPC 的方式。這樣無需太多工作即可實現一套基于 gRPC 服務的 RESTful 接口,方便前端使用調用接口,同時也方便開發過程中通過 Postman/Paw 之類的工具調試接口。
gateway -> gRPC 映射方式:
例如,gRPC 接口要求的通用的 metadata 參數(如 platform, device_id 等)在 HTTP RESTful 的傳遞方式如下:
dart
為了便于客戶端調用,連接復用及通用參數傳遞,我們封裝了 dart 的基礎庫。
BaseClient 維護了針對 HOST 緩存的連接池,同時也提供了接口需要傳遞的 metadata 信息。
golang
golang 后端服務需要同時支持 gRPC 和 gateway 兩種請求方式。為了簡化部署和上線依賴,gateway 和 gRPC 的功能放在了一起,并通過攔截器注入對應的功能,主要包括 gRPC 統計,訪問日志,接口鑒權,請求參數校驗,gateway JSON 編碼等。
為了提高開發效率,方便維護及模塊復用,服務端按功能進行組件化開發。每個組件可以單獨運行一個服務,也可以和其它組件共同組成一個服務。每個組件都需要實現 Component 接口:
對應組件開發完成后,需要開發對應的服務容器,步驟如下。
1 base.Init(context.TODO(), cfg, &global.Callback{ 2 Authenticator: &auth.Callback{}, 3 LogCapture: &log.Capture{}, 4 })
base.DefaultServer.AddPublicServer(rpcPort, gatewayPort, setting.TLSConfig)
1 base.DefaultServer.RegisterComponent(&user.Component{}) 2 base.DefaultServer.RegisterComponent(&push.Component{}) 3 ...
base.DefaultServer.Serve()
proto 規范
gRPC 基于標準化的 IDL(ProtoBuf)來生成服務器端和客戶端代碼,我們決定將所有的接口描述及文檔說明都放到 proto 文件中,便于查看及修改。對 proto 的接口描述及注釋的規范如下:
代碼生成
golang
1 gengo: 2 @protoc -Iproto \ 3 -I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ 4 -I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \ 5 -I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \ 6 --go_out=plugins=grpc:go/pb \ 7 --grpc-gateway_out=logtostderr=true:go/pb \ 8 --validate_out="lang=go:go/pb" \ 9 --auth_out="lang=go:go/pb" \ 10 proto/*.proto
golang 使用 go mod 的方式直接引入 pb 生成的 .go 文件
dart
修改 pubspec.yaml,執行 flutter packages get 或 flutter packages upgrade
1 dependencies: 2 flutter: 3 sdk: flutter 4 5 protobuf: ^0.13.4 6 grpc: ^1.0.1 7 user: 8 git: 9 url: git@github.com:project/repo.git 10 path: dart/user
gRPC gateway 提供了通過 proto 文件生成 swagger API 文檔,缺點是只支持 gateway 的 RESTful 接口,并且默認的展示方式有點不符合我們的常規文檔使用方式。
我們基于 protoc 插件開發了 protoc-gen-markdown 工具,可以由 proto 文件生成 markdown 文檔,提供 gRPC 接口描述,以及 RESTful 接口描述及 JSON 示例,提供全文目錄,支持錨點導航等。生成方式如下:
1gendoc: 2 @protoc -Iproto \ 3 -I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ 4 -I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \ 5 -I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \ 6 --markdown_out=":doc" \ 7 proto/*.proto
文檔會在對應路徑生成接口列表 README.md,以及每個 protobuf 對應的接口文檔。
傳統的 RESTful 接口在調試及問題排查時,可以通過抓包或者 MitM(中間人攻擊)的方式,配置也比較容易。而 gRPC 因為使用了 HTTP2 及 protobuf 二進制流,抓包及數據流反解難度相對較高,調試及問題排查時會比較復雜。為了解決這個問題,我們通過服務端注入的方式,配合查詢后臺過濾對應的請求日志,從而實現如下類似抓包的效果。
Go語言中文網,致力于每日分享編碼、開源等知識,歡迎關注我,會有意想不到的收獲!
本文由花椒服務端團隊原創授權發布
*請認真填寫需求信息,我們會在24小時內與您取得聯系。