Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go: ['1.14', '1.15', '1.16', '1.17', '1.18']
go: ['1.14', '1.15', '1.16', '1.17', '1.18', '1.19']

services: {}
steps:
Expand Down
165 changes: 125 additions & 40 deletions http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Functions

- `client()` - returns http client instance for further usage. Avaliable options as table:

```
proxy="http(s)://<user>:<password>@host:<port>",
timeout= 10,
Expand All @@ -12,11 +13,22 @@ basic_auth_user = "",
basic_auth_password = "",
headers = {"key"="value"},
debug = false,

# When set, the client will present the client cert for "mTLS"
client_public_cert_pem_file = nil,
client_private_key_pem_file = nil,

# When set, this will be used to verify the server certificate (useful for private enterprise certificate authorities).
# Prefer this over insecure_ssl when possible
root_cas_pem_file = "",
```

- `request(method, url, [data])` - make request userdata.

## Methods

### client

- `do_request(request)` - returns result of request. Avaliable data are: 'body', 'headers', 'code'

## Examples
Expand All @@ -30,9 +42,15 @@ local client = http.client()
-- GET
local request = http.request("GET", "http://hostname.com")
local result, err = client:do_request(request)
if err then error(err) end
if not(result.code == 200) then error("code") end
if not(result.body == "xxx.xxx.xxx.xxx") then error("body") end
if err then
error(err)
end
if not (result.code == 200) then
error("code")
end
if not (result.body == "xxx.xxx.xxx.xxx") then
error("body")
end

-- auth basic
local request = http.request("GET", "http://hostname.com")
Expand All @@ -44,18 +62,18 @@ local request = http.request("POST", "http://hostname.com/api.json", "{}")
request:header_set("Content-Type", "application/json")

-- with proxy
local client = http.client({proxy="http(s)://login:password@hostname.com"})
local client = http.client({ proxy = "http(s)://login:password@hostname.com" })
local request = http.request("POST", "http://hostname.com/api.json", "{}")

-- ignore ssl
local client = http.client({insecure_ssl=true})
local client = http.client({ insecure_ssl = true })
local request = http.request("POST", "http://hostname.com/api.json", "{}")

-- set headers for all request
local client = http.client({ headers={key="value"} })
local client = http.client({ headers = { key = "value" } })

-- set basic auth for all request
local client = http.client({basic_auth_user="admin", basic_auth_password="123456"})
local client = http.client({ basic_auth_user = "admin", basic_auth_password = "123456" })
```

### Server
Expand All @@ -64,40 +82,44 @@ local client = http.client({basic_auth_user="admin", basic_auth_password="123456

```lua
local server, err = http.server("127.0.0.1:1113")
if err then error(err) end
if err then
error(err)
end

while true do
local request, response = server:accept() -- lock and wait request

-- print request
print("host:", request.host)
print("method:", request.method)
print("referer:", request.referer)
print("proto:", request.proto)
print("path:", request.path)
print("raw_path:", request.raw_path)
print("raw_query:", request.raw_query)
print("request_uri:", request.request_uri)
print("remote_addr:", request.remote_addr)
print("user_agent: "..request.user_agent)

-- get body
local body, err = request.body()
if err then error(err) end
print("body:", body)

for k, v in pairs(request.headers) do
print("header: ", k, v)
end
for k, v in pairs(request.query) do
print("query params: ", k, "=" ,v)
end
-- write response
response:code(200) -- write header
response:header("content-type", "application/json")
response:write(request.request_uri) -- write data
-- response:redirect("http://google.com")
response:done() -- end response
local request, response = server:accept() -- lock and wait request

-- print request
print("host:", request.host)
print("method:", request.method)
print("referer:", request.referer)
print("proto:", request.proto)
print("path:", request.path)
print("raw_path:", request.raw_path)
print("raw_query:", request.raw_query)
print("request_uri:", request.request_uri)
print("remote_addr:", request.remote_addr)
print("user_agent: " .. request.user_agent)

-- get body
local body, err = request.body()
if err then
error(err)
end
print("body:", body)

