Apinto 网关: Go语言实现 HTTP 转 gRPC

news/2024/7/10 20:01:30 标签: golang, http, gRPC, API, 网关, gateway, 开源

https://img-blog.csdnimg.cn/e4beee1f64124cb0b192f22b6d1e7028.png" width="1080" />

gRPC 是由 Google 开发的一个高性能、通用的开源RPC框架,主要面向移动应用开发且基于 HTTP/2 协议标准而设计,同时支持大多数流行的编程语言。

gRPC 基于 HTTP/2 协议传输, HTTP/2 相比 HTTP1.x有以下优势:

  • 采用二进制格式传输协议,支持多路复用。

  • 支持通过同一个连接发送多个并发的请求,支持流式传输。

  • 服务器可以对客户端的一个请求发送多个响应。

  • 对消息头进行压缩传输,节省消息头占用的网络流量。gRPC使用Protocol Buffers 作为序列化协议。

但同时,gRPC 也有自身的局限性:

浏览器支持有限

当下,不可能直接从浏览器调用 gRPC 服务。gRPC 大量使用HTTP/2 功能,没有浏览器提供支持 gRPC 客户机的 Web 请求所需的控制级别。例如,浏览器不允许调用者要求使用的 HTTP/2,或者提供对底层 HTTP/2 框架的访问。

不是人类可读的

HTTP API 请求以文本形式发送,可以由人读取和创建。默认情况下,gRPC 消息使用 protobuf 编码。虽然 protobuf 的发送和接收效率很高,但它的二进制格式是不可读的

protobuf 需要在 *.proto 文件中指定的消息接口描述才能正确反序列化。需要额外的工具来分析线路上的Protobuf 有效负载,并手工编写请求。

若需要将内部 gRPC 作为接口开放给外部用户或浏览器调用,则需要有第三方代理解决 HTTP 协议转换成 gRPC 协议的问题。

为此,我们在 Apinto 多协议支持的基础上,发布了 HTTP 转 gRPC 插件

(📌eolinker.com:apinto:http_to_grpc)

https://img-blog.csdnimg.cn/c52746ea0a2242c389d0ed4b3c32e2c4.png" width="1080" />Apinto 通过插件的方式支持 HTTP协议转换成gRPC协议请求,在http_to_grpc 插件中完成了 HTTP 客户端 与 gRPC Server 通讯时的协议转换及数据编解码工作,其通讯的过程如下:

https://img-blog.csdnimg.cn/1d5b3a175721482a81f15e44a97d2adc.png" width="1080" />

接下来通过一个完整的示例向大家演示怎样构建一个 HTTP 请求,并通过 Apinto 进行 HTTP协议 转换成 gRPC 协议请求。在以下的示例中,我们会将 Go 作为 gRPC Server 服务端处理程序,使用 Eolink 作为 HTTP 客户端,发起 HTTP 请求。

以下示例可以在 Apinto 仓库中获取。

配置 Protocol Buffer

1. 创建示例文件 msg.proto 

syntax = "proto3";

option go_package = "github.com/eolinker/apinto/example/grpc/demo_service";

package Service;

// HelloRequest 请求
message HelloRequest {
  string name = 1;
}

// HelloResponse 响应,成功消息时,msg为json数据
message HelloResponse {
  string msg = 1;
  string err = 2;
}

该文件定义了示例消息类型,我们在这里定义了一个 HelloRequest 和 HelloResponse 的 message

2. 创建示例文件 service.proto

syntax = "proto3";

option go_package = "github.com/eolinker/apinto/example/grpc/demo_service";
import "msg.proto";

package Service;

service Hello {
  rpc Hello(HelloRequest) returns (HelloResponse){};
  rpc StreamRequest(stream HelloRequest) returns (HelloResponse){};
  rpc StreamResponse(HelloRequest) returns (stream HelloResponse) {};
  rpc AllStream(stream HelloRequest)returns (stream HelloResponse) {};
}

该文件定义了服务 Hello,引入了第一步创建的文件 msg.proto,定义了四个方法,包含了一元 RPC、客户端流、服务端流、双向流四种 gRPC 通信模式。

配置服务端程序

1. 创建自动生成 gRPC 文件脚本(grpc.sh)

#!/bin/bash

OUT_DIR=demo_service

set -e

mkdir -p $OUT_DIR

