阅读 114

apisix的:gateway&transcode&grpc基于HTTP2协议翻译

gRPC 代理

这个按照官方指引目前使用体验很好,就是服务注册发现部分这里还没有写完,后面会更新文章

grpc-transcode

其中就涉及到一段源码:

function _M.find_method(protos, service, method)     for k, loaded in pairs(protos) do         if type(loaded) == 'table' then             local package = loaded.package             for _, s in ipairs(loaded.service or {}) do                 if package .. "." .. s.name == service then                     for _, m in ipairs(s.method) do                         if m.name == method then                             return m                         end                     end                 end             end         end     end     return nil end 复制代码

其中就涉及到protobuf的使用规范了,在我司,java体系的grpc使用规范,但是对跨语言没有做到这么的规范,就导致出现了一个bug

image.png 需要给用用的每个pb文件增加这个package,不然网关调用不通:developers.google.com/protocol-bu…
原文解释:

The way a package specifier affects the generated code depends on your chosen language:

  • In C++  the generated classes are wrapped inside a C++ namespace. For example, Open would be in the namespace foo::bar.

  • In Java and Kotlin, the package is used as the Java package, unless you explicitly provide an option java_package in your .proto file.

  • In Python, the package directive is ignored, since Python modules are organized according to their location in the file system.

  • In Go, the package is used as the Go package name, unless you explicitly provide an option go_package in your .proto file.

  • In Ruby, the generated classes are wrapped inside nested Ruby namespaces, converted to the required Ruby capitalization style (first letter capitalized; if the first character is not a letter, PB_ is prepended). For example, Open would be in the namespace Foo::Bar.

  • In C#  the package is used as the namespace after converting to PascalCase, unless you explicitly provide an option csharp_namespace in your .proto file. For example, Open would be in the namespace Foo.Bar.

Packages and Name Resolution

Type name resolution in the protocol buffer language works like C++: first the innermost scope is searched, then the next-innermost, and so on, with each package considered to be "inner" to its parent package. A leading '.' (for example, .foo.bar.Baz) means to start from the outermost scope instead.

The protocol buffer compiler resolves all type names by parsing the imported .proto files. The code generator for each language knows how to refer to each type in that language, even if it has different scoping rules.

gRPC over HTTP2译文

介绍

本文主要描述 grpc 基于 http2 framing 的实现

大纲

下述是在一次grpc请求和应答里通用的消息原子组成:

  • Request → Request-Headers *Length-Prefixed-Message EOS

  • Response → (Response-Headers *Length-Prefixed-Message Trailers) / Trailers-Only

Requests

  • Request → Request-Headers *Length-Prefixed-Message EOS

Request-Headers

Request-Headers 被当作用 http2 headers,通过 HEADERS + CONTINUATION 帧来传输

  • Request-Headers → Call-Definition *Custom-Metadata

    • Binary-Header → {Header-Name "-bin" } {base64 encoded value}

    • ASCII-Header → Header-Name ASCII-Value

    • Header-Name → 1*( %x30-39 / %x61-7A / "_" / "-" / ".") ; 0-9 a-z _ - .

    • ASCII-Value → 1*( %x20-%x7E ) ; space and printable ASCII

    • Method → ":method POST"

    • Scheme → ":scheme " ("http" / "https")

    • Path → ":path" "/" Service-Name "/" {method name} # But see note below.

    • Authority → ":authority" {virtual host name of authority}

    • TE → "te" "trailers" # Used to detect incompatible proxies

    • Timeout → "grpc-timeout" TimeoutValue TimeoutUnit

    • TimeoutValue → {positive integer as ASCII string of at most 8 digits}

    • TimeoutUnit → Hour / Minute / Second / Millisecond / Microsecond / Nanosecond

    • Content-Type → "content-type" "application/grpc" [("+proto" / "+json" / {custom})]

    • Content-Coding → "identity" / "gzip" / "deflate" / "snappy" / {custom}

    • Message-Encoding → "grpc-encoding" Content-Coding

    • Message-Accept-Encoding → "grpc-accept-encoding" Content-Coding *("," Content-Coding)

    • User-Agent → "user-agent" {structured user-agent string}

    • Message-Type → "grpc-message-type" {type name for message schema}

    • Service-Name → {IDL-specific service name}

    • Hour → "H"

    • Minute → "M"

    • Second → "S"

    • Millisecond → "m"

    • Microsecond → "u"

    • Nanosecond → "n"

    • Call-Definition → Method Scheme Path TE [Authority] [Timeout] Content-Type [Message-Type] [Message-Encoding] [Message-Accept-Encoding] [User-Agent]

    • Custom-Metadata → Binary-Header / ASCII-Header

