New upstream version 0.4
Santiago Ruano Rincón
3 years ago
0 | name: ci | |
1 | ||
2 | on: | |
3 | pull_request: {} | |
4 | push: | |
5 | branches: [ $default-branch ] | |
6 | ||
7 | jobs: | |
8 | luacheck: | |
9 | runs-on: ubuntu-latest | |
10 | steps: | |
11 | - uses: actions/checkout@v2 | |
12 | with: | |
13 | path: lua-http | |
14 | - uses: leafo/gh-actions-lua@v8.0.0 | |
15 | - uses: leafo/gh-actions-luarocks@v4.0.0 | |
16 | - name: install-tooling | |
17 | run: luarocks install luacheck | |
18 | - name: luacheck | |
19 | run: | | |
20 | cd lua-http | |
21 | luacheck . | |
22 | ||
23 | test: | |
24 | runs-on: ubuntu-latest | |
25 | strategy: | |
26 | matrix: | |
27 | luaVersion: | |
28 | - "5.1" | |
29 | - "5.2" | |
30 | - "5.3" | |
31 | - "5.4" | |
32 | - luajit-2.0.5 | |
33 | - luajit-2.1.0-beta3 | |
34 | luaCompileFlags: [""] | |
35 | zlib: ["", "lzlib", "lua-zlib"] | |
36 | remove_compat53: [false] | |
37 | ||
38 | exclude: | |
39 | # lzlib doesn't support Lua 5.4+ | |
40 | - luaVersion: "5.4" | |
41 | zlib: "lzlib" | |
42 | include: | |
43 | - luaVersion: "5.3" | |
44 | luaCompileFlags: LUA_CFLAGS="-DLUA_INT_TYPE=LUA_INT_INT" | |
45 | - luaVersion: "5.3" | |
46 | remove_compat53: true | |
47 | ||
48 | steps: | |
49 | - uses: actions/checkout@v2 | |
50 | with: | |
51 | path: lua-http | |
52 | - uses: leafo/gh-actions-lua@v8.0.0 | |
53 | with: | |
54 | luaVersion: ${{ matrix.luaVersion }} | |
55 | - uses: leafo/gh-actions-luarocks@v4.0.0 | |
56 | - name: install-tooling | |
57 | run: | | |
58 | luarocks install luacov-coveralls | |
59 | luarocks install busted | |
60 | - name: install-dependencies | |
61 | run: | | |
62 | cd lua-http | |
63 | luarocks install --only-deps http-scm-0.rockspec | |
64 | ||
65 | - name: install-lzlib | |
66 | if: matrix.zlib == 'lzlib' | |
67 | run: luarocks install lzlib | |
68 | - name: install-lua-zlib | |
69 | if: matrix.zlib == 'lua-zlib' | |
70 | run: luarocks install lua-zlib | |
71 | ||
72 | - name: remove-compat53 | |
73 | if: matrix.remove_compat53 | |
74 | run: luarocks remove compat53 | |
75 | ||
76 | - name: test | |
77 | run: | | |
78 | cd lua-http | |
79 | busted -c -o utfTerminal | |
80 | ||
81 | - name: coveralls | |
82 | continue-on-error: true | |
83 | env: | |
84 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} | |
85 | run: | | |
86 | cd lua-http | |
87 | luacov-coveralls -v | |
88 | ||
89 | typedlua: | |
90 | runs-on: ubuntu-latest | |
91 | steps: | |
92 | - uses: actions/checkout@v2 | |
93 | with: | |
94 | path: lua-http | |
95 | - uses: leafo/gh-actions-lua@v8.0.0 | |
96 | with: | |
97 | luaVersion: "5.3" # tlc doesn't work with 5.4+ | |
98 | - uses: leafo/gh-actions-luarocks@v4.0.0 | |
99 | - name: install-tooling | |
100 | run: luarocks install https://raw.githubusercontent.com/andremm/typedlua/master/typedlua-scm-1.rockspec | |
101 | - name: install-dependencies | |
102 | run: | | |
103 | cd lua-http | |
104 | luarocks install --only-deps http-scm-0.rockspec | |
105 | - name: typedlua | |
106 | run: | | |
107 | cd lua-http | |
108 | tlc -o /dev/null spec/require-all.lua |
0 | language: python | |
1 | ||
2 | sudo: false | |
3 | ||
4 | env: | |
5 | matrix: | |
6 | - LUA="lua 5.1" | |
7 | - LUA="lua 5.1" ZLIB=lzlib | |
8 | - LUA="lua 5.1" ZLIB=lua-zlib | |
9 | - LUA="lua 5.2" | |
10 | - LUA="lua 5.2" ZLIB=lzlib | |
11 | - LUA="lua 5.2" ZLIB=lua-zlib | |
12 | - LUA="lua 5.3" | |
13 | - LUA="lua 5.3" ZLIB=lzlib | |
14 | - LUA="lua 5.3" ZLIB=lua-zlib | |
15 | - LUA="lua 5.3" COMPAT53=no | |
16 | - LUA="lua 5.3" LUA_CFLAGS="-DLUA_INT_TYPE=LUA_INT_INT" | |
17 | - LUA="luajit @" | |
18 | - LUA="luajit @" ZLIB=lzlib | |
19 | - LUA="luajit @" ZLIB=lua-zlib | |
20 | - LUA="luajit 2.0" | |
21 | - LUA="luajit 2.0" ZLIB=lzlib | |
22 | - LUA="luajit 2.0" ZLIB=lua-zlib | |
23 | - LUA="luajit 2.1" | |
24 | - LUA="luajit 2.1" ZLIB=lzlib | |
25 | - LUA="luajit 2.1" ZLIB=lua-zlib | |
26 | ||
27 | branches: | |
28 | only: | |
29 | - master | |
30 | ||
31 | before_install: | |
32 | - pip install hererocks | |
33 | - hererocks ~/hererocks -r^ --$LUA --cflags=$LUA_CFLAGS | |
34 | - export PATH=$PATH:~/hererocks/bin | |
35 | - eval $(luarocks path --bin) | |
36 | - luarocks install luacheck | |
37 | - luarocks install https://raw.githubusercontent.com/andremm/typedlua/master/typedlua-scm-1.rockspec | |
38 | - luarocks install luacov-coveralls | |
39 | - luarocks install busted | |
40 | ||
41 | install: | |
42 | - luarocks install --only-deps http-scm-0.rockspec | |
43 | - if [ "$ZLIB" = "lzlib" ]; then luarocks install lzlib; fi | |
44 | - if [ "$ZLIB" = "lua-zlib" ]; then luarocks install lua-zlib; fi | |
45 | - if [ "$COMPAT53" = "no" ]; then luarocks remove compat53; fi | |
46 | ||
47 | script: | |
48 | - luacheck . | |
49 | - tlc -o /dev/null spec/require-all.lua | |
50 | - busted -c | |
51 | ||
52 | after_success: | |
53 | - luacov-coveralls -v | |
54 | ||
55 | notifications: | |
56 | email: | |
57 | on_success: change | |
58 | on_failure: always | |
59 | ||
60 | cache: | |
61 | directories: | |
62 | - $HOME/.cache/hererocks |
0 | 0 | The MIT License (MIT) |
1 | 1 | |
2 | Copyright (c) 2015-2019 Daurnimator | |
2 | Copyright (c) 2015-2021 Daurnimator | |
3 | 3 | |
4 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | 5 | of this software and associated documentation files (the "Software"), to deal |
0 | 0.4 - 2021-02-06 | |
1 | ||
2 | - Support multiple elliptic curves under OpenSSL 1.1.1+ (#150) | |
3 | - Improve support for Lua 5.4 (not longer require bit library to be installed) (#180) | |
4 | - Ignore delayed RST_STREAM frames in HTTP 2 (#145) | |
5 | ||
6 | ||
0 | 7 | 0.3 - 2019-02-13 |
1 | 8 | |
2 | 9 | - Fix incorrect Sec-WebSocket-Protocol negotiation |
16 | 16 | |
17 | 17 | ## Status |
18 | 18 | |
19 | [![Build Status](https://travis-ci.org/daurnimator/lua-http.svg)](https://travis-ci.org/daurnimator/lua-http) | |
19 | [![Build Status](https://github.com/daurnimator/lua-http/workflows/ci/badge.svg)](https://github.com/daurnimator/lua-http/actions?query=workflow%3Aci) | |
20 | 20 | [![Coverage Status](https://coveralls.io/repos/daurnimator/lua-http/badge.svg?branch=master&service=github)](https://coveralls.io/github/daurnimator/lua-http?branch=master) |
21 | 21 | [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/108/badge)](https://bestpractices.coreinfrastructure.org/projects/108) |
22 | 22 |
112 | 112 | |
113 | 113 | ### `h1_connection:read_request_line(timeout)` <!-- --> {#http.h1_connection:read_request_line} |
114 | 114 | |
115 | Reads a request line from the socket. Returns the request method, requested path and HTTP version for an incoming request. `:read_request_line()` yields until a `"\r\n"` terminated chunk is received, or `timeout` is exceeded. If the incoming chunk is not a valid HTTP request line, `nil` is returned. On error, returns `nil`, an error message and an error number. | |
115 | Reads a request line from the socket. Returns the request method, request target and HTTP version for an incoming request. `:read_request_line()` yields until a `"\r\n"` terminated chunk is received, or `timeout` is exceeded. If the incoming chunk is not a valid HTTP request line, `nil` is returned. On error, returns `nil`, an error message and an error number. | |
116 | 116 | |
117 | 117 | |
118 | 118 | ### `h1_connection:read_status_line(timeout)` <!-- --> {#http.h1_connection:read_status_line} |
145 | 145 | Reads the next available line of data from the request and returns the chunk and any chunk extensions. This function will yield until chunk size is received or `timeout` is exceeded. If the chunk size is indicated as `0` then `false` and any chunk extensions are returned. Returns `nil`, an error message and an error number if there was an error reading reading the chunk header or the socket. |
146 | 146 | |
147 | 147 | |
148 | ### `h1_connection:write_request_line(method, path, httpversion, timeout)` <!-- --> {#http.h1_connection:write_request_line} | |
148 | ### `h1_connection:write_request_line(method, target, httpversion, timeout)` <!-- --> {#http.h1_connection:write_request_line} | |
149 | 149 | |
150 | 150 | Writes the opening HTTP 1.x request line for a new request to the socket buffer. Yields until success or `timeout`. If the write fails, returns `nil`, an error message and an error number. |
151 | 151 |
6 | 6 | This means we can ignore the differences between bit libraries. |
7 | 7 | ]] |
8 | 8 | |
9 | -- Lua 5.3 has built-in bit operators, wrap them in a function. | |
10 | if _VERSION == "Lua 5.3" then | |
9 | -- Lua 5.1 didn't have `load` or bitwise operators, just let it fall through. | |
10 | if _VERSION ~= "Lua 5.1" then | |
11 | -- Lua 5.3+ has built-in bit operators, wrap them in a function. | |
11 | 12 | -- Use debug.getinfo to get correct file+line numbers for loaded snippet |
12 | 13 | local info = debug.getinfo(1, "Sl") |
13 | return assert(load(("\n"):rep(info.currentline+1)..[[return { | |
14 | local has_bitwise, bitwise = pcall(load(("\n"):rep(info.currentline+1)..[[return { | |
14 | 15 | band = function(a, b) return a & b end; |
15 | 16 | bor = function(a, b) return a | b end; |
16 | 17 | bxor = function(a, b) return a ~ b end; |
17 | }]], info.source))() | |
18 | }]], info.source)) | |
19 | if has_bitwise then | |
20 | return bitwise | |
21 | end | |
18 | 22 | end |
19 | 23 | |
20 | 24 | -- The "bit" library that comes with luajit |
166 | 166 | end |
167 | 167 | return nil, err, errno |
168 | 168 | end |
169 | local method, path, httpversion = line:match("^(%w+) (%S+) HTTP/(1%.[01])\r\n$") | |
169 | local method, target, httpversion = line:match("^(%w+) (%S+) HTTP/(1%.[01])\r\n$") | |
170 | 170 | if not method then |
171 | 171 | self.socket:seterror("r", ce.EILSEQ) |
172 | 172 | local ok, errno2 = self.socket:unget(line) |
182 | 182 | return nil, onerror(self.socket, "read_request_line", ce.EILSEQ) |
183 | 183 | end |
184 | 184 | httpversion = httpversion == "1.0" and 1.0 or 1.1 -- Avoid tonumber() due to locale issues |
185 | return method, path, httpversion | |
185 | return method, target, httpversion | |
186 | 186 | end |
187 | 187 | |
188 | 188 | function connection_methods:read_status_line(timeout) |
343 | 343 | end |
344 | 344 | end |
345 | 345 | |
346 | function connection_methods:write_request_line(method, path, httpversion, timeout) | |
346 | function connection_methods:write_request_line(method, target, httpversion, timeout) | |
347 | 347 | assert(method:match("^[^ \r\n]+$")) |
348 | assert(path:match("^[^ \r\n]+$")) | |
348 | assert(target:match("^[^ \r\n]+$")) | |
349 | 349 | assert(httpversion == 1.0 or httpversion == 1.1) |
350 | local line = string.format("%s %s HTTP/%s\r\n", method, path, httpversion == 1.0 and "1.0" or "1.1") | |
350 | local line = string.format("%s %s HTTP/%s\r\n", method, target, httpversion == 1.0 and "1.0" or "1.1") | |
351 | 351 | local ok, err, errno = self.socket:xwrite(line, "f", timeout) |
352 | 352 | if not ok then |
353 | 353 | return nil, err, errno |
266 | 266 | if self.state == "half closed (local)" then |
267 | 267 | return nil |
268 | 268 | end |
269 | local method, path, httpversion = self.connection:read_request_line(0) | |
269 | local method, target, httpversion = self.connection:read_request_line(0) | |
270 | 270 | if method == nil then |
271 | 271 | if httpversion == ce.ETIMEDOUT then |
272 | 272 | timeout = deadline and deadline-monotime() |
274 | 274 | return self:read_headers(deadline and deadline-monotime()) |
275 | 275 | end |
276 | 276 | end |
277 | return nil, path, httpversion | |
277 | return nil, target, httpversion | |
278 | 278 | end |
279 | 279 | self.req_method = method |
280 | 280 | self.peer_version = httpversion |
281 | 281 | headers = new_headers() |
282 | 282 | headers:append(":method", method) |
283 | 283 | if method == "CONNECT" then |
284 | headers:append(":authority", path) | |
284 | headers:append(":authority", target) | |
285 | 285 | else |
286 | headers:append(":path", path) | |
286 | headers:append(":path", target) | |
287 | 287 | end |
288 | 288 | headers:append(":scheme", self:checktls() and "https" or "http") |
289 | 289 | self:set_state("open") |
481 | 481 | [":path"] = true; |
482 | 482 | [":scheme"] = true; |
483 | 483 | [":status"] = true; |
484 | [":protocol"] = true; -- from RFC 8441 | |
484 | 485 | -- fields written manually in :write_headers |
485 | 486 | ["connection"] = true; |
486 | 487 | ["content-length"] = true; |
563 | 564 | if self.state == "idle" then |
564 | 565 | method = assert(headers:get(":method"), "missing method") |
565 | 566 | self.req_method = method |
566 | local path | |
567 | local target | |
567 | 568 | if method == "CONNECT" then |
568 | path = assert(headers:get(":authority"), "missing authority") | |
569 | target = assert(headers:get(":authority"), "missing authority") | |
569 | 570 | assert(not headers:has(":path"), "CONNECT requests should not have a path") |
570 | 571 | else |
571 | 572 | -- RFC 7230 Section 5.4: A client MUST send a Host header field in all HTTP/1.1 request messages. |
572 | 573 | assert(self.connection.version < 1.1 or headers:has(":authority"), "missing authority") |
573 | path = assert(headers:get(":path"), "missing path") | |
574 | target = assert(headers:get(":path"), "missing path") | |
574 | 575 | end |
575 | 576 | if self.connection.req_locked then |
576 | 577 | -- Wait until previous request has been fully written |
584 | 585 | self.connection.pipeline:push(self) |
585 | 586 | self.connection.req_locked = self |
586 | 587 | -- write request line |
587 | local ok, err, errno = self.connection:write_request_line(method, path, self.connection.version, 0) | |
588 | local ok, err, errno = self.connection:write_request_line(method, target, self.connection.version, 0) | |
588 | 589 | if not ok then |
589 | 590 | return nil, err, errno |
590 | 591 | end |
689 | 689 | end |
690 | 690 | if stream.state == "idle" then |
691 | 691 | return nil, h2_errors.PROTOCOL_ERROR:new_traceback("'RST_STREAM' frames MUST NOT be sent for a stream in the 'idle' state"), ce.EILSEQ |
692 | elseif stream.state == "closed" then | |
693 | -- probably a delayed RST_STREAM, ignore | |
694 | return true | |
692 | 695 | end |
693 | 696 | |
694 | 697 | local err_code = sunpack(">I4", payload) |
1382 | 1385 | if max_available < (#payload - sent) then |
1383 | 1386 | if max_available > 0 then |
1384 | 1387 | -- send partial payload |
1385 | local ok, err, errno = self:write_data_frame(payload:sub(sent+1, sent+max_available), false, timeout) | |
1388 | local ok, err, errno = self:write_data_frame(payload:sub(sent+1, sent+max_available), false, false, timeout) | |
1386 | 1389 | if not ok then |
1387 | 1390 | return nil, err, errno |
1388 | 1391 | end |
2 | 2 | |
3 | 3 | Design criteria: |
4 | 4 | - the same header field is allowed more than once |
5 | - must be able to fetch seperate occurences (important for some headers e.g. Set-Cookie) | |
6 | - optionally available as comma seperated list | |
5 | - must be able to fetch separate occurences (important for some headers e.g. Set-Cookie) | |
6 | - optionally available as comma separated list | |
7 | 7 | - http2 adds flag to headers that they should never be indexed |
8 | 8 | - header order should be recoverable |
9 | 9 |
56 | 56 | end |
57 | 57 | end |
58 | 58 | end |
59 | if scheme == "http" or scheme == "ws" then | |
59 | if scheme == "http" then | |
60 | 60 | if self.http_proxy then |
61 | 61 | return self.http_proxy |
62 | 62 | end |
63 | elseif scheme == "https" or scheme == "wss" then | |
63 | elseif scheme == "https" then | |
64 | 64 | if self.https_proxy then |
65 | 65 | return self.https_proxy |
66 | 66 | end |
76 | 76 | path = path .. "?" .. uri_t.query |
77 | 77 | end |
78 | 78 | headers:upsert(":path", path) |
79 | if scheme == "wss" then | |
80 | scheme = "https" | |
81 | elseif scheme == "ws" then | |
82 | scheme = "http" | |
83 | end | |
79 | 84 | headers:upsert(":scheme", scheme) |
80 | 85 | end |
81 | 86 | if uri_t.userinfo then |
94 | 99 | return setmetatable({ |
95 | 100 | host = host; |
96 | 101 | port = port; |
97 | tls = (scheme == "https" or scheme == "wss"); | |
102 | tls = (scheme == "https"); | |
98 | 103 | headers = headers; |
99 | 104 | body = nil; |
100 | 105 | }, request_mt) |
208 | 213 | if not is_connect then |
209 | 214 | new_req.headers:upsert(":scheme", new_scheme) |
210 | 215 | end |
211 | if new_scheme == "https" or new_scheme == "wss" then | |
216 | if new_scheme == "https" then | |
212 | 217 | new_req.tls = true |
213 | elseif new_scheme == "http" or new_scheme == "ws" then | |
218 | elseif new_scheme == "http" then | |
214 | 219 | new_req.tls = false |
215 | 220 | else |
216 | 221 | return nil, "unknown scheme", ce.EINVAL |
351 | 356 | local host = self.host |
352 | 357 | local port = self.port |
353 | 358 | local tls = self.tls |
359 | local version = self.version | |
354 | 360 | |
355 | 361 | -- RFC 6797 Section 8.3 |
356 | 362 | if not tls and self.hsts and self.hsts:check(host) then |
438 | 444 | tls = tls; |
439 | 445 | ctx = self.ctx; |
440 | 446 | sendname = self.sendname; |
441 | version = self.version; | |
447 | version = version; | |
442 | 448 | h2_settings = default_h2_settings; |
443 | 449 | }, deadline and deadline-monotime()) |
444 | 450 | if connection == nil then |
474 | 480 | tls = tls; |
475 | 481 | ctx = self.ctx; |
476 | 482 | sendname = self.sendname ~= nil and self.sendname or host; |
477 | version = self.version; | |
483 | version = version; | |
478 | 484 | h2_settings = default_h2_settings; |
479 | 485 | }, deadline and deadline-monotime()) |
480 | 486 | if connection == nil then |
495 | 501 | tls = tls; |
496 | 502 | ctx = self.ctx; |
497 | 503 | sendname = self.sendname; |
498 | version = self.version; | |
504 | version = version; | |
499 | 505 | h2_settings = default_h2_settings; |
500 | 506 | }, deadline and deadline-monotime()) |
501 | 507 | if connection == nil then |
754 | 754 | local ctx = openssl_ctx.new("TLS", false) |
755 | 755 | ctx:setCipherList(intermediate_cipher_list) |
756 | 756 | ctx:setOptions(default_tls_options) |
757 | ctx:setEphemeralKey(openssl_pkey.new{ type = "EC", curve = "prime256v1" }) | |
757 | if ctx.setGroups then | |
758 | ctx:setGroups("P-521:P-384:P-256") | |
759 | else | |
760 | ctx:setEphemeralKey(openssl_pkey.new{ type = "EC", curve = "prime256v1" }) | |
761 | end | |
758 | 762 | local store = ctx:getStore() |
759 | 763 | store:addDefaults() |
760 | 764 | ctx:setVerify(openssl_ctx.VERIFY_PEER) |
765 | 769 | local ctx = openssl_ctx.new("TLS", true) |
766 | 770 | ctx:setCipherList(intermediate_cipher_list) |
767 | 771 | ctx:setOptions(default_tls_options) |
768 | ctx:setEphemeralKey(openssl_pkey.new{ type = "EC", curve = "prime256v1" }) | |
772 | if ctx.setGroups then | |
773 | ctx:setGroups("P-521:P-384:P-256") | |
774 | else | |
775 | ctx:setEphemeralKey(openssl_pkey.new{ type = "EC", curve = "prime256v1" }) | |
776 | end | |
769 | 777 | return ctx |
770 | 778 | end |
771 | 779 |
544 | 544 | |
545 | 545 | local function new_from_uri(uri, protocols) |
546 | 546 | local request = http_request.new_from_uri(uri) |
547 | local scheme = request.headers:get(":scheme") | |
548 | assert(scheme == "ws" or scheme == "wss", "scheme not websocket") | |
549 | 547 | local self = new("client") |
550 | 548 | self.request = request |
551 | 549 | self.request.version = 1.1 |
0 | package = "http" | |
1 | version = "0.3-0" | |
2 | ||
3 | description = { | |
4 | summary = "HTTP library for Lua"; | |
5 | homepage = "https://github.com/daurnimator/lua-http"; | |
6 | license = "MIT"; | |
7 | } | |
8 | ||
9 | source = { | |
10 | url = "https://github.com/daurnimator/lua-http/archive/v0.3.zip"; | |
11 | dir = "lua-http-0.3"; | |
12 | } | |
13 | ||
14 | dependencies = { | |
15 | "lua >= 5.1"; | |
16 | "compat53 >= 0.3"; -- Only if lua < 5.3 | |
17 | "bit32"; -- Only if lua == 5.1 | |
18 | "cqueues >= 20161214"; | |
19 | "luaossl >= 20161208"; | |
20 | "basexx >= 0.2.0"; | |
21 | "lpeg"; | |
22 | "lpeg_patterns >= 0.5"; | |
23 | "binaryheap >= 0.3"; | |
24 | "fifo"; | |
25 | -- "psl"; -- Optional | |
26 | } | |
27 | ||
28 | build = { | |
29 | type = "builtin"; | |
30 | modules = { | |
31 | ["http.bit"] = "http/bit.lua"; | |
32 | ["http.client"] = "http/client.lua"; | |
33 | ["http.connection_common"] = "http/connection_common.lua"; | |
34 | ["http.cookie"] = "http/cookie.lua"; | |
35 | ["http.h1_connection"] = "http/h1_connection.lua"; | |
36 | ["http.h1_reason_phrases"] = "http/h1_reason_phrases.lua"; | |
37 | ["http.h1_stream"] = "http/h1_stream.lua"; | |
38 | ["http.h2_connection"] = "http/h2_connection.lua"; | |
39 | ["http.h2_error"] = "http/h2_error.lua"; | |
40 | ["http.h2_stream"] = "http/h2_stream.lua"; | |
41 | ["http.headers"] = "http/headers.lua"; | |
42 | ["http.hpack"] = "http/hpack.lua"; | |
43 | ["http.hsts"] = "http/hsts.lua"; | |
44 | ["http.proxies"] = "http/proxies.lua"; | |
45 | ["http.request"] = "http/request.lua"; | |
46 | ["http.server"] = "http/server.lua"; | |
47 | ["http.socks"] = "http/socks.lua"; | |
48 | ["http.stream_common"] = "http/stream_common.lua"; | |
49 | ["http.tls"] = "http/tls.lua"; | |
50 | ["http.util"] = "http/util.lua"; | |
51 | ["http.version"] = "http/version.lua"; | |
52 | ["http.websocket"] = "http/websocket.lua"; | |
53 | ["http.zlib"] = "http/zlib.lua"; | |
54 | ["http.compat.prosody"] = "http/compat/prosody.lua"; | |
55 | ["http.compat.socket"] = "http/compat/socket.lua"; | |
56 | }; | |
57 | } |
0 | package = "http" | |
1 | version = "0.4-0" | |
2 | ||
3 | description = { | |
4 | summary = "HTTP library for Lua"; | |
5 | homepage = "https://github.com/daurnimator/lua-http"; | |
6 | license = "MIT"; | |
7 | } | |
8 | ||
9 | source = { | |
10 | url = "https://github.com/daurnimator/lua-http/archive/v0.4.zip"; | |
11 | dir = "lua-http-0.4"; | |
12 | } | |
13 | ||
14 | dependencies = { | |
15 | "lua >= 5.1"; | |
16 | "compat53 >= 0.3"; -- Only if lua < 5.3 | |
17 | "bit32"; -- Only if lua == 5.1 | |
18 | "cqueues >= 20161214"; | |
19 | "luaossl >= 20161208"; | |
20 | "basexx >= 0.2.0"; | |
21 | "lpeg"; | |
22 | "lpeg_patterns >= 0.5"; | |
23 | "binaryheap >= 0.3"; | |
24 | "fifo"; | |
25 | -- "psl"; -- Optional | |
26 | } | |
27 | ||
28 | build = { | |
29 | type = "builtin"; | |
30 | modules = { | |
31 | ["http.bit"] = "http/bit.lua"; | |
32 | ["http.client"] = "http/client.lua"; | |
33 | ["http.connection_common"] = "http/connection_common.lua"; | |
34 | ["http.cookie"] = "http/cookie.lua"; | |
35 | ["http.h1_connection"] = "http/h1_connection.lua"; | |
36 | ["http.h1_reason_phrases"] = "http/h1_reason_phrases.lua"; | |
37 | ["http.h1_stream"] = "http/h1_stream.lua"; | |
38 | ["http.h2_connection"] = "http/h2_connection.lua"; | |
39 | ["http.h2_error"] = "http/h2_error.lua"; | |
40 | ["http.h2_stream"] = "http/h2_stream.lua"; | |
41 | ["http.headers"] = "http/headers.lua"; | |
42 | ["http.hpack"] = "http/hpack.lua"; | |
43 | ["http.hsts"] = "http/hsts.lua"; | |
44 | ["http.proxies"] = "http/proxies.lua"; | |
45 | ["http.request"] = "http/request.lua"; | |
46 | ["http.server"] = "http/server.lua"; | |
47 | ["http.socks"] = "http/socks.lua"; | |
48 | ["http.stream_common"] = "http/stream_common.lua"; | |
49 | ["http.tls"] = "http/tls.lua"; | |
50 | ["http.util"] = "http/util.lua"; | |
51 | ["http.version"] = "http/version.lua"; | |
52 | ["http.websocket"] = "http/websocket.lua"; | |
53 | ["http.zlib"] = "http/zlib.lua"; | |
54 | ["http.compat.prosody"] = "http/compat/prosody.lua"; | |
55 | ["http.compat.socket"] = "http/compat/socket.lua"; | |
56 | }; | |
57 | } |
29 | 29 | local stream = client:new_stream() |
30 | 30 | client:shutdown() |
31 | 31 | local headers = new_headers() |
32 | headers:append(":method", "GET") | |
33 | headers:append(":scheme", "http") | |
32 | 34 | headers:append(":authority", "myauthority") |
33 | headers:append(":method", "GET") | |
34 | 35 | headers:append(":path", "/a") |
35 | 36 | assert.same(ce.EPIPE, select(3, stream:write_headers(headers, true))) |
36 | 37 | client:close() |
42 | 43 | cq:wrap(function() |
43 | 44 | local stream = client:new_stream() |
44 | 45 | local req_headers = new_headers() |
46 | req_headers:append(":method", "GET") | |
47 | req_headers:append(":scheme", "http") | |
45 | 48 | req_headers:append(":authority", "myauthority") |
46 | req_headers:append(":method", "GET") | |
47 | 49 | req_headers:append(":path", "/a") |
48 | 50 | assert(stream:write_headers(req_headers, true)) |
49 | 51 | local res_headers = assert(stream:get_headers()) |
90 | 92 | local server, client = new_pair(1.1) |
91 | 93 | local stream = client:new_stream() |
92 | 94 | local headers = new_headers() |
95 | headers:append(":method", "GET") | |
96 | headers:append(":scheme", "http") | |
93 | 97 | headers:append(":authority", "myauthority") |
94 | headers:append(":method", "GET") | |
95 | 98 | headers:append(":path", "/a") |
96 | 99 | assert(stream:write_headers(headers, true)) |
97 | 100 | local cq = cqueues.new():wrap(function() |
108 | 111 | cq:wrap(function() |
109 | 112 | local stream = client:new_stream() |
110 | 113 | local req_headers = new_headers() |
114 | req_headers:append(":method", "GET") | |
115 | req_headers:append(":scheme", "http") | |
111 | 116 | req_headers:append(":authority", "myauthority") |
112 | req_headers:append(":method", "GET") | |
113 | 117 | req_headers:append(":path", "/a") |
114 | 118 | assert(stream:write_headers(req_headers, true)) |
115 | 119 | local res_headers = assert(stream:get_headers()) |
134 | 138 | cq:wrap(function() |
135 | 139 | local stream = client:new_stream() |
136 | 140 | local req_headers = new_headers() |
141 | req_headers:append(":method", "GET") | |
142 | req_headers:append(":scheme", "http") | |
137 | 143 | req_headers:append(":authority", "myauthority") |
138 | req_headers:append(":method", "GET") | |
139 | 144 | req_headers:append(":path", "/a") |
140 | 145 | assert(stream:write_headers(req_headers, true)) |
141 | 146 | assert(stream:get_headers()) |
165 | 170 | cq:wrap(function() |
166 | 171 | local stream = client:new_stream() |
167 | 172 | local headers = new_headers() |
173 | headers:append(":method", "GET") | |
174 | headers:append(":scheme", "http") | |
168 | 175 | headers:append(":authority", "myauthority") |
169 | headers:append(":method", "GET") | |
170 | 176 | headers:append(":path", "/a") |
171 | 177 | headers:append("transfer-encoding", "chunked") |
172 | 178 | assert(stream:write_headers(headers, false)) |
216 | 222 | cq:wrap(function() |
217 | 223 | local stream = client:new_stream() |
218 | 224 | local headers = new_headers() |
225 | headers:append(":method", "GET") | |
226 | headers:append(":scheme", "http") | |
219 | 227 | headers:append(":authority", "myauthority") |
220 | headers:append(":method", "GET") | |
221 | 228 | headers:append(":path", "/a") |
222 | 229 | headers:append("transfer-encoding", "chunked") |
223 | 230 | assert(stream:write_headers(headers, false)) |
247 | 254 | do |
248 | 255 | local stream = client:new_stream() |
249 | 256 | local headers = new_headers() |
257 | headers:append(":method", "GET") | |
258 | headers:append(":scheme", "http") | |
250 | 259 | headers:append(":authority", "myauthority") |
251 | headers:append(":method", "GET") | |
252 | 260 | headers:append(":path", "/a") |
253 | 261 | headers:append("content-length", "100") |
254 | 262 | assert(stream:write_headers(headers, false)) |
257 | 265 | do |
258 | 266 | local stream = client:new_stream() |
259 | 267 | local headers = new_headers() |
268 | headers:append(":method", "GET") | |
269 | headers:append(":scheme", "http") | |
260 | 270 | headers:append(":authority", "myauthority") |
261 | headers:append(":method", "GET") | |
262 | 271 | headers:append(":path", "/b") |
263 | 272 | headers:append("content-length", "0") |
264 | 273 | assert(stream:write_headers(headers, true)) |
312 | 321 | if client_sync then client_sync:wait() end |
313 | 322 | local a = client:new_stream() |
314 | 323 | local ah = new_headers() |
324 | ah:append(":method", "GET") | |
325 | ah:append(":scheme", "http") | |
315 | 326 | ah:append(":authority", "myauthority") |
316 | ah:append(":method", "GET") | |
317 | 327 | ah:append(":path", "/a") |
318 | 328 | assert(a:write_headers(ah, true)) |
319 | 329 | end) |
321 | 331 | client_sync:signal(); client_sync = nil; |
322 | 332 | local b = client:new_stream() |
323 | 333 | local bh = new_headers() |
334 | bh:append(":method", "POST") | |
335 | bh:append(":scheme", "http") | |
324 | 336 | bh:append(":authority", "myauthority") |
325 | bh:append(":method", "POST") | |
326 | 337 | bh:append(":path", "/b") |
327 | 338 | assert(b:write_headers(bh, false)) |
328 | 339 | cqueues.sleep(0.01) |
331 | 342 | cq:wrap(function() |
332 | 343 | local c = client:new_stream() |
333 | 344 | local ch = new_headers() |
345 | ch:append(":method", "GET") | |
346 | ch:append(":scheme", "http") | |
334 | 347 | ch:append(":authority", "myauthority") |
335 | ch:append(":method", "GET") | |
336 | 348 | ch:append(":path", "/c") |
337 | 349 | assert(c:write_headers(ch, true)) |
338 | 350 | end) |
374 | 386 | |
375 | 387 | do |
376 | 388 | local h = new_headers() |
389 | h:append(":method", "POST") | |
390 | h:append(":scheme", "http") | |
377 | 391 | h:append(":authority", "myauthority") |
378 | h:append(":method", "POST") | |
379 | 392 | h:append(":path", "/") |
380 | 393 | h:upsert("id", "a") |
381 | 394 | assert(a:write_headers(h, false)) |
442 | 455 | cq:wrap(function() |
443 | 456 | local a = client:new_stream() |
444 | 457 | local h = new_headers() |
458 | h:append(":method", "POST") | |
459 | h:append(":scheme", "http") | |
445 | 460 | h:append(":authority", "myauthority") |
446 | h:append(":method", "POST") | |
447 | 461 | h:append(":path", "/a") |
448 | 462 | h:append("expect", "100-continue") |
449 | 463 | assert(a:write_headers(h, false)) |
475 | 489 | cq:wrap(function() |
476 | 490 | local a = client:new_stream() |
477 | 491 | local h = new_headers() |
492 | h:append(":method", "GET") | |
493 | h:append(":scheme", "http") | |
478 | 494 | h:append(":authority", "myauthority") |
479 | h:append(":method", "GET") | |
480 | 495 | h:append(":path", "/") |
481 | 496 | assert(a:write_headers(h, true)) |
482 | 497 | end) |
123 | 123 | assert_loop(cq, TEST_TIMEOUT) |
124 | 124 | assert.truthy(cq:empty()) |
125 | 125 | end) |
126 | it("ignores delayed RST_STREAM on already closed stream", function() | |
127 | local s, c = new_pair() | |
128 | local cq = cqueues.new() | |
129 | cq:wrap(function() | |
130 | local client_stream = c:new_stream() | |
131 | local req_headers = new_headers() | |
132 | req_headers:append(":method", "GET") | |
133 | req_headers:append(":scheme", "http") | |
134 | req_headers:append(":path", "/") | |
135 | req_headers:append(":authority", "example.com") | |
136 | assert(client_stream:write_headers(req_headers, true)) | |
137 | assert(client_stream:get_headers()) | |
138 | assert("closed", client_stream.state) | |
139 | -- both sides now have stream in closed state | |
140 | -- send server a RST_STREAM: it should get ignored | |
141 | assert(client_stream:rst_stream("post-closed rst_stream")) | |
142 | assert(c:close()) | |
143 | end) | |
144 | cq:wrap(function() | |
145 | local stream = assert(s:get_next_incoming_stream()) | |
146 | assert(stream:get_headers()) | |
147 | local res_headers = new_headers() | |
148 | res_headers:append(":status", "200") | |
149 | assert(stream:write_headers(res_headers, true)) | |
150 | -- both sides now have stream in closed state | |
151 | assert("closed", stream.state) | |
152 | -- process incoming frames until EOF (i.e. drain RST_STREAM) | |
153 | -- the RST_STREAM frame should be ignored. | |
154 | assert(s:loop()) | |
155 | assert(s:close()) | |
156 | end) | |
157 | cq:wrap(function() | |
158 | assert(s:loop()) | |
159 | end) | |
160 | assert_loop(cq, TEST_TIMEOUT) | |
161 | assert.truthy(cq:empty()) | |
162 | end) | |
126 | 163 | end) |
127 | 164 | describe("push_promise", function() |
128 | 165 | it("permits a simple push promise from server => client", function() |
267 | 267 | })) |
268 | 268 | local headers = http_headers.new() |
269 | 269 | headers:append(":method", "GET") |
270 | headers:append(":scheme", "http") | |
270 | 271 | headers:append(":path", "/") |
271 | 272 | headers:append(":authority", "foo") |
272 | 273 | -- Normal request |
11 | 11 | end |
12 | 12 | local function new_request_headers() |
13 | 13 | local headers = new_headers() |
14 | headers:append(":method", "GET") | |
15 | headers:append(":scheme", "http") | |
14 | 16 | headers:append(":authority", "myauthority") |
15 | headers:append(":method", "GET") | |
16 | 17 | headers:append(":path", "/") |
17 | 18 | return headers |
18 | 19 | end |
91 | 91 | end |
92 | 92 | local correct_headers = http_headers.new() |
93 | 93 | correct_headers:append(":method", "GET") |
94 | correct_headers:append(":scheme", "http") | |
94 | 95 | correct_headers:append(":authority", "example.com") |
95 | 96 | correct_headers:append(":path", "/") |
96 | 97 | correct_headers:append("upgrade", "websocket") |
374 | 375 | port = 0; |
375 | 376 | onstream = function(s, stream) |
376 | 377 | local headers = assert(stream:get_headers()) |
378 | assert.same("http", headers:get(":scheme")) | |
377 | 379 | local ws = websocket.new_from_stream(stream, headers) |
378 | 380 | assert(ws:accept()) |
379 | 381 | assert(ws:close()) |