protoc --grpc-gateway_opt logtostderr=true \
       --grpc-gateway_opt paths=source_relative \
       --grpc-gateway_opt generate_unbound_methods=true \
       --grpc-gateway_out=$OUT_DIR \
       --go_out=$OUT_DIR --go_opt=paths=source_relative     \
       --go-grpc_out=$OUT_DIR --go-grpc_opt=paths=source_relative *.proto

该脚本将生成 gRPC 客户端/服务端调用相关代码,并将其存储到 demo_service 目录下。

执行 grpc.sh,生成服务端 Go 原始消息和服务/客户端存根。

./grpc.sh

2. 实现服务端处理程序接口

package main

import (
 "fmt"

 service "github.com/eolinker/apinto/example/grpc/demo_service"

 "google.golang.org/grpc/metadata"

 "google.golang.org/grpc"

 "golang.org/x/net/context"
)

var _ service.HelloServer = (*Server)(nil)

type Server struct {
 service.UnimplementedHelloServer
}

func NewServer() *Server {
 return &Server{}
}

func (s *Server) Hello(ctx context.Context, request *service.HelloRequest) (*service.HelloResponse, error) {

 trailingMD, ok := metadata.FromIncomingContext(ctx)
 if ok {
  grpc.SetTrailer(ctx, trailingMD)
 }
 return &service.HelloResponse{
  Msg: fmt.Sprintf("hello,%s", request.Name),
 }, nil
}

上述代码重新定义了Hello方法:

  • 将 HelloRequest的 name 字段通过 HelloResponse 的 msg 字段封装成 hello,%s 的结果返回

  • 将请求的 Header 作为 gRPC 响应的 Trailer 头部返回

3. 定义 gRPC 服务端入口文件

package main

import (
  "fmt"
  "net"

  "google.golang.org/grpc/reflection"

  service "github.com/eolinker/apinto/example/grpc/demo_service"

  "google.golang.org/grpc/credentials"

  "google.golang.org/grpc"
  "google.golang.org/grpc/grpclog"
)

func main() {
  Parse()
  err := listen()
  if err != nil {
    fmt.Println(err)
    return
  }
}

func listen() error {
  address := fmt.Sprintf("%s:%d", BindIP, ListenPort)
  l, err := net.Listen("tcp", address)
  if err != nil {
    grpclog.Fatalf("Failed to listen: %v", err)
  }

  var opts []grpc.ServerOption
  if TlsKey != "" && TlsPem != "" {
    creds, err := credentials.NewServerTLSFromFile(TlsPem, TlsKey)
    if err != nil {
      grpclog.Fatalf("Failed to generate credentials %v", err)
    }
    opts = append(opts, grpc.Creds(creds))
  }

  // 实例化grpc Server
  s := grpc.NewServer(opts...)
  ser := NewServer()
  // 注册HelloService
  service.RegisterHelloServer(s, ser)
  // 开启grpc反射
  reflection.Register(s)
  fmt.Println("Listen on " + address)

  return s.Serve(l)
}

在此处,gRPC 服务端开启了 gRPC 反射,配置 Apinto 网关时,可选择绑定具体的 Protobuf 资源,也可以直接启用反射,动态获取 gRPC 服务端的 Protobuf 信息。

4. 编译 gRPC 服务器程序

cd server && go build -o grpcServer

5. 启动gRPC服务

./grpcServer

配置 Apinto HTTP 转 gRPC 插件

Apinto 提供了可视化界面工具 Apinto-Dashboard,降低初学者的使用成本,以下操作均在 Apinto-Dashboard 中进行配置。

为了让使用者快速上手,我们此处演示的教程使用 Apinto 可视化项目 Apinto-Dashboard 进行演示。项目仓库地址请按需点击:

  • Apinto 项目地址https://github.com/eolinker/apinto

  • Apinto-Dashboard 项目地址https://github.com/eolinker/apinto-dashboard

1.新增节点插件

在左侧导航栏中,点击 基础设施 > 节点插件,进入节点插件列表。点击 添加插件

https://img-blog.csdnimg.cn/5e14b7ccb3304fac8a29c905355155b0.png" width="1080" />

点击 拓展ID 单选框,在下拉选项后选中 http_to_grpc 插件,填写插件名称信息,点击 保存

https://img-blog.csdnimg.cn/1d8a929f471b46f6a0b89b7ee916290c.png" width="1080" />

2. 发布节点插件

在左侧导航栏中,点击 基础设施 集群,进入集群列表。选中需要发布节点插件的集群,点击进入

