Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
fe4b189
http/server: Optionally use path instead of host/port combination
RyanSquared Feb 10, 2016
067a641
spec/server|http/client: UNIX domain sockets for client, and tests
RyanSquared Feb 10, 2016
1c8612c
LICENSE: Update copyright year
daurnimator Feb 11, 2016
d92207a
doc/index.md: Use plural
daurnimator Feb 12, 2016
f931c2a
http/server: Keep a condition variable to be alerted of shutdown.
daurnimator Feb 12, 2016
18cac34
doc/index: Document current http.server methods
daurnimator Feb 12, 2016
0e959e4
README: Add luarocks install instuctions
daurnimator Feb 12, 2016
1311c35
spec/request_spec: Put handle_redirect tests into separate tests
daurnimator Feb 15, 2016
fe62e33
http/request: Add :clone method
daurnimator Feb 15, 2016
9d0892b
http/request: Implement :handle_redirect by :clone-ing then changing …
daurnimator Feb 15, 2016
c89b285
spec/util_spec: Add tests for split_authority
daurnimator Feb 15, 2016
01225a8
spec/util_spec: Add tests for to_authority
daurnimator Feb 15, 2016
e42b15c
spec/util_spec: Add test for decodeURI
daurnimator Feb 15, 2016
c6422f6
README: Remove TODO section, add Features, update Status
daurnimator Feb 15, 2016
823a7ce
http/h2_{connection,stream}: Don't rely on compat53 when in lua 5.3
daurnimator Feb 15, 2016
a73acef
http/h1_stream: On close_when_done, remove self from pipeline *before…
daurnimator Feb 15, 2016
b62814d
http/h1_connection: get_next_incoming_stream now blocks until there i…
daurnimator Feb 16, 2016
3083c2a
.travis.yml: Run luacheck
daurnimator Feb 17, 2016
415341d
.travis.yml: Install hererocks out of tree (fixes luacheck failure)
daurnimator Feb 17, 2016
2a12379
http/request: Update port on scheme change
daurnimator Feb 17, 2016
f7475d0
.travis.yml: Add test for lua 5.3 without compat53 installed
daurnimator Feb 17, 2016
08b14e7
http/h1_stream: Don't allow server to send headers while stream is idle
daurnimator Feb 17, 2016
bc20d50
spec/compat_prosody_spec: Add full end-to-end test
daurnimator Feb 17, 2016
ff04668
.luacov: Add compat source subdir
daurnimator Feb 17, 2016
9ee6ead
spec/compat_prosody_spec: Add POST test
daurnimator Feb 17, 2016
0caa077
http/compat/prosody: Remove trailing semicolons
daurnimator Feb 17, 2016
972f656
spec/compat_prosody_spec: Add duplicate headers to POST test
daurnimator Feb 17, 2016
12c8c20
examples/h2_streaming: Commit http2 streaming example
daurnimator Feb 28, 2016
78914f1
.gitignore: Actually ignore luacov files
daurnimator Feb 29, 2016
d601307
http/tls: Remove duplicated cipher
daurnimator Feb 29, 2016
96b8ae6
http/h1_stream: Calculate stats_sent based on uncompressed size
daurnimator Mar 1, 2016
9852189
http/cilent.lua: Fix merge with master
Mar 24, 2016
85aef6f
spec/server_spec: Remove paths via finally()
Mar 24, 2016
80097bc
http/server|spec/server_spec: add (pending) generation for contexts w…
Mar 24, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
./luacov.report.out
/luacov.report.out
/luacov.stats.out
1 change: 1 addition & 0 deletions .luacov
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ return {
deletestats = true;
include = {
"/http/[^/]+$";
"/http/compat/[^/]+$";
};
exclude = {
};
Expand Down
8 changes: 6 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ env:
- LUA="lua 5.2" ZLIB=lua-zlib
- LUA="lua 5.3"
- LUA="lua 5.3" ZLIB=lzlib
- LUA="lua 5.3" COMPAT53=no
# lua-zlib is currently unavailable for lua5.3 https://github.com/brimworks/lua-zlib/issues/28
- LUA="luajit @"
- LUA="luajit @" ZLIB=lzlib
Expand All @@ -29,20 +30,23 @@ branches:

before_install:
- pip install hererocks
- hererocks here -r^ --$LUA # Install latest LuaRocks version
- hererocks ~/hererocks -r^ --$LUA # Install latest LuaRocks version
# plus the Lua version for this build job
# into 'here' subdirectory
- export PATH=$PATH:$PWD/here/bin # Add directory with all installed binaries to PATH
- export PATH=$PATH:~/hererocks/bin # Add directory with all installed binaries to PATH
- eval `luarocks path --bin`
- luarocks install luacheck
- luarocks install luacov-coveralls
- luarocks install busted

install:
- luarocks install --only-deps http-scm-0.rockspec
- if [ "$ZLIB" = "lzlib" ]; then luarocks install lzlib; fi
- if [ "$ZLIB" = "lua-zlib" ]; then luarocks install lua-zlib; fi
- if [ "$COMPAT53" = "no" ]; then luarocks remove compat53; fi

script:
- luacheck .
- busted -c

after_success:
Expand Down
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2015 Daurnimator
Copyright (c) 2015-2016 Daurnimator

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
28 changes: 12 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
## Features

- Optionally asynchronous (including DNS lookups and SSL)
- Supports HTTP version 1.0, 1.1 and 2
- Functionality for both client and server
- Websockets
- Compatible with Lua 5.1, 5.2, 5.3 and [LuaJIT](http://luajit.org/)


Expand All @@ -11,29 +14,22 @@
Can be found at [https://daurnimator.github.io/lua-http/](https://daurnimator.github.io/lua-http/)


# Status

This project is a work in progress and not ready for production use.
## Status

[![Build Status](https://travis-ci.org/daurnimator/lua-http.svg)](https://travis-ci.org/daurnimator/lua-http)
[![Coverage Status](https://coveralls.io/repos/daurnimator/lua-http/badge.svg?branch=master&service=github)](https://coveralls.io/github/daurnimator/lua-http?branch=master)

## Todo

- [x] HTTP 1.1
- [x] [HTTP 2](https://http2.github.io/http2-spec/)
- [x] [HPACK](https://http2.github.io/http2-spec/compression.html)
- [ ] Connection pooling
- [ ] [`socket.http`](http://w3.impa.br/~diego/software/luasocket/http.html) compatibility layer
- [x] Prosody [`net.http`](https://prosody.im/doc/developers/net/http) compatibility layer
- [x] Handle redirects
- [ ] Be able to use an HTTP proxy
- [x] Compression (e.g. gzip)
- [ ] Websockets
- HTTP client API is reaching stability
- The HTTP server API is still changing


# Installation

It's recommended to install lua-http by using [luarocks](https://luarocks.org/).
This will automatically install run-time lua dependencies for you.

$ luarocks install --server=http://luarocks.org/dev http

## Dependencies

- [cqueues](http://25thandclement.com/~william/projects/cqueues.html) >= 20150907
Expand All @@ -42,7 +38,7 @@ This project is a work in progress and not ready for production use.
- [lpeg_patterns](https://github.com/daurnimator/lpeg_patterns) >= 0.2
- [fifo](https://github.com/daurnimator/fifo.lua)

If you want to use gzip compression you will need **one** of:
To use gzip compression you need **one** of:

- [lzlib](https://github.com/LuaDist/lzlib) or [lua-zlib](https://github.com/brimworks/lua-zlib)

Expand Down
34 changes: 31 additions & 3 deletions doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -664,13 +664,13 @@ Set to `math.huge` to not give up.

### `request.post301` <!-- --> {#http.request.post301}

Respect RFC 2616 Section 10.3.2 and **don't** convert POST requests into body-less GET requests when following a 301 redirect. The non-RFC behaviour is ubiquitous in web browsers and assumed by server. Modern HTTP endpoints send status code 308 to indicate that they don't want the method to be changed.
Respect RFC 2616 Section 10.3.2 and **don't** convert POST requests into body-less GET requests when following a 301 redirect. The non-RFC behaviour is ubiquitous in web browsers and assumed by servers. Modern HTTP endpoints send status code 308 to indicate that they don't want the method to be changed.
Defaults to `false`.


### `request.post302` <!-- --> {#http.request.post302}

Respect RFC 2616 Section 10.3.3 and **don't** convert POST requests into body-less GET requests when following a 302 redirect. The non-RFC behaviour is ubiquitous in web browsers and assumed by server. Modern HTTP endpoints send status code 307 to indicate that they don't want the method to be changed.
Respect RFC 2616 Section 10.3.3 and **don't** convert POST requests into body-less GET requests when following a 302 redirect. The non-RFC behaviour is ubiquitous in web browsers and assumed by servers. Modern HTTP endpoints send status code 307 to indicate that they don't want the method to be changed.
Defaults to `false`.


Expand All @@ -683,6 +683,15 @@ Allows setting a request body. `body` may be a string, function or lua file obje
- If `body` is a lua file object, it will be [`:seek`'d](http://www.lua.org/manual/5.3/manual.html#pdf-file:seek) to the start, then sent as a body. Any errors encountered during file operations **will be thrown**.


### `request:clone()` <!-- --> {#http.request:clone}

Creates and returns a clone of the request.

The clone has its own deep copy of the [`.headers`](#http.request.headers) field.

The [`.tls`](#http.request.tls) and body fields are shallow copied from the original request.


### `request:go(timeout)` <!-- --> {#http.request:timeout}

Performs the request.
Expand All @@ -693,7 +702,26 @@ On success, returns the response [*headers*](#http.headers) and a [*stream*](#st

## http.server

### `listen(options)` <!-- --> {#http.client.connect}
This interface is **unstable**.

### `listen(options)` <!-- --> {#http.server.connect}


### `server:listen(timeout)` <!-- --> {#http.server:listen}


### `server:localname()` <!-- --> {#http.server:localname}


### `server:pause()` <!-- --> {#http.server:pause}

Cause [`server:run`](#http.server:run) to stop processing new clients and return.


### `server:close()` <!-- --> {#http.server:close}


### `server:run(on_stream, cq)` <!-- --> {#http.server:run}


## http.stream_common
Expand Down
14 changes: 14 additions & 0 deletions examples/h2_streaming.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--[[
Makes a request to an HTTP2 endpoint that has an infinite length response.

Usage: lua examples/h2_streaming.lua
]]

local request = require "http.request"

-- This endpoint returns a never-ending stream of chunks containing the current time
local req = request.new_from_uri("https://http2.golang.org/clockstream")
local _, stream = assert(req:go())
for chunk in stream:each_chunk() do
io.write(chunk)
end
33 changes: 18 additions & 15 deletions http/client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,8 @@ local function onerror(socket, op, why, lvl) -- luacheck: ignore 212
return string.format("%s: %s", op, ce.strerror(why)), why
end

local function connect(options, timeout)
local function negotiate(s, options, timeout)
local deadline = timeout and (monotime()+timeout)
local s do
local errno
s, errno = cs.connect {
family = options.family;
host = options.host;
port = options.port;
sendname = options.sendname;
v6only = options.v6only;
nodelay = true;
}
if s == nil then
return nil, ce.strerror(errno), errno
end
end
s:onerror(onerror)
local tls = options.tls
local version = options.version
Expand Down Expand Up @@ -88,6 +74,23 @@ local function connect(options, timeout)
end
end

local function connect(options, timeout)
-- TODO: https://github.com/wahern/cqueues/issues/124
local s, errno = cs.connect {
family = options.family;
host = options.host;
port = options.port;
path = options.path;
sendname = options.sendname;
v6only = options.v6only;
nodelay = true;
}
if s == nil then
return nil, ce.strerror(errno), errno
end
return negotiate(s, options, timeout)
end

return {
connect = connect;
}
6 changes: 3 additions & 3 deletions http/compat/prosody.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ local function new_prosody(url, ex, callback)
local cq = assert(cqueues.running(), "must be running inside a cqueue")
local ok, req = pcall(new_from_uri, url)
if not ok then
callback(nil, 0, req);
return nil, "invalid-url";
callback(nil, 0, req)
return nil, "invalid-url"
end
req.follow_redirects = false -- prosody doesn't follow redirects
if ex then
Expand Down Expand Up @@ -80,7 +80,7 @@ local function new_prosody(url, ex, callback)
httpversion = stream.peer_version;
headers = headers_as_kv;
body = response_body;
};
}
callback(response_body, code, response, self)
end, req)
return req
Expand Down
29 changes: 16 additions & 13 deletions http/h1_connection.lua
Original file line number Diff line number Diff line change
Expand Up @@ -120,22 +120,25 @@ end
-- this function *should never throw*
function connection_methods:get_next_incoming_stream(timeout)
assert(self.type == "server")
local deadline = timeout and (monotime()+timeout)
-- Make sure we don't try and read before the previous request has been fully read
if self.req_locked then
repeat
-- Wait until previous requests have been fully read
if not self.req_cond:wait(timeout) then
return nil, ce.ETIMEDOUT
if self.req_locked then
if not self.req_cond:wait(deadline and deadline - monotime()) then
return nil, ce.ETIMEDOUT
end
assert(self.req_locked == nil)
end
assert(self.req_locked == nil)
end
if self.socket == nil or self.socket:eof("r") then
return nil, ce.EPIPE
end
-- check if socket has already got an error set
local errno = self.socket:error("r")
if errno then
return nil, onerror(self.socket, "read", errno, 3)
end
if self.socket == nil then
return nil, ce.EPIPE
end
-- Wait for at least one byte
local ok, err, errno = self.socket:fill(1, deadline and deadline-monotime())
if not ok then
return nil, err or ce.EPIPE, errno
end
until not self.req_locked
local stream = h1_stream.new(self)
self.pipeline:push(stream)
self.req_locked = stream
Expand Down
14 changes: 9 additions & 5 deletions http/h1_stream.lua
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ function stream_methods:set_state(new)
-- If we have just finished writing the response
if (old == "idle" or old == "open" or old == "half closed (remote)")
and (new == "half closed (local)" or new == "closed") then
-- remove ourselves from the write pipeline
assert(self.connection.pipeline:pop() == self)
if self.close_when_done then
self.connection:shutdown()
end
-- remove ourselves from the write pipeline
assert(self.connection.pipeline:pop() == self)
local next_stream = self.connection.pipeline:peek()
if next_stream then
next_stream.pipeline_cond:signal()
Expand All @@ -124,11 +124,11 @@ function stream_methods:set_state(new)
-- If we have just finished reading the response;
if (old == "idle" or old == "open" or old == "half closed (local)")
and (new == "half closed (remote)" or new == "closed") then
-- remove ourselves from the read pipeline
assert(self.connection.pipeline:pop() == self)
if self.close_when_done then
self.connection:shutdown()
end
-- remove ourselves from the read pipeline
assert(self.connection.pipeline:pop() == self)
local next_stream = self.connection.pipeline:peek()
if next_stream then
next_stream.pipeline_cond:signal()
Expand Down Expand Up @@ -370,6 +370,9 @@ function stream_methods:write_headers(headers, end_stream, timeout)
end
local status_code, method
if self.type == "server" then
if self.state == "idle" then
error("cannot write headers when stream is idle")
end
-- Make sure we're at the front of the pipeline
if self.connection.pipeline:peek() ~= self then
if not self.pipeline_cond:wait(deadline and (deadline-monotime)) then
Expand Down Expand Up @@ -711,6 +714,7 @@ function stream_methods:write_chunk(chunk, end_stream, timeout)
else
assert(self.connection.pipeline:peek() == self)
end
local orig_size = #chunk
if self.body_write_deflate then
chunk = self.body_write_deflate(chunk, end_stream)
end
Expand Down Expand Up @@ -756,7 +760,7 @@ function stream_methods:write_chunk(chunk, end_stream, timeout)
elseif self.body_write_type ~= "missing" then
error("unknown body writing method")
end
self.stats_sent = self.stats_sent + #chunk
self.stats_sent = self.stats_sent + orig_size
if end_stream then
if self.state == "half closed (remote)" then
self:set_state("closed")
Expand Down
6 changes: 5 additions & 1 deletion http/h2_connection.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ local h2_error = require "http.h2_error"
local h2_stream = require "http.h2_stream"
local hpack = require "http.hpack"
local h2_banned_ciphers = require "http.tls".banned_ciphers
local assert = require "compat53.module".assert
local spack = string.pack or require "compat53.string".pack
local sunpack = string.unpack or require "compat53.string".unpack

local assert = assert
if _VERSION:match("%d+%.?%d*") < "5.3" then
assert = require "compat53.module".assert
end

local function xor(a, b)
return (a and b) or not (a or b)
end
Expand Down
6 changes: 5 additions & 1 deletion http/h2_stream.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ local band = require "http.bit".band
local bor = require "http.bit".bor
local h2_errors = require "http.h2_error".errors
local stream_common = require "http.stream_common"
local assert = require "compat53.module".assert
local spack = string.pack or require "compat53.string".pack
local sunpack = string.unpack or require "compat53.string".unpack
local unpack = table.unpack or unpack -- luacheck: ignore 113

local assert = assert
if _VERSION:match("%d+%.?%d*") < "5.3" then
assert = require "compat53.module".assert
end

local function xor(a, b)
return (a and b) or not (a or b)
end
Expand Down
Loading