for k, v in pairs(request.headers) do
print("header: ", k, v)
end
for k, v in pairs(request.query) do
print("query params: ", k, "=", v)
end
-- write response
response:code(200) -- write header
response:header("content-type", "application/json")
response:write(request.request_uri) -- write data
-- response:redirect("http://google.com")
response:done() -- end response

end
```
Expand All @@ -106,7 +128,9 @@ end

```lua
local server, err = http.server("127.0.0.1:1113")
if err then error(err) end
if err then
error(err)
end

server:do_handle_string([[ -- do_handle_file

Expand All @@ -124,6 +148,67 @@ response:done()
]]
```

#### Handle variant (multithreaded as function)

```lua
local server, err = http.server("127.0.0.1:1113")
assert(not err, tostring(err))

server:do_handle_function(function(response, request)
response:code(200)
response:write("OK\n")
response:done()
end)
```

#### Listen to an open port and get the address (host:port)

```lua
local server, err = http.server {}
assert(not err, tostring(err))
local addr = server:addr()
```

#### TLS support

```lua
local server, err = http.server {
addr = "127.0.0.1:1113",

-- Setting both of these enables TLS
server_public_cert_pem_file = "test/data/test.cert.pem",
server_private_key_pem_file = "test/data/test.key.pem",
}
assert(not err, tostring(err))

server:do_handle_function(function(response, request)
response:code(200)
response:write("OK\n")
response:done()
end)
```

#### mTLS support (enforce client certs)

```lua
local server, err = http.server {
addr = "127.0.0.1:1113",
client_cas_pem_file = "test/data/test.cert.pem",
client_auth = "RequireAndVerifyClientCert", -- See https://pkg.go.dev/crypto/tls@go1.19.2#ClientAuthType

-- Setting both of these enables TLS
server_public_cert_pem_file = "test/data/test.cert.pem",
server_private_key_pem_file = "test/data/test.key.pem",
}
assert(not err, tostring(err))

server:do_handle_function(function(response, request)
response:code(200)
response:write("OK\n")
response:done()
end)
```

#### Serve Static files

```lua
Expand Down
54 changes: 54 additions & 0 deletions http/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ package http_test

import (
"crypto/subtle"
"crypto/tls"
"crypto/x509"
"fmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/vadv/gopher-lua-libs/tests"
"golang.org/x/sync/errgroup"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -218,3 +224,51 @@ func TestApi(t *testing.T) {
assert.NoError(t, state.DoFile("./test/test_serve_static.lua"))
})
}

func TestMTLSClient(t *testing.T) {
s := httptest.NewUnstartedServer(http.HandlerFunc(func(writer http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(writer, "OK\n")
}))
defer s.Close()
serverCert, err := tls.LoadX509KeyPair("test/data/test.cert.pem", "test/data/test.key.pem")
require.NoError(t, err)
caData, err := ioutil.ReadFile("test/data/test.cert.pem")
require.NoError(t, err)
cas := x509.NewCertPool()
cas.AppendCertsFromPEM(caData)
s.TLS = &tls.Config{
Certificates: []tls.Certificate{serverCert},
ClientCAs: cas,
ClientAuth: tls.RequireAndVerifyClientCert,
}
s.StartTLS()

preload := tests.SeveralPreloadFuncs(
lua_http.Preload,
func(L *lua.LState) {
// Pass the httptest server URL as a global, so it can be used for queries.
L.SetGlobal("tURL", lua.LString(s.URL))
},
)
assert.NotZero(t, tests.RunLuaTestFile(t, preload, "test/test_mtls_client.lua"))
}

func TestMTLSServerWithClient(t *testing.T) {
preload := tests.SeveralPreloadFuncs(
lua_http.Preload,
lua_time.Preload,
inspect.Preload,
plugin.Preload,
)
assert.NotZero(t, tests.RunLuaTestFile(t, preload, "test/test_mtls_server_with_client.lua"))
}

func TestServer(t *testing.T) {
preload := tests.SeveralPreloadFuncs(
lua_http.Preload,
lua_time.Preload,
inspect.Preload,
plugin.Preload,
)
assert.NotZero(t, tests.RunLuaTestFile(t, preload, "test/test_server.lua"))
}
Loading