https://img-blog.csdnimg.cn/e029478313ec4f4991bd7070f2f78e4d.png" width="1080" />

点击 节点插件 选项卡,选中插件后方的扳手按钮

https://img-blog.csdnimg.cn/b6d32bc8406544f097b7da6b56cd4303.png" width="1080" />

在弹出框中,将状态改成 启用 ,点击 提交 。

https://img-blog.csdnimg.cn/0d42ab1bffc145d69610f167f48ce0c5.png" width="1080" />

在节点插件列表,点击 发布

https://img-blog.csdnimg.cn/732a93aa87414ea7a59584c08049cf4c.png" width="1080" />

在弹出框中点击 提交

https://img-blog.csdnimg.cn/17c574368bb84a00a86c7397e79f9b1a.png" width="1080" />

注意:该步骤非必需,仅在节点插件有改动时(新增、删除、修改节点插件顺序等),才需要重新在集群中发布上线。

3. 新增 API 操作模版,绑定 http_to_grpc 插件

在左侧导航栏中,点击 公共配置 > API 操作模版,进入操作模版列表后,点击 新建模版

https://img-blog.csdnimg.cn/8aafd37d71014e4dacbb6502291b511d.png" width="1080" />

点击 添加插件

https://img-blog.csdnimg.cn/c74225e4f14a49eab91de353200793e6.png" width="1080" />

在弹出框中选中 http_to_grpc,填写插件配置

https://img-blog.csdnimg.cn/4395273927cb4f42bb7830af7e42aa0b.png" width="1080" />

配置参数说明:

https://img-blog.csdnimg.cn/dc756002fd574af9adb1e3ff4834753b.png" width="1080" />

  • 当服务名称不填时,则默认使用 HTTP 请求路径的第一个/和第二个/之间的值作为服务名

  • 当方法名称不填时,则默认使用 HTTP 请求路径的第二个/和第三个/之间的值作为服务名

  • 即:若 HTTP 请求路径上/Service.Hello/Hello,则此时服务名称为 Service.Hello,方法名称为 Hello

示例配置

由于 gRPC 服务端示例中,我们开启了 gRPC 反射,因此,在配置插件时,reflect 设置为 true

{
     "authority": "",
     "format": "json",
     "headers": {},
     "method": "",
     "reflect": true,
     "service": ""
 }

填写完成后点击 保存

点击保存成功的插件模版,进入到 上线管理 页面,点击 上线 按钮

https://img-blog.csdnimg.cn/4c8767833aea42e99e2ab6725b1a5a4a.png" width="1080" />

4. 新增上游服务,填写 gRPC 服务地址

在左侧导航栏中,点击 上游,进入上游列表。点击新建 上游服务

https://img-blog.csdnimg.cn/ec3fe91fd5854360bb9bb11dbb5ecc86.png" width="1080" />

填写上游服务信息,目标节点支持多个地址,此处填写上面启动的 gRPC 服务地址。

https://img-blog.csdnimg.cn/8152d307e086492dad99cb309ff8a6e2.png" width="1080" />

保存后,点击 上线管理

https://img-blog.csdnimg.cn/9c5a5ae8ed7449cea2e2a510fcc8f5c2.png" width="1080" />

选中需要上线的集群,点击 上线

https://img-blog.csdnimg.cn/7356e78cb47a43eabe62a4d42e1254e9.png" width="1080" />

5. 新增 API,绑定 API 操作模版,绑定 gRPC 上游服务

在左侧导航栏中,点击 API ,进入API列表后,点击 新建 API,选中 HTTP 。

https://img-blog.csdnimg.cn/1d93f7e318214b7dae582d72f69447f8.png" width="1080" />

填写接口的基本信息,绑定上游,绑定插件模版。

https://img-blog.csdnimg.cn/30dfabbf25c14ba1ba49a323c3d94555.png" width="1080" />

保存后,点击 API 后方的 上线管理 按钮,将 API 上线到对应的集群即可。

https://img-blog.csdnimg.cn/2bad2d94ab3b429a89ed31184e9efc4e.png" width="1080" />

求 Service.Hello 服务的 Hello 方法

在上文中,我们定义了 Hello 方法的功能:

  • 将 HelloRequest 中的 name 字段通过 HelloResponse 的 msg 字段封装成 hello,%s 的结果返回。

  • 将请求的 Header 作为 gRPC 响应的 Trailer 头部返回。

