Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[THREESCALE-9542] Part 2: Add support to proxy request with Transfer-Encoding: chunked #1403

Merged
merged 8 commits into from
Jan 22, 2024
157 changes: 130 additions & 27 deletions gateway/src/apicast/http_proxy.lua
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
local format = string.format
local tostring = tostring
local ngx_flush = ngx.flush
local ngx_get_method = ngx.req.get_method
local ngx_http_version = ngx.req.http_version
local ngx_send_headers = ngx.send_headers

local resty_url = require "resty.url"
local resty_resolver = require 'resty.resolver'
local round_robin = require 'resty.balancer.round_robin'
local http_proxy = require 'resty.http.proxy'
local file_reader = require("resty.file").file_reader
local file_size = require("resty.file").file_size
local client_body_reader = require("resty.http.request_reader").get_client_body_reader
local send_response = require("resty.http.response_writer").send_response
local concat = table.concat

local _M = { }

local http_methods_with_body = {
POST = true,
PUT = true,
PATCH = true

Check warning on line 23 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L23

Added line #L23 was not covered by tests
}

local DEFAULT_CHUNKSIZE = 32 * 1024

function _M.reset()
_M.balancer = round_robin.new()
_M.resolver = resty_resolver
Expand Down Expand Up @@ -82,15 +98,61 @@
)
end

local function forward_https_request(proxy_uri, proxy_auth, uri, skip_https_connect)
-- This is needed to call ngx.req.get_body_data() below.
ngx.req.read_body()
local function handle_expect()
local expect = ngx.req.get_headers()["Expect"]
if type(expect) == "table" then
expect = expect[1]

Check warning on line 104 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L102-L104

Added lines #L102 - L104 were not covered by tests
end

local request = {
uri = uri,
method = ngx.req.get_method(),
headers = ngx.req.get_headers(0, true),
path = format('%s%s%s', ngx.var.uri, ngx.var.is_args, ngx.var.query_string or ''),
if expect and expect:lower() == "100-continue" then
ngx.status = 100
local ok, err = ngx_send_headers()

Check warning on line 109 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L107-L109

Added lines #L107 - L109 were not covered by tests

if not ok then
return nil, "failed to send response header: " .. (err or "unknown")

Check warning on line 112 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L111-L112

Added lines #L111 - L112 were not covered by tests
end

ok, err = ngx_flush(true)
if not ok then
return nil, "failed to flush response header: " .. (err or "unknown")

Check warning on line 117 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L115-L117

Added lines #L115 - L117 were not covered by tests
end
end
end

local function forward_https_request(proxy_uri, uri, proxy_opts)
local body, err
local sock
local opts = proxy_opts or {}
local req_method = ngx_get_method()
local encoding = ngx.req.get_headers()["Transfer-Encoding"]
local is_chunked = encoding and encoding:lower() == "chunked"

Check warning on line 128 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L125-L128

Added lines #L125 - L128 were not covered by tests

if http_methods_with_body[req_method] then
if opts.request_unbuffered and ngx_http_version() == 1.1 then
local _, err = handle_expect()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if Expect needs to be handled for when buffering is enabled

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I do not think we should be doing this. The lua-resty-http lib is doing that for us. WDYT?

Copy link
Contributor Author

@tkan145 tkan145 Dec 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lib lua-resty-http is a client library and it handles the Expect returned from the server, while we are acting as a server here and need to process the Expect header from the client.

When I sent a large payload using cURL, the request hung, I later found out it was due to the Expect header.

I will run some more tests to see whether we really need it here

Copy link
Member

@eguzki eguzki Dec 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I think I understand now.

I think that when buffered is on, APIcast should protect upstream and should handle the Expect: 100-Continue. That is, it is the apicast who returns HTTP Response 100 Continue and then consumes the body before opening the connection to upstream. I think this is how it works right now in master. The request Expect: 100-Continue and response 100 Continue happens twice. First time between downstream and then between apicast and upstream (done by lua resty http lib because the Expect header is still there). We might consider removing the expect header on "buffered" mode. Unless we want to keep the Expect protocol with upstream to avoid sending the body if upstream does not want to. Which also makes sense to me. It is actually a requirement from rfc2616#section-8.2.3 to be like this. Check Requirements for HTTP/1.1 proxies: section.

When unbuffered is on, APIcast does not read the body with ngx.req.read_body(), thus, it does not send 100 Continue to downstream. I think that is the reason you saw the request hung. Ideally, I think that we should let upstream to decide if it wants to continue or not, and propagate the response to downstream. Downstream would start sending the body only when upstream tells to do that. I think it is quite hard to implement that. Basically because the lua resty http lib consumes the 100 Continue response of the upstream and then tries to send the body. I do not see a way to do this, other than sending manually the 100 Continue response to the downstream and create a body reader that will be consumed by the lua resty http library. But I can see some issues in there as well. What if upstream says 302 redirect or 400 bad request instead of 100 Continue? The downstream client would have already write the body in the downstream socket and that socket would be unusable for following up HTTP sessions. I do not know how to proceed regarding this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I heave re-written the message above. In case you have read it previosly, please re-read it again 🙏

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused here. I haven't read the openresty code but do you mean ngx.req.read_body() will send 100 Continue downstream? Doesn't that also mean that APIcast returns 100 Continue to the downstream application before establishing the upstream connection?

Regarding the 400, please correct me if I'm wrong, but I think the only case where the upstream server returns this error is if there is data in the request body. In my head the flow will be as follow

client -> Expect: 100-Continue -> upstream -> 100 Continue -> client
client -> start sending body -> upstream read body -> return 400

Copy link
Member

@eguzki eguzki Dec 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't read the openresty code but do you mean ngx.req.read_body() will send 100 Continue downstream?

Yes!

Doesn't that also mean that APIcast returns 100 Continue to the downstream application before establishing the upstream connection?

Exactly (when buffered mode is on)

the only case where the upstream server returns this error is if there is data in the request body

400 Bad Request is just an example. It could be 5XX error as well. In unbuffered mode, the workflow would be as follows (in my head)

client -> Expect: 100-Continue -> apicast
client <- 100 Continue <- apicast
client -> write body to socket -> apicast 
# Apicast did not read the body yet, it just created a body reader from the socket
apicast -> create connection via proxy -> TLS upstream
apicast (lua resty http) -> Expect: 100-Continue -> TLS upstream
apicast (lua resty http) <- 100 Continue <- TLS upstream
apicast (lua resty http) -> send body from the body reader -> TLS upstream

So let's say that upstream does not want it to start upload:

client -> Expect: 100-Continue -> apicast
client <- 100 Continue <- apicast
client -> write body to socket -> apicast 
# Apicast did not read the body yet, it just created a body reader from the socket
apicast -> create connection via proxy -> TLS upstream
apicast (lua resty http) -> Expect: 100-Continue -> TLS upstream
apicast (lua resty http) <- 5XX Error <- TLS upstream
client <-  5XX Error <- apicast

My issue with this is that the client has sent the body and nobody has consumed it. I need to try this scenario to see what we can do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From this nginx thread https://mailman.nginx.org/pipermail/nginx/2021-May/060643.html. I think nginx does not handle this well either

How about we send back error response, discard the body and close the socket?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we send back error response, discard the body and close the socket?

It's aggressive, but can be a way out.

if err then
ngx.log(ngx.ERR, "failed to handle expect header, err: ", err)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)

