Skip to content

Commit 73c8984

Browse files
feature: preserve body after parse (#63)
Co-authored-by: Johnny Wang <[email protected]>
1 parent 48a6960 commit 73c8984

File tree

3 files changed

+201
-3
lines changed

3 files changed

+201
-3
lines changed

README.markdown

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,19 @@ not want to save the data on local file systems.
161161

162162
[Back to TOC](#table-of-contents)
163163

164+
Usage
165+
=====
166+
167+
```lua
168+
local upload = require "resty.upload"
169+
local form, err = upload:new(self, chunk_size, max_line_size, preserve_body)
170+
```
171+
`chunk_size` defaults to 4096. It is the size used to read data from the socket.
172+
173+
`max_line_size` defaults to 512. It is the size limit to read the chunked body header.
174+
175+
By Default, `lua-resty-upload` will consume the request body. For proxy mode this means upstream will not see the body. When `preserve_body` is set to true, the request body will be preserved. Note that this option is not free. When enabled, it will double the memory usage of `resty.upload`.
176+
164177
Author
165178
======
166179

lib/resty/upload.lua

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ local match = string.match
77
local setmetatable = setmetatable
88
local type = type
99
local ngx_var = ngx.var
10+
local ngx_init_body = ngx.req.init_body
11+
local ngx_finish_body = ngx.req.finish_body
12+
local ngx_append_body = ngx.req.append_body
1013
-- local print = print
1114

1215

@@ -21,11 +24,57 @@ local STATE_READING_HEADER = 2
2124
local STATE_READING_BODY = 3
2225
local STATE_EOF = 4
2326

24-
2527
local mt = { __index = _M }
2628

2729
local state_handlers
2830

31+
local function wrapped_receiveuntil(self, until_str)
32+
local iter, err_outer = self:old_receiveuntil(until_str)
33+
if iter == nil then
34+
ngx_finish_body()
35+
end
36+
37+
local function wrapped(size)
38+
local ret, err = iter(size)
39+
if ret then
40+
ngx_append_body(ret)
41+
end
42+
43+
-- non-nil ret for call with no size or successful size call and nil ret
44+
if (not size and ret) or (size and not ret and not err) then
45+
ngx_append_body(until_str)
46+
end
47+
return ret, err
48+
end
49+
50+
return wrapped, err_outer
51+
end
52+
53+
54+
local function wrapped_receive(self, arg)
55+
local ret, err, partial = self:old_receive(arg)
56+
if ret then
57+
ngx_append_body(ret)
58+
59+
elseif partial then
60+
ngx_append_body(partial)
61+
end
62+
63+
if ret == nil then
64+
ngx_finish_body()
65+
end
66+
67+
return ret, err
68+
end
69+
70+
71+
local function req_socket_body_collector(sock)
72+
sock.old_receiveuntil = sock.receiveuntil
73+
sock.old_receive = sock.receive
74+
sock.receiveuntil = wrapped_receiveuntil
75+
sock.receive = wrapped_receive
76+
end
77+
2978

3079
local function get_boundary()
3180
local header = ngx_var.content_type
@@ -46,7 +95,7 @@ local function get_boundary()
4695
end
4796

4897

49-
function _M.new(self, chunk_size, max_line_size)
98+
function _M.new(self, chunk_size, max_line_size, preserve_body)
5099
local boundary = get_boundary()
51100

52101
-- print("boundary: ", boundary)
@@ -62,6 +111,11 @@ function _M.new(self, chunk_size, max_line_size)
62111
return nil, err
63112
end
64113

114+
if preserve_body then
115+
ngx_init_body(chunk_size)
116+
req_socket_body_collector(sock)
117+
end
118+
65119
local read2boundary, err = sock:receiveuntil("--" .. boundary)
66120
if not read2boundary then
67121
return nil, err
@@ -79,7 +133,8 @@ function _M.new(self, chunk_size, max_line_size)
79133
read2boundary = read2boundary,
80134
read_line = read_line,
81135
boundary = boundary,
82-
state = STATE_BEGIN
136+
state = STATE_BEGIN,
137+
preserve_body = preserve_body
83138
}, mt)
84139
end
85140

@@ -104,6 +159,10 @@ local function discard_line(self)
104159

105160
local dummy, err = read_line(1)
106161
if dummy then
162+
if self.preserve_body then
163+
ngx_finish_body()
164+
end
165+
107166
return nil, "line too long: " .. line .. dummy .. "..."
108167
end
109168