HTTP2 协议要求 reserved headers (以 ":" 开头的 header) 必须出现在其他 headers 之前。此外,还要求 Timeout 头必须紧跟在 reserved headers 之后被发送。还有就是,Call-Definition 需要在发送 Custom-Metadata 之前发送。

一些 gRPC 的实现可能会允许上面的 Path 格式被覆盖,但是这样的功能是强烈不推荐的。gRPC 不会特意的去阻止用户使用 Path 格式覆盖这个功能, 但我们绝不会主动的支持它,另外就是当 PATH 的格式不是上述那样的话, 一些功能(如:service config support)可能会不能正常工作。

在 Timeout 被缺省时, 服务端应当假定 Timeout 是无穷大的。客户端实现可以根据其部署要求自由发送默认的最小超时时间。

如果 Content-Type 不是以 "application/grpc" 开头时, gRPC 服务端应当以 HTTP 状态码 415 (Unsupported Media Type) 进行应答。这样, 能阻断 HTTP/2 的客户端(以状态码200标识成功)解析一个 gRPC 错误。

Custom-Metadata 是一系列由应用层定义的 key-value 键值对组成的集合。应用程序不能使用以 "grpc-" 开头的 Header 名作为 Custom-Metadata 的 key, 因为以 "grpc-" 开头的 Header 名被用于预留的在未来给 GRPC 使用。

注意,因为 HTTP2 不允许二进制序列作为 header 的值,所以二进制的 header 值必须使用 Base64 编码。实现时必须可以接收 padded 和 un-padded 的值,发送 un-padded 的值。应用程序定义的二进制 header 名需要以 "-bin" 为后缀。运行库可以使用这个后缀检测出二进制的 headers,然后在发送和接收时使用 base64 进行加解密。

Custom-Metadata 不能保证 header 的顺序除非有重复的 header 名。当有重复的 header 名时,在语义上等价于将他们的值以 "," 作为分隔符拼接起来。所以,实现时需要注意,对于 二进制类型的 header,需要先用 "," 进行分割,然后再用 base64 进行解密处理。

Custom-Metadata 的 ASCII 类型的值,不能有空格做前后缀,如果有的话,会被剥离掉。另外,这里可以使用的 ASCII 字符范围比 HTTP 更加严格。实现时,不能因为接收到在 HTTP 有效但在 Custom-Metadata 中无效的 ASCII 字符而出错,但是却没有严格定义具体的行为:可以抛弃也可以接受这个值。如果被接受,必须注意确保允许应用程序将值作为元数据回显。例如,如果这个元数据被当作一个列表在一个请求中传递给应用程序,那么如果应用程序把这个元数据放在应答中时不应引发一个错误。

服务端可以限制 Request-Headers 的大小,建议默认 8kb。建议实现时计算 header 的总大小,像 HTTP/2 的 SETTINGS_MAX_HEADER_LIST_SIZE 那样:the sum of all header fields, for each field the sum of the uncompressed field name and value lengths plus 32, with binary values' lengths being post-Base64.

Length-Prefixed-Message

一个 Request 中可以有多个 Length-Prefixed-Message 数据,它们通过 http2 的 DATA 帧进行传输。

  • Length-Prefixed-Message → Compressed-Flag Message-Length Message

    • Compressed-Flag → 0 / 1 # encoded as 1 byte unsigned integer

    • Message-Length → {length of Message} # encoded as 4 byte unsigned integer (big endian)

    • Message → *{binary octet}