Check warning on line 135 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L130-L135

Added lines #L130 - L135 were not covered by tests
end

if is_chunked then

Check warning on line 138 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L138

Added line #L138 was not covered by tests
-- The default ngx reader does not support chunked request
-- so we will need to get the raw request socket and manually
-- decode the chunked request
sock, err = ngx.req.socket(true)

Check warning on line 142 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L142

Added line #L142 was not covered by tests
else
sock, err = ngx.req.socket()

Check warning on line 144 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L144

Added line #L144 was not covered by tests
end

if not sock then
ngx.log(ngx.ERR, "unable to obtain request socket: ", err)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)

Check warning on line 149 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L147-L149

Added lines #L147 - L149 were not covered by tests
end

body = client_body_reader(sock, DEFAULT_CHUNKSIZE, is_chunked)

Check warning on line 152 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L152

Added line #L152 was not covered by tests
else
-- This is needed to call ngx.req.get_body_data() below.
ngx.req.read_body()

Check warning on line 155 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L155

Added line #L155 was not covered by tests

-- We cannot use resty.http's .get_client_body_reader().
-- In POST requests with HTTPS, the result of that call is nil, and it
Expand All @@ -101,26 +163,53 @@
-- read and need to be cached in a local file. This request will return
-- nil, so after this we need to read the temp file.
-- https://github.com/openresty/lua-nginx-module#ngxreqget_body_data
body = ngx.req.get_body_data(),
proxy_uri = proxy_uri,
proxy_auth = proxy_auth
}
body = ngx.req.get_body_data()

Check warning on line 166 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L166

Added line #L166 was not covered by tests

if not body then
local temp_file_path = ngx.req.get_body_file()
ngx.log(ngx.INFO, "HTTPS Proxy: Request body is bigger than client_body_buffer_size, read the content from path='", temp_file_path, "'")

