Skip to content

Commit 7d74ce7

Browse files
committed
Merge branch 'write_body_from_file-options'
2 parents 4d2e787 + 13800ed commit 7d74ce7

File tree

6 files changed

+148
-45
lines changed

6 files changed

+148
-45
lines changed

NEWS

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ UNRELEASED
66
- Add http.tls.old_cipher_list (#112)
77
- Add http.cookie module (#117)
88
- Improvements to http.hsts module (#119)
9+
- Add `options` argument form to `stream:write_body_from_file()` (#125)
910

1011

1112
0.2 - 2017-05-28

doc/interfaces/stream.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,12 @@ Writes the string `chunk` to the stream. If `end_stream` is true, the body will
8989
Writes the string `str` to the stream and ends the stream. On error, returns `nil`, an error message and an error number.
9090

9191

92-
### `stream:write_body_from_file(file, timeout)` <!-- --> {#stream:write_body_from_file}
92+
### `stream:write_body_from_file(options|file, timeout)` <!-- --> {#stream:write_body_from_file}
93+
94+
- `options` is a table containing:
95+
- `.file` (file)
96+
- `.count` (positive integer): number of bytes of `file` to write
97+
defaults to infinity (the whole file will be written)
9398

9499
Writes the contents of file `file` to the stream and ends the stream. `file` will not be automatically seeked, so ensure it is at the correct offset before calling. On error, returns `nil`, an error message and an error number.
95100

doc/modules/http.h1_stream.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@ See [`stream:write_chunk(chunk, end_stream, timeout)`](#stream:write_chunk)
9494
See [`stream:write_body_from_string(str, timeout)`](#stream:write_body_from_string)
9595

9696

97-
### `h1_stream:write_body_from_file(file, timeout)` <!-- --> {#http.h1_stream:write_body_from_file}
97+
### `h1_stream:write_body_from_file(options|file, timeout)` <!-- --> {#http.h1_stream:write_body_from_file}
9898

99-
See [`stream:write_body_from_file(file, timeout)`](#stream:write_body_from_file)
99+
See [`stream:write_body_from_file(options|file, timeout)`](#stream:write_body_from_file)
100100

101101

102102
### `h1_stream:shutdown()` <!-- --> {#http.h1_stream:shutdown}

doc/modules/http.h2_stream.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ See [`stream:write_chunk(chunk, end_stream, timeout)`](#stream:write_chunk)
8787
See [`stream:write_body_from_string(str, timeout)`](#stream:write_body_from_string)
8888

8989

90-
### `h2_stream:write_body_from_file(file, timeout)` <!-- --> {#http.h2_stream:write_body_from_file}
90+
### `h2_stream:write_body_from_file(options|file, timeout)` <!-- --> {#http.h2_stream:write_body_from_file}
9191

92-
See [`stream:write_body_from_file(file, timeout)`](#stream:write_body_from_file)
92+
See [`stream:write_body_from_file(options|file, timeout)`](#stream:write_body_from_file)
9393

9494

9595
### `h2_stream:shutdown()` <!-- --> {#http.h2_stream:shutdown}

http/stream_common.lua

+18-4
Original file line numberDiff line numberDiff line change
@@ -152,21 +152,35 @@ function stream_methods:write_body_from_string(str, timeout)
152152
return self:write_chunk(str, true, timeout)
153153
end
154154

155-
function stream_methods:write_body_from_file(file, timeout)
155+
function stream_methods:write_body_from_file(options, timeout)
156156
local deadline = timeout and (monotime()+timeout)
157-
-- Can't use :lines here as in Lua 5.1 it doesn't take a parameter
158-
while true do
159-
local chunk, err = file:read(CHUNK_SIZE)
157+
local file, count
158+
if io.type(options) then -- lua-http <= 0.2 took a file handle
159+
file = options
160+
else
161+
file = options.file
162+
count = options.count
163+
end
164+
if count == nil then
165+
count = math.huge
166+
elseif type(count) ~= "number" or count < 0 or count % 1 ~= 0 then
167+
error("invalid .count parameter (expected positive integer)")
168+
end
169+
while count > 0 do
170+
local chunk, err = file:read(math.min(CHUNK_SIZE, count))
160171
if chunk == nil then
161172
if err then
162173
error(err)
174+
elseif count ~= math.huge and count > 0 then
175+
error("unexpected EOF")
163176
end
164177
break
165178
end
166179
local ok, err2, errno2 = self:write_chunk(chunk, false, deadline and (deadline-monotime()))
167180
if not ok then
168181
return nil, err2, errno2
169182
end
183+
count = count - #chunk
170184
end
171185
return self:write_chunk("", true, deadline and (deadline-monotime()))
172186
end

spec/stream_common_spec.lua

+119-36
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,19 @@ describe("http.stream_common", function()
1010
c = h1_connection.new(c, "client", version)
1111
return s, c
1212
end
13+
local function new_request_headers()
14+
local headers = new_headers()
15+
headers:append(":authority", "myauthority")
16+
headers:append(":method", "GET")
17+
headers:append(":path", "/")
18+
return headers
19+
end
1320
it("Can read a number of characters", function()
1421
local server, client = new_pair(1.1)
1522
local cq = cqueues.new()
1623
cq:wrap(function()
1724
local stream = client:new_stream()
18-
local headers = new_headers()
19-
headers:append(":authority", "myauthority")
20-
headers:append(":method", "GET")
21-
headers:append(":path", "/")
22-
assert(stream:write_headers(headers, false))
25+
assert(stream:write_headers(new_request_headers(), false))
2326
assert(stream:write_chunk("foo", false))
2427
assert(stream:write_chunk("\nb", false))
2528
assert(stream:write_chunk("ar\n", true))
@@ -47,11 +50,7 @@ describe("http.stream_common", function()
4750
local cq = cqueues.new()
4851
cq:wrap(function()
4952
local stream = client:new_stream()
50-
local headers = new_headers()
51-
headers:append(":authority", "myauthority")
52-
headers:append(":method", "GET")
53-
headers:append(":path", "/")
54-
assert(stream:write_headers(headers, false))
53+
assert(stream:write_headers(new_request_headers(), false))
5554
assert(stream:write_chunk("foo", false))
5655
assert(stream:write_chunk("\nb", false))
5756
assert(stream:write_chunk("ar\n", true))
@@ -72,11 +71,7 @@ describe("http.stream_common", function()
7271
local cq = cqueues.new()
7372
cq:wrap(function()
7473
local stream = client:new_stream()
75-
local headers = new_headers()
76-
headers:append(":authority", "myauthority")
77-
headers:append(":method", "GET")
78-
headers:append(":path", "/")
79-
assert(stream:write_headers(headers, false))
74+
assert(stream:write_headers(new_request_headers(), false))
8075
assert(stream:write_chunk("hello world!", true))
8176
end)
8277
cq:wrap(function()
@@ -89,28 +84,116 @@ describe("http.stream_common", function()
8984
client:close()
9085
server:close()
9186
end)
92-
it("can write body from temporary file", function()
93-
local server, client = new_pair(1.1)
94-
local cq = cqueues.new()
95-
cq:wrap(function()
96-
local file = io.tmpfile()
97-
assert(file:write("hello world!"))
98-
assert(file:seek("set"))
99-
local stream = client:new_stream()
100-
local headers = new_headers()
101-
headers:append(":authority", "myauthority")
102-
headers:append(":method", "GET")
103-
headers:append(":path", "/")
104-
assert(stream:write_headers(headers, false))
105-
assert(stream:write_body_from_file(file))
87+
describe("write_body_from_file", function()
88+
it("works with a temporary file", function()
89+
local server, client = new_pair(1.1)
90+
local cq = cqueues.new()
91+
cq:wrap(function()
92+
local file = io.tmpfile()
93+
assert(file:write("hello world!"))
94+
assert(file:seek("set"))
95+
local stream = client:new_stream()
96+
assert(stream:write_headers(new_request_headers(), false))
97+
assert(stream:write_body_from_file(file))
98+
end)
99+
cq:wrap(function()
100+
local stream = assert(server:get_next_incoming_stream())
101+
assert.same("hello world!", assert(stream:get_body_as_string()))
102+
end)
103+
assert_loop(cq, TEST_TIMEOUT)
104+
assert.truthy(cq:empty())
105+
client:close()
106+
server:close()
106107
end)
107-
cq:wrap(function()
108-
local stream = assert(server:get_next_incoming_stream())
109-
assert.same("hello world!", assert(stream:get_body_as_string()))
108+
it("works using the options form", function()
109+
local server, client = new_pair(1.1)
110+
local cq = cqueues.new()
111+
cq:wrap(function()
112+
local file = io.tmpfile()
113+
assert(file:write("hello world!"))
114+
assert(file:seek("set"))
115+
local stream = client:new_stream()
116+
assert(stream:write_headers(new_request_headers(), false))
117+
assert(stream:write_body_from_file({
118+
file = file;
119+
}))
120+
end)
121+
cq:wrap(function()
122+
local stream = assert(server:get_next_incoming_stream())
123+
assert.same("hello world!", assert(stream:get_body_as_string()))
124+
end)
125+
assert_loop(cq, TEST_TIMEOUT)
126+
assert.truthy(cq:empty())
127+
client:close()
128+
server:close()
129+
end)
130+
it("validates .count option", function()
131+
local server, client = new_pair(1.1)
132+
local cq = cqueues.new()
133+
cq:wrap(function()
134+
local stream = client:new_stream()
135+
assert(stream:write_headers(new_request_headers(), false))
136+
assert.has_error(function()
137+
stream:write_body_from_file({
138+
file = io.tmpfile();
139+
count = "invalid count field";
140+
})
141+
end)
142+
end)
143+
cq:wrap(function()
144+
assert(server:get_next_incoming_stream())
145+
end)
146+
assert_loop(cq, TEST_TIMEOUT)
147+
assert.truthy(cq:empty())
148+
client:close()
149+
server:close()
150+
end)
151+
it("limits number of bytes when using .count option", function()
152+
local server, client = new_pair(1.1)
153+
local cq = cqueues.new()
154+
cq:wrap(function()
155+
local file = io.tmpfile()
156+
assert(file:write("hello world!"))
157+
assert(file:seek("set"))
158+
local stream = client:new_stream()
159+
assert(stream:write_headers(new_request_headers(), false))
160+
assert(stream:write_body_from_file({
161+
file = file;
162+
count = 5;
163+
}))
164+
end)
165+
cq:wrap(function()
166+
local stream = assert(server:get_next_incoming_stream())
167+
assert.same("hello", assert(stream:get_body_as_string()))
168+
end)
169+
assert_loop(cq, TEST_TIMEOUT)
170+
assert.truthy(cq:empty())
171+
client:close()
172+
server:close()
173+
end)
174+
it("reports an error on early EOF", function()
175+
local server, client = new_pair(1.1)
176+
local cq = cqueues.new()
177+
cq:wrap(function()
178+
local file = io.tmpfile()
179+
assert(file:write("hello world!"))
180+
assert(file:seek("set"))
181+
local stream = client:new_stream()
182+
assert(stream:write_headers(new_request_headers(), false))
183+
assert.has_error(function()
184+
assert(stream:write_body_from_file({
185+
file = file;
186+
count = 50; -- longer than the file
187+
}))
188+
end)
189+
end)
190+
cq:wrap(function()
191+
assert(server:get_next_incoming_stream())
192+
end)
193+
assert_loop(cq, TEST_TIMEOUT)
194+
assert.truthy(cq:empty())
195+
client:close()
196+
server:close()
110197
end)
111-
assert_loop(cq, TEST_TIMEOUT)
112-
assert.truthy(cq:empty())
113-
client:close()
114-
server:close()
115198
end)
116199
end)

0 commit comments

Comments
 (0)