@@ -179,6 +238,10 @@ local function read_header(self)
179238

180239
local dummy, err = read_line(1)
181240
if dummy then
241+
if self.preserve_body then
242+
ngx_finish_body()
243+
end
244+
182245
return nil, nil, "line too long: " .. line .. dummy .. "..."
183246
end
184247

t/sanity.t

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,3 +648,125 @@ read: ["eof"]
648648
--- no_error_log
649649
[error]
650650

651+
652+
653+
=== TEST 11: body preserve off
654+
--- http_config eval: $::HttpConfig
655+
--- config
656+
location /t {
657+
content_by_lua '
658+
local upload = require "resty.upload"
659+
local ljson = require "ljson"
660+
661+
local form = upload:new(5)
662+
663+
form:set_timeout(1000) -- 1 sec
664+
665+
while true do
666+
local typ, res, err = form:read()
667+
if not typ then
668+
ngx.say("failed to read: ", err)
669+
return
670+
end
671+
672+
ngx.say("read: ", ljson.encode({typ, res}))
673+
674+
if typ == "eof" then
675+
break
676+
end
677+
end
678+
679+
local typ, res, err = form:read()
680+
ngx.say("read: ", ljson.encode({typ, res}))
681+
682+
ngx.say("remain body: ", ngx.req.get_body_data(), ",", ngx.req.get_body_file())
683+
';
684+
}
685+
--- more_headers
686+
Content-Type: multipart/form-data; boundary=---------------------------820127721219505131303151179
687+
--- request eval
688+
qq{POST /t\n-----------------------------820127721219505131303151179\r
689+
Content-Disposition: form-data; name="file1"; filename="a.txt"\r
690+
Content-Type: text/plain\r
691+
\r
692+
Hello, world\r\n-----------------------------820127721219505131303151179\r
693+
Content-Disposition: form-data; name="test"\r
694+
\r
695+
value\r
696+
\r\n-----------------------------820127721219505131303151179--\r
697+
}
698+
--- response_body
699+
read: ["header",["Content-Disposition","form-data; name=\"file1\"; filename=\"a.txt\"","Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\""]]
700+
read: ["header",["Content-Type","text/plain","Content-Type: text/plain"]]
701+
read: ["body","Hello"]
702+
read: ["body",", wor"]
703+
read: ["body","ld"]
704+
read: ["part_end"]
705+
read: ["header",["Content-Disposition","form-data; name=\"test\"","Content-Disposition: form-data; name=\"test\""]]
706+
read: ["body","value"]
707+
read: ["body","\r\n"]
708+
read: ["part_end"]
709+
read: ["eof"]
710+
read: ["eof"]
711+
remain body: nil,nil
712+
--- no_error_log
713+
[error]
714+
715+
716+
717+
=== TEST 12: body preserve on
718+
--- http_config eval: $::HttpConfig
719+
--- config
720+
location /t {
721+
content_by_lua '
722+
local original_len = ngx.req.get_headers()["Content-Length"]
723+
local upload = require "resty.upload"
724+
725+
local form = upload:new(5, nil, true)
726+
727+
form:set_timeout(1000) -- 1 sec
728+
729+
while true do
730+
local typ, res, err = form:read()
731+
if not typ then
732+
ngx.say("failed to read: ", err)
733+
return
734+
end
735+
736+
if typ == "eof" then
737+
break
738+
end
739+
end
740+
741+
local typ, res, err = form:read()
742+
743+
local body = ngx.req.get_body_data()
744+
local new_len
745+
if body then
746+
new_len = #body
747+
else
748+
new_len = io.open(ngx.req.get_body_file(), "r"):seek("end")
749+
end
750+
ngx.say("content_length: ", original_len)
751+
ngx.say("remain body length: ", new_len)
752+
';
753+
}
754+
--- more_headers
755+
Content-Type: multipart/form-data; boundary=---------------------------820127721219505131303151179
756+
--- request eval
757+
qq{POST /t\n-----------------------------820127721219505131303151179\r
758+
Content-Disposition: form-data; name="file1"; filename="a.txt"\r
759+
Content-Type: text/plain\r
760+
\r
761+
Hello, world\r\n-----------------------------820127721219505131303151179\r
762+
Content-Disposition: form-data; name="test"\r
763+
\r
764+
value\r
765+
\r\n-----------------------------820127721219505131303151179--\r
766+
}
767+
--- response_body
768+
content_length: 336
769+
remain body length: 336
770+
771+
--- no_error_log
772+
[error]

0 commit comments

Comments
 (0)