Check warning on line 170 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L168-L170

Added lines #L168 - L170 were not covered by tests

if temp_file_path then
body, err = file_reader(temp_file_path)
if err then
ngx.log(ngx.ERR, "HTTPS proxy: Failed to read temp body file, err: ", err)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)

Check warning on line 176 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L172-L176

Added lines #L172 - L176 were not covered by tests
end

if is_chunked then

Check warning on line 179 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L179

Added line #L179 was not covered by tests
-- If the body is smaller than "client_boby_buffer_size" the Content-Length header is
-- set based on the size of the buffer. However, when the body is rendered to a file,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the body is smaller than "client_boby_buffer_size" the Content-Length header is set based on the size of the buffer

Who is doing that? In other words, when all the conditions meet:

  • the request is chunked,
  • buffering is enabled
  • the request body is small

Who sets the Content-Length header?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lua-resty-http will set the Content-Length based on the body that we passed in. But good catch I should have put more details in the comment

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see, It's because it is a string and the resty-http gets the length out of it. It happens here. I would make it explicit, but good enough.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree because this is something that will come up again in future when troubleshooting but it doesn't need to be done in this PR, can be added at a later date that if headers["Content-Length"]=nil then headers["Content-Length"]=#body (this will at least be a useful reference for now)

-- we will need to calculate and manually set the Content-Length header based on the
-- file size
local contentLength, err = file_size(temp_file_path)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this safe to do for ALL requests which meet these conditions? I see that the calls in the file_size function are I/O blocking calls so I am wondering how harmful to performance those could be given they are not executed within a coroutine. If a coroutine cannot be used then we should consider using the lua-io-nginx module for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it enough to wrap that functionality with a coroutine? I don't know how useful that would be since it would yield on the first call anyway. Also the file_reader also call io.open all every request that has body buffered to file, so I guess we pay the price of calling io.open one more time?

But I totally agree with you that it is a I/O blocking function and should be avoided.

Checking the module lua-io-nginx I can see that this module is currently considered experimental. And it seems like it runs the task on another thread. However, I'm not so sure about this because we have to pay for context switching, threads, locking, etc.

It's worth to mention that the cost time of a single I/O operation won't be reduced, it was just
transferred from the main thread (the one executes the event loop) to another exclusive thread.
Indeed, the overhead might be a little higher, because of the extra tasks transferring, lock waiting,
Lua coroutine resumption (and can only be resumed in the next event loop) and so forth. Nevertheless,
after the offloading, the main thread doesn't block due to the I/O operation, and this is the fundamental
advantage compared with the native Lua I/O library.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No sure how expensive is

function fsize (filename)
      local handle, err = open(filename)
      local current = handle:seek()      -- get current position
      local size = handle:seek("end")    -- get file size
      handle:seek("set", current)        -- restore position
      return size
    end

Theoretically any IO operation could block the thread. We could try coroutines or any other mean to make it non blocking. Reading lua-nginx-module introduction it says:

Disk operations with relatively small amount of data can be done using the standard Lua io library but huge file reading and writing should be avoided wherever possible as they may block the Nginx process significantly. Delegating all network and disk I/O operations to Nginx's subrequests (via the [ngx.location.capture](https://github.com/openresty/lua-nginx-module#ngxlocationcapture) method and similar) is strongly recommended for maximum performance.

Not sure if we can follow that recommendation, though. @tkan145 we can try to discuss about this in a short call..

Anyway, AFAIK, we have never measured the capacity of APICast to handle traffic with request body big enough to be persisted in disk. All the tests performed where "simple" GET requests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed and also updated README file. @kevprice83 can you help review the README file and let me know if I need to add anything else?

if err then
ngx.log(ngx.ERR, "HTTPS proxy: Failed to set content length, err: ", err)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)

Check warning on line 187 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L184-L187

Added lines #L184 - L187 were not covered by tests
end

ngx.req.set_header("Content-Length", tostring(contentLength))

Check warning on line 190 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L190

Added line #L190 was not covered by tests
end
end
end

if not request.body then
local temp_file_path = ngx.req.get_body_file()
ngx.log(ngx.INFO, "HTTPS Proxy: Request body is bigger than client_body_buffer_size, read the content from path='", temp_file_path, "'")

if temp_file_path then
local body, err = file_reader(temp_file_path)
if err then
ngx.log(ngx.ERR, "HTTPS proxy: Failed to read temp body file, err: ", err)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
request.body = body
-- The whole request is buffered in the memory so remove the Transfer-Encoding: chunked
if is_chunked then
ngx.req.set_header("Transfer-Encoding", nil)