当 Compressed-Flag 值为1时,标识着二进制的 Message 是使用 Message-Encoding 头中声明的方式压缩过的。值为0时,意味着没有被压缩。
如果 Message-Encoding 头缺省的情况下,Compressed-Flag 值必须为0。压缩的上下文信息不会在跨越 message 边界时依然被维护,所以,实现时必须为流中的每一个 message 创建一个新的上下文。

EOS(end-of-stream)

对于请求来说,EOS (end-of-stream) 通过接收到的最后一个 DATA 帧中的 END_STREAM 标记来标识的。在 Request 需要被关闭但是又没有数据需要发送的情况下,gRPC的实现必须发送一个包含 END_STREAM 标记的空 DATA 帧。

Responses

  • Response → (Response-Headers *Length-Prefixed-Message Trailers) / Trailers-Only

Response-Headers

  • Response-Headers → HTTP-Status [Message-Encoding] [Message-Accept-Encoding] Content-Type *Custom-Metadata

  • Trailers-Only → HTTP-Status Content-Type Trailers

  • Trailers → Status [Status-Message] *Custom-Metadata

    • Percent-Encoded → 1*(Percent-Byte-Unencoded / Percent-Byte-Encoded)

    • Percent-Byte-Unencoded → 1*( %x20-%x24 / %x26-%x7E ) ; space and VCHAR, except %

    • Percent-Byte-Encoded → "%" 2HEXDIGIT ; 0-9 A-F

    • Status → "grpc-status" 1*DIGIT ; 0-9

    • Status-Message → "grpc-message" Percent-Encoded

    • HTTP-Status → ":status 200"

Response-Headers 和 Trailers-Only 都是通过一个独立的 HTTP2 HEADERS 帧块进行传输。大多数的 responses 都应该既有 headers 又有 trailers,但是 Trailers-Only 当在需要产生一个即时错误时是被允许的。另外,Status 信息必须在 Trailers 中,即使 status code 是 OK.

对于 responses 来说,end-of-stream 是通过最后一个传输 Trailers 的 HEADERS 帧的 END_STREAM 标记来标示的。

实现时应当期望 broken deployments 在响应中发送 非200 的 HTTP状态代码以及各种非GRPC内容类型并且省略状态和状态消息。当这种情况发生时,实现时必须合成一个 Status & Status-Message 以传播给应用层

客户端应当限制 Response-Headers, Trailers, 或 Trailers-Only 的大小,一般推荐上述对象的大小限制都是 8Kb

Status 的值部分是十进制编码的整数,作为ASCII字符串,没有任何前导零

Status-Message 的值部分理论上应当是一个描述错误的 Unicode 字符串,实际上多用 UTF-8 跟着是 url 编码(percent-encoding)。当解码无效值时,该实现一定不能抛出错误 或者 丢弃这个 message。最坏情况,就是终止解码这个 Status-Message,这样用户可以接收到原始的 url 编码格式数据。或者,该实现可以解码有效部分,同时保留损坏的% - 编码,或者用替换字符(例如,'?'或Unicode替换字符)替换它们。

Example

以下以 unary-call 为例展示 HTTP2 的帧序列

Request

**

    HEADERS (flags = END_HEADERS)     :method = POST     :scheme = http     :path = /google.pubsub.v2.PublisherService/CreateTopic     :authority = pubsub.googleapis.com     grpc-timeout = 1S     content-type = application/grpc+proto     grpc-encoding = gzip     authorization = Bearer y235.wef315yfh138vh31hv93hv8h3v     DATA (flags = END_STREAM)     <Length-Prefixed Message> 复制代码

Response

**

    HEADERS (flags = END_HEADERS)     :status = 200     grpc-encoding = gzip     content-type = application/grpc+proto     DATA     <Length-Prefixed Message>     HEADERS (flags = END_STREAM, END_HEADERS)     grpc-status = 0 # OK     trace-proto-bin = jher831yy13JHy3hc 复制代码

