-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Expand file tree
/
Copy pathgrpc-web.lua
More file actions
199 lines (167 loc) · 6.64 KB
/
grpc-web.lua
File metadata and controls
199 lines (167 loc) · 6.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You under the Apache License, Version 2.0
-- (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
local ngx = ngx
local ngx_arg = ngx.arg
local core = require("apisix.core")
local req_set_uri = ngx.req.set_uri
local req_set_body_data = ngx.req.set_body_data
local decode_base64 = ngx.decode_base64
local encode_base64 = ngx.encode_base64
local bit = require("bit")
local string = string
local ALLOW_METHOD_OPTIONS = "OPTIONS"
local ALLOW_METHOD_POST = "POST"
local CONTENT_ENCODING_BASE64 = "base64"
local CONTENT_ENCODING_BINARY = "binary"
local DEFAULT_CORS_ALLOW_ORIGIN = "*"
local DEFAULT_CORS_ALLOW_METHODS = ALLOW_METHOD_POST
local DEFAULT_CORS_ALLOW_HEADERS = "content-type,x-grpc-web,x-user-agent"
local DEFAULT_PROXY_CONTENT_TYPE = "application/grpc"
local plugin_name = "grpc-web"
local schema = {
type = "object",
properties = {},
}
local grpc_web_content_encoding = {
["application/grpc-web"] = CONTENT_ENCODING_BINARY,
["application/grpc-web-text"] = CONTENT_ENCODING_BASE64,
["application/grpc-web+proto"] = CONTENT_ENCODING_BINARY,
["application/grpc-web-text+proto"] = CONTENT_ENCODING_BASE64,
}
local _M = {
version = 0.1,
priority = 505,
name = plugin_name,
schema = schema,
}
function _M.check_schema(conf)
return core.schema.check(schema, conf)
end
function _M.access(conf, ctx)
-- set context variable mime
-- When processing non gRPC Web requests, `mime` can be obtained in the context
-- and set to the `Content-Type` of the response
ctx.grpc_web_mime = core.request.header(ctx, "Content-Type")
local method = core.request.get_method()
if method == ALLOW_METHOD_OPTIONS then
return 204
end
if method ~= ALLOW_METHOD_POST then
-- https://github.com/grpc/grpc-web/blob/master/doc/browser-features.md#cors-support
core.log.error("request method: `", method, "` invalid")
return 400
end
local encoding = grpc_web_content_encoding[ctx.grpc_web_mime]
if not encoding then
core.log.error("request Content-Type: `", ctx.grpc_web_mime, "` invalid")
return 400
end
-- set context variable encoding method
ctx.grpc_web_encoding = encoding
-- set grpc path
if not (ctx.curr_req_matched and ctx.curr_req_matched[":ext"]) then
core.log.error("routing configuration error, grpc-web plugin only supports ",
"`prefix matching` pattern routing")
return 400
end
local path = ctx.curr_req_matched[":ext"]
if path:byte(1) ~= core.string.byte("/") then
path = "/" .. path
end
req_set_uri(path)
-- set grpc body
local body, err = core.request.get_body()
if err then
core.log.error("failed to read request body, err: ", err)
return 400
end
if encoding == CONTENT_ENCODING_BASE64 then
body = decode_base64(body)
if not body then
core.log.error("failed to decode request body")
return 400
end
end
-- set grpc content-type
core.request.set_header(ctx, "Content-Type", DEFAULT_PROXY_CONTENT_TYPE)
-- set grpc body
req_set_body_data(body)
end
function _M.header_filter(conf, ctx)
local method = core.request.get_method()
if method == ALLOW_METHOD_OPTIONS then
core.response.set_header("Access-Control-Allow-Methods", DEFAULT_CORS_ALLOW_METHODS)
core.response.set_header("Access-Control-Allow-Headers", DEFAULT_CORS_ALLOW_HEADERS)
end
if not ctx.cors_allow_origins then
core.response.set_header("Access-Control-Allow-Origin", DEFAULT_CORS_ALLOW_ORIGIN)
end
core.response.set_header("Content-Type", ctx.grpc_web_mime)
core.response.set_header("Access-Control-Expose-Headers", "grpc-message,grpc-status")
end
function _M.body_filter(conf, ctx)
-- If the MIME extension type description of the gRPC-Web standard is not obtained,
-- indicating that the request is not based on the gRPC Web specification,
-- the processing of the request body will be ignored
-- https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md
-- https://github.com/grpc/grpc-web/blob/master/doc/browser-features.md#cors-support
if not ctx.grpc_web_mime then
return
end
if ctx.grpc_web_encoding == CONTENT_ENCODING_BASE64 then
local chunk = ngx_arg[1]
chunk = encode_base64(chunk)
ngx_arg[1] = chunk
end
--[[
upstream_trailer_* available since NGINX version 1.13.10 :
https://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_trailer_
grpc-web trailer format reference:
envoyproxy/envoy/source/extensions/filters/http/grpc_web/grpc_web_filter.cc
Format for grpc-web trailer
1 byte: 0x80
4 bytes: length of the trailer
n bytes: trailer
--]]
local status = ctx.var.upstream_trailer_grpc_status
local message = ctx.var.upstream_trailer_grpc_message
if status ~= "" and status ~= nil then
local status_str = "grpc-status:" .. status
local status_msg = "grpc-message:" .. ( message or "")
local grpc_web_trailer = status_str .. "\r\n" .. status_msg .. "\r\n"
local len = #grpc_web_trailer
-- 1 byte: 0x80
local trailer_buf = string.char(0x80)
-- 4 bytes: length of the trailer
trailer_buf = trailer_buf .. string.char(
bit.band(bit.rshift(len, 24), 0xff),
bit.band(bit.rshift(len, 16), 0xff),
bit.band(bit.rshift(len, 8), 0xff),
bit.band(len, 0xff)
)
-- n bytes: trailer
trailer_buf = trailer_buf .. grpc_web_trailer
if ctx.grpc_web_encoding == CONTENT_ENCODING_BINARY then
ngx_arg[1] = ngx_arg[1] .. trailer_buf
else
ngx_arg[1] = ngx_arg[1] .. encode_base64(trailer_buf)
end
-- clear trailer
ctx.var.upstream_trailer_grpc_status = nil
ctx.var.upstream_trailer_grpc_message = nil
end
end
return _M