Check warning on line 197 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L196-L197

Added lines #L196 - L197 were not covered by tests
end
end
end

local httpc, err = http_proxy.new(request, skip_https_connect)
local request = {

Check warning on line 202 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L202

Added line #L202 was not covered by tests
uri = uri,
method = ngx.req.get_method(),
headers = ngx.req.get_headers(0, true),
path = format('%s%s%s', ngx.var.uri, ngx.var.is_args, ngx.var.query_string or ''),
body = body,
proxy_uri = proxy_uri,
proxy_auth = opts.proxy_auth

Check warning on line 209 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L209

Added line #L209 was not covered by tests
}

local httpc, err = http_proxy.new(request, opts.skip_https_connect)

Check warning on line 212 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L212

Added line #L212 was not covered by tests

if not httpc then
ngx.log(ngx.ERR, 'could not connect to proxy: ', proxy_uri, ' err: ', err)
Expand All @@ -132,8 +221,16 @@
res, err = httpc:request(request)

if res then
httpc:proxy_response(res)
httpc:set_keepalive()
if opts.request_unbuffered and is_chunked then
eguzki marked this conversation as resolved.
Show resolved Hide resolved
local bytes, err = send_response(sock, res, DEFAULT_CHUNKSIZE)
if not bytes then
ngx.log(ngx.ERR, "failed to send response: ", err)
return sock:send("HTTP/1.1 502 Bad Gateway")

Check warning on line 228 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L224-L228

Added lines #L224 - L228 were not covered by tests
end
else
httpc:proxy_response(res)
httpc:set_keepalive()

Check warning on line 232 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L231-L232

Added lines #L231 - L232 were not covered by tests
end
else
ngx.log(ngx.ERR, 'failed to proxy request to: ', proxy_uri, ' err : ', err)
return ngx.exit(ngx.HTTP_BAD_GATEWAY)
Expand Down Expand Up @@ -186,7 +283,13 @@
return
elseif uri.scheme == 'https' then
upstream:rewrite_request()
forward_https_request(proxy_uri, proxy_auth, uri, upstream.skip_https_connect)
local proxy_opts = {

Check warning on line 286 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L286

Added line #L286 was not covered by tests
proxy_auth = proxy_auth,
skip_https_connect = upstream.skip_https_connect,
request_unbuffered = upstream.request_unbuffered

Check warning on line 289 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L289

Added line #L289 was not covered by tests
}

forward_https_request(proxy_uri, uri, proxy_opts)

Check warning on line 292 in gateway/src/apicast/http_proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/http_proxy.lua#L292

Added line #L292 was not covered by tests
return ngx.exit(ngx.OK) -- terminate phase
else
ngx.log(ngx.ERR, 'could not connect to proxy: ', proxy_uri, ' err: ', 'invalid request scheme')
Expand Down
1 change: 1 addition & 0 deletions gateway/src/apicast/upstream.lua
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ function _M:call(context)
self:set_skip_https_connect_on_proxy();
end

self.request_unbuffered = context.request_unbuffered
http_proxy.request(self, proxy_uri)
else
local err = self:rewrite_request()
Expand Down
16 changes: 16 additions & 0 deletions gateway/src/resty/file.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,20 @@
end)
end

function _M.file_size(filename)
local handle, err = open(filename)

Check warning on line 32 in gateway/src/resty/file.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/file.lua#L32

Added line #L32 was not covered by tests

if err then
return nil, err

Check warning on line 35 in gateway/src/resty/file.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/file.lua#L34-L35

Added lines #L34 - L35 were not covered by tests
end

local current = handle:seek()
local size = handle:seek("end")

Check warning on line 39 in gateway/src/resty/file.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/file.lua#L38-L39

Added lines #L38 - L39 were not covered by tests

handle:seek("set", current)
handle:close()

Check warning on line 42 in gateway/src/resty/file.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/file.lua#L41-L42

Added lines #L41 - L42 were not covered by tests

return size

Check warning on line 44 in gateway/src/resty/file.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/file.lua#L44

Added line #L44 was not covered by tests
end

return _M
47 changes: 47 additions & 0 deletions gateway/src/resty/http/request_reader.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
local httpc = require "resty.resolver.http"

local _M = {
}

local cr_lf = "\r\n"

-- chunked_reader return a body reader that translates the data read from
-- lua-resty-http client_body_reader to HTTP "chunked" format before returning it
--
-- The chunked reader return nil when the final 0-length chunk is read
local function chunked_reader(sock, chunksize)
chunksize = chunksize or 65536
local eof = false
local reader = httpc:get_client_body_reader(chunksize, sock)
eguzki marked this conversation as resolved.
Show resolved Hide resolved
if not reader then
return nil