User Agents

虽然协议不要求用户代理来运行,但建议客户端提供结构化的用户代理字符串,该字符串提供了调用库、版本和平台的基本描述,以便在异构环境中进行问题诊断。建议库开发人员使用以下结构:
User-Agent → "grpc-" Language ?("-" Variant) "/" Version ?( " (" *(AdditionalProperty ";") ")" )
E.g.

**

    grpc-java/1.2.3     grpc-ruby/1.2.3     grpc-ruby-jruby/1.3.4     grpc-java-android/0.9.1 (gingerbread/1.2.4; nexus5; tmobile) 复制代码

幂等性 和 重试

除非被显示的定位, 不然 gRPC 调用不应被假设为幂等的. 特别地:

  • 无法验证已启动的调用将不会重试

  • 没有重复抑制机制,因为没有必要

  • 标记为幂等的呼叫可以多次发送

HTTP2 Transport Mapping

Stream Identification

所有 GRPC 调用都需要指定一个内部ID。我们将在此方案中使用 HTTP2 stream-id 作为调用标识符。注意:这些 ID 是已打开 HTTP2 会话的上下文,并且,在处理多个 HTTP2 会话的给定进程中不是唯一的,也不能用作GUID。

Data Frames

DATA 帧边界与 Length-Prefixed-Message 边界无关,并且实现不应对其对齐做出任何假设。

Errors

在 RPC 期间发生应用程序或运行时错误时,将在 Trailers 中传递 Status 和 Status-Message。

在某些情况下,消息流的帧可能已损坏,RPC 运行时将选择使用 RST_STREAM 帧向其对等方指示此状态。RPC运行时实现应该将 RST_STREAM 解释为流的立即 full-closure,并且应该将错误传播到调用应用程序层。

Security

当 TLS 与 HTTP2 一起使用时,HTTP2 规范要求使用 TLS 1.2 或更高版本。它还对部署中允许的密码施加了一些额外的限制,以避免已知问题以及需要SNI支持。另外,预计 HTTP2 将与专有传输安全机制结合使用,此时规范不能对其提出任何有意义的建议

Connection Management

GOAWAY Frame

由 servers 发送给 clients 用于标识 servers 不再在相关的连接上接收任何新的 stream。这个帧包含了 server 成功接收的最后一个 stream id。clients 应该认为最后一个被 server 成功接收的 stream 之后的任何 stream 都是 UNAVAILABLE,应当在其他地方进行重试。clients 可以继续处理已接受的流,直到它们完成或连接终止。

servers 应当在终止连接前发送 GOAWAY 帧,以可靠地通知 clients 哪些工作已被服务器接受并正在执行。

PING Frame

clients 和 servers 都可以发送 PING 帧,对端必须以它接收到的内容进行回复。它被用来断定连接依然存活,并提供了一种评估 end-to-end 延迟的方法。如果 servers 启动的 PING 未在运行时期望的截止期限内收到响应,则服务器上的所有未完成调用将以 CANCELED 状态关闭。如果 clients 启动的 PING 超时未收到响应时,将导致所有调用以 UNAVAILABLE 状态关闭。请注意,PING 的频率高度依赖于网络环境,实现可以根据网络和应用要求自由调整 PING 频率。

Connection failure

如果客户端上发生可检测的连接故障,则将以 UNAVAILABLE 状态关闭所有调用。对于服务器,将以 CANCELED 状态关闭打开的调用。

Appendix A - GRPC for Protobuf

  1. protobuf 声明的服务接口很容易通过 protoc 的代码生成扩展映射到 GRPC 上。以下定义要使用的映射。

    • Service-Name → ?( {proto package name} "." ) {service name}

    • Message-Type → {fully qualified proto message name}

    • Content-Type → "application/grpc+proto"


作者:阿拉斯加大闸蟹
链接:https://juejin.cn/post/7025898195087687693

文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