调用结果如下:

https://img-blog.csdnimg.cn/1ca964167e0b4509aab8add22306c1b3.png" width="1080" />

https://img-blog.csdnimg.cn/edd44d085c384d688c4348f604bab711.png" width="1080" />

https://img-blog.csdnimg.cn/a9a2b508717e4d4eb27d228c5f91dcf4.png" width="1080" />

目前 Apinto 及其周边项目已经开源,我们希望通过 Apinto 强大的插件拓展能力,用户可像乐高积木一样根据需要自行拓展 Apinto 的插件,以满足不同的业务市场需求。

Apinto 目前属于萌芽阶段,我们希望集合广大开源爱好者的力量,与大家一起讨论方案,接受大家的批评指正,一起将产品打磨完善,做下一个端与端间的Traffic Middleware。这是一个开放和积极的项目,我们诚挚地邀请您一起参与到我们的项目开源工作中。每一个贡献都是有意义的,包括但不限于:

·查找 bugs,取得性能上的提升

·帮助完善文档,提供用户操作体验

·提交你们的 issue,让我们知道您的奇思妙想

·参与自定义插件的开发,丰富 Apinto 的能力

...

欢迎各位开源爱好者参与到 Apinto 项目中,和我们一起为开源事业贡献自己的力量!

Apinto GitHub 地址https://csdnimg.cn/release/blog_editor_html/release2.3.6/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=N7T8" alt="icon-default.png?t=N7T8" />https://github.com/eolinker/apinto


http://www.niftyadmin.cn/n/5019957.html

相关文章

【iOS】push与present Controller的区别

文章目录 前言一、push方法二、pop方法三、present方法四、dismiss方法五、dismiss多级的方法举例动画 前言 iOS推出与退出界面有两种方式——push与present,接下来笔者分别介绍这两种方式 一、push方法 SecondViewController *second [[SecondViewController all…

html实现邮件模版布局-flex布局table布局-demo

邮件模版布局 flex - 布局简单方便 兼容性差 table - 优点 就是兼容性好&#xff0c;其他没有优点 注&#xff1a;使用图片需要png最好&#xff0c;使用svg图google邮箱会出现不能使用的情况 效果图 flex布局 <!DOCTYPE html> <html lang"en" xmlns:th&qu…

day4_C++

day4_C 思维导图重载 思维导图 重载 #include <iostream> #include <cstring> using namespace std;class MyString { private:char *str;int size; public:MyString():size(10){str new char[size];strcpy(str,"");}MyString(const char *s){size st…

PCL入门(四):octree简单使用

目录 1. 八叉树(Octree)2. 简单使用 参考博客《三维点云数据的两种结构Kdtree和Octree》和《八叉树》 1. 八叉树(Octree) 只需要考虑三维情况下的八叉树的情况&#xff0c;如下 设置最大的递归深度&#xff1b;找出场景的最大尺寸&#xff0c;并据此创建第一个立方体若未到达…

CCC数字钥匙设计【NFC】--什么是AID?

1、NFC中的AID是什么&#xff1f; AID&#xff0c;英文全称为Application Identifier&#xff0c;这是NFC技术中的概念&#xff0c;AID用于唯一标识一个应用。 NFC应用的AID相关操作&#xff0c;包括注册和删除应用的AID、查询应用是否是指定AID的默认应用、获取应用的AID等 …

WebGL 视图矩阵、模型视图矩阵

目录 立方体由三角形构成 视点和视线 视点、观察目标点和上方向 视点&#xff1a; 观察目标点&#xff1a; 上方向&#xff1a; 在WebGL中&#xff0c;观察者的默认状态应该是这样的&#xff1a; 视图矩阵程序&#xff08;LookAtTriangles.js&#xff09; 实际上&…

iframe 实现跨域,两页面之间的通信

一、 背景 一个项目为vue2&#xff0c;一个项目为vue3&#xff0c;两个不同的项目实现iframe嵌入&#xff0c;并实现通信 二、方案 iframe跨域时&#xff0c;iframe组件之间常用的通信&#xff0c;主要是H5的possmessage方法 三、案例代码 父页面-vue2&#xff08;端口号为…

解决括号相关匹配问题

解决括号相关问题 力扣20- 有效的括号 题目 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。有效字符串需满足&#xff1a;1. 左括号必须用相同类型的右括号闭合。 2. 左括号必须以正确的…