Check warning on line 17 in gateway/src/resty/http/request_reader.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/request_reader.lua#L13-L17

Added lines #L13 - L17 were not covered by tests
end

return function()
if eof then
return nil

Check warning on line 22 in gateway/src/resty/http/request_reader.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/request_reader.lua#L21-L22

Added lines #L21 - L22 were not covered by tests
end

local buffer, err = reader()
if err then
return nil, err

Check warning on line 27 in gateway/src/resty/http/request_reader.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/request_reader.lua#L25-L27

Added lines #L25 - L27 were not covered by tests
end
if buffer then
local chunk = string.format("%x\r\n", #buffer) .. buffer .. cr_lf
return chunk

Check warning on line 31 in gateway/src/resty/http/request_reader.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/request_reader.lua#L29-L31

Added lines #L29 - L31 were not covered by tests
else
eof = true
return "0\r\n\r\n"

Check warning on line 34 in gateway/src/resty/http/request_reader.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/request_reader.lua#L33-L34

Added lines #L33 - L34 were not covered by tests
end
end
end

function _M.get_client_body_reader(sock, chunksize, is_chunked)
if is_chunked then
return chunked_reader(sock, chunksize)

Check warning on line 41 in gateway/src/resty/http/request_reader.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/request_reader.lua#L40-L41

Added lines #L40 - L41 were not covered by tests
else
return httpc:get_client_body_reader(chunksize, sock)

Check warning on line 43 in gateway/src/resty/http/request_reader.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/request_reader.lua#L43

Added line #L43 was not covered by tests
end
end

return _M
58 changes: 58 additions & 0 deletions gateway/src/resty/http/response_writer.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
local _M = {
}

local cr_lf = "\r\n"

local function send(socket, data)
if not data or data == '' then
ngx.log(ngx.DEBUG, 'skipping sending nil')
return

Check warning on line 9 in gateway/src/resty/http/response_writer.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/response_writer.lua#L7-L9

Added lines #L7 - L9 were not covered by tests
end

return socket:send(data)

Check warning on line 12 in gateway/src/resty/http/response_writer.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/response_writer.lua#L12

Added line #L12 was not covered by tests
end

-- write_response writes response body reader to sock in the HTTP/1.x server response format,
-- The connection is closed if send() fails or when returning a non-zero
function _M.send_response(sock, response, chunksize)
eguzki marked this conversation as resolved.
Show resolved Hide resolved
local bytes, err
chunksize = chunksize or 65536

Check warning on line 19 in gateway/src/resty/http/response_writer.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/response_writer.lua#L19

Added line #L19 was not covered by tests

if not response then
ngx.log(ngx.ERR, "no response provided")
return

Check warning on line 23 in gateway/src/resty/http/response_writer.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/response_writer.lua#L21-L23

Added lines #L21 - L23 were not covered by tests
end

if not sock then
return nil, "socket not initialized yet"

Check warning on line 27 in gateway/src/resty/http/response_writer.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/response_writer.lua#L26-L27

Added lines #L26 - L27 were not covered by tests
end

-- Status line
local status = "HTTP/1.1 " .. response.status .. " " .. response.reason .. cr_lf
bytes, err = send(sock, status)
if not bytes then
return nil, "failed to send status line, err: " .. (err or "unknown")

Check warning on line 34 in gateway/src/resty/http/response_writer.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/response_writer.lua#L31-L34

Added lines #L31 - L34 were not covered by tests
end

-- Write body
local reader = response.body_reader

Check warning on line 38 in gateway/src/resty/http/response_writer.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/response_writer.lua#L38

Added line #L38 was not covered by tests
repeat
local chunk, read_err

chunk, read_err = reader(chunksize)
if read_err then
return nil, "failed to read response body, err: " .. (err or "unknown")

Check warning on line 44 in gateway/src/resty/http/response_writer.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/response_writer.lua#L42-L44

Added lines #L42 - L44 were not covered by tests
end

if chunk then
bytes, err = send(sock, chunk)
if not bytes then
return nil, "failed to send response body, err: " .. (err or "unknown")

Check warning on line 50 in gateway/src/resty/http/response_writer.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/response_writer.lua#L47-L50

Added lines #L47 - L50 were not covered by tests
end
end
until not chunk

Check warning on line 53 in gateway/src/resty/http/response_writer.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/response_writer.lua#L53

Added line #L53 was not covered by tests

return true, nil

Check warning on line 55 in gateway/src/resty/http/response_writer.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/response_writer.lua#L55

Added line #L55 was not covered by tests
end

return _M
Loading
Loading