@@ -68,6 +68,46 @@ function _M.check_schema(conf)
68
68
return core .schema .check (schema , conf )
69
69
end
70
70
71
+ local function exit (ctx , status )
72
+ ctx .grpc_web_skip_body_filter = true
73
+ return status
74
+ end
75
+
76
+ --- Build gRPC-Web trailer chunk
77
+ -- grpc-web trailer format reference:
78
+ -- envoyproxy/envoy/source/extensions/filters/http/grpc_web/grpc_web_filter.cc
79
+ --
80
+ -- Format for grpc-web trailer
81
+ -- 1 byte: 0x80
82
+ -- 4 bytes: length of the trailer
83
+ -- n bytes: trailer
84
+ -- It using upstream_trailer_* variables from nginx, it is available since NGINX version 1.13.10
85
+ -- https://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_trailer_
86
+ --
87
+ -- @param grpc_status number grpc status code
88
+ -- @param grpc_message string grpc message
89
+ -- @return string grpc-web trailer chunk in raw string
90
+ local build_trailer = function (grpc_status , grpc_message )
91
+ local status_str = " grpc-status:" .. grpc_status
92
+ local status_msg = " grpc-message:" .. ( grpc_message or " " )
93
+ local grpc_web_trailer = status_str .. " \r\n " .. status_msg .. " \r\n "
94
+ local len = # grpc_web_trailer
95
+
96
+ -- 1 byte: 0x80
97
+ local trailer_buf = string.char (0x80 )
98
+ -- 4 bytes: length of the trailer
99
+ trailer_buf = trailer_buf .. string.char (
100
+ bit .band (bit .rshift (len , 24 ), 0xff ),
101
+ bit .band (bit .rshift (len , 16 ), 0xff ),
102
+ bit .band (bit .rshift (len , 8 ), 0xff ),
103
+ bit .band (len , 0xff )
104
+ )
105
+ -- n bytes: trailer
106
+ trailer_buf = trailer_buf .. grpc_web_trailer
107
+
108
+ return trailer_buf
109
+ end
110
+
71
111
function _M .access (conf , ctx )
72
112
-- set context variable mime
73
113
-- When processing non gRPC Web requests, `mime` can be obtained in the context
@@ -76,19 +116,19 @@ function _M.access(conf, ctx)
76
116
77
117
local method = core .request .get_method ()
78
118
if method == ALLOW_METHOD_OPTIONS then
79
- return 204
119
+ return exit ( ctx , 204 )
80
120
end
81
121
82
122
if method ~= ALLOW_METHOD_POST then
83
123
-- https://github.com/grpc/grpc-web/blob/master/doc/browser-features.md#cors-support
84
124
core .log .error (" request method: `" , method , " ` invalid" )
85
- return 400
125
+ return exit ( ctx , 405 )
86
126
end
87
127
88
128
local encoding = grpc_web_content_encoding [ctx .grpc_web_mime ]
89
129
if not encoding then
90
130
core .log .error (" request Content-Type: `" , ctx .grpc_web_mime , " ` invalid" )
91
- return 400
131
+ return exit ( ctx , 400 )
92
132
end
93
133
94
134
-- set context variable encoding method
@@ -98,7 +138,7 @@ function _M.access(conf, ctx)
98
138
if not (ctx .curr_req_matched and ctx .curr_req_matched [" :ext" ]) then
99
139
core .log .error (" routing configuration error, grpc-web plugin only supports " ,
100
140
" `prefix matching` pattern routing" )
101
- return 400
141
+ return exit ( ctx , 400 )
102
142
end
103
143
104
144
local path = ctx .curr_req_matched [" :ext" ]
@@ -110,16 +150,17 @@ function _M.access(conf, ctx)
110
150
111
151
-- set grpc body
112
152
local body , err = core .request .get_body ()
113
- if err then
153
+ if err or not body then
114
154
core .log .error (" failed to read request body, err: " , err )
115
- return 400
155
+ return exit ( ctx , 400 )
116
156
end
117
157
118
158
if encoding == CONTENT_ENCODING_BASE64 then
119
159
body = decode_base64 (body )
160
+ ngx .log (ngx .WARN , " DECODE BODY: " , body )
120
161
if not body then
121
162
core .log .error (" failed to decode request body" )
122
- return 400
163
+ return exit ( ctx , 400 )
123
164
end
124
165
end
125
166
@@ -139,11 +180,19 @@ function _M.header_filter(conf, ctx)
139
180
if not ctx .cors_allow_origins then
140
181
core .response .set_header (" Access-Control-Allow-Origin" , DEFAULT_CORS_ALLOW_ORIGIN )
141
182
end
142
- core .response .set_header (" Content-Type" , ctx .grpc_web_mime )
143
183
core .response .set_header (" Access-Control-Expose-Headers" , DEFAULT_CORS_EXPOSE_HEADERS )
184
+
185
+ if not ctx .grpc_web_skip_body_filter then
186
+ core .response .set_header (" Content-Type" , ctx .grpc_web_mime )
187
+ core .response .set_header (" Content-Length" , nil )
188
+ end
144
189
end
145
190
146
191
function _M .body_filter (conf , ctx )
192
+ if ctx .grpc_web_skip_body_filter then
193
+ return
194
+ end
195
+
147
196
-- If the MIME extension type description of the gRPC-Web standard is not obtained,
148
197
-- indicating that the request is not based on the gRPC Web specification,
149
198
-- the processing of the request body will be ignored
@@ -159,48 +208,21 @@ function _M.body_filter(conf, ctx)
159
208
ngx_arg [1 ] = chunk
160
209
end
161
210
162
- --[[
163
- upstream_trailer_* available since NGINX version 1.13.10 :
164
- https://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_trailer_
165
-
166
- grpc-web trailer format reference:
167
- envoyproxy/envoy/source/extensions/filters/http/grpc_web/grpc_web_filter.cc
168
-
169
- Format for grpc-web trailer
170
- 1 byte: 0x80
171
- 4 bytes: length of the trailer
172
- n bytes: trailer
173
-
174
- --]]
175
- local status = ctx .var .upstream_trailer_grpc_status
176
- local message = ctx .var .upstream_trailer_grpc_message
177
- if status ~= " " and status ~= nil then
178
- local status_str = " grpc-status:" .. status
179
- local status_msg = " grpc-message:" .. ( message or " " )
180
- local grpc_web_trailer = status_str .. " \r\n " .. status_msg .. " \r\n "
181
- local len = # grpc_web_trailer
182
-
183
- -- 1 byte: 0x80
184
- local trailer_buf = string.char (0x80 )
185
- -- 4 bytes: length of the trailer
186
- trailer_buf = trailer_buf .. string.char (
187
- bit .band (bit .rshift (len , 24 ), 0xff ),
188
- bit .band (bit .rshift (len , 16 ), 0xff ),
189
- bit .band (bit .rshift (len , 8 ), 0xff ),
190
- bit .band (len , 0xff )
191
- )
192
- -- n bytes: trailer
193
- trailer_buf = trailer_buf .. grpc_web_trailer
211
+ if ngx_arg [2 ] then -- if eof
212
+ local status = ctx .var .upstream_trailer_grpc_status
213
+ local message = ctx .var .upstream_trailer_grpc_message
194
214
195
- if ctx .grpc_web_encoding == CONTENT_ENCODING_BINARY then
196
- ngx_arg [1 ] = ngx_arg [1 ] .. trailer_buf
197
- else
198
- ngx_arg [1 ] = ngx_arg [1 ] .. encode_base64 (trailer_buf )
215
+ -- When the response body completes and still does not receive the grpc status
216
+ local resp_ok = status ~= nil and status ~= " "
217
+ local trailer_buf = build_trailer (
218
+ resp_ok and status or 2 ,
219
+ resp_ok and message or " upstream grpc status not received"
220
+ )
221
+ if ctx .grpc_web_encoding == CONTENT_ENCODING_BASE64 then
222
+ trailer_buf = encode_base64 (trailer_buf )
199
223
end
200
224
201
- -- clear trailer
202
- ctx .var .upstream_trailer_grpc_status = nil
203
- ctx .var .upstream_trailer_grpc_message = nil
225
+ ngx_arg [1 ] = ngx_arg [1 ] .. trailer_buf
204
226
end
205
227
end
206
228
0 commit comments