https://github.com/negasus/traefik2-luascript
LuaScript middleware for Traefik v2
https://github.com/negasus/traefik2-luascript
lua middleware traefik
Last synced: 15 days ago
JSON representation
LuaScript middleware for Traefik v2
- Host: GitHub
- URL: https://github.com/negasus/traefik2-luascript
- Owner: negasus
- Created: 2019-04-02T21:57:33.000Z (almost 7 years ago)
- Default Branch: master
- Last Pushed: 2021-11-30T09:42:48.000Z (over 4 years ago)
- Last Synced: 2024-06-19T00:20:23.785Z (almost 2 years ago)
- Topics: lua, middleware, traefik
- Language: Go
- Size: 43 KB
- Stars: 64
- Watchers: 6
- Forks: 6
- Open Issues: 2
-
Metadata Files:
- Readme: readme.md
Awesome Lists containing this project
README
# Traefik2 LuaScript
**LuaScript** is middleware for [Traefik v2](https://github.com/containous/traefik) for execute lua script with access to API
Under cover used LUA VM from [Yusuke Inuzuka](https://github.com/yuin/gopher-lua)
An [issue](https://github.com/containous/traefik/issues/1336#issuecomment-478517290)
Post on [the community portal](https://community.containo.us/t/custom-middleware-for-traefik2-lua-script/5463)
## About
This middleware allows you to write your business logic in LUA script
- get an incoming request
- get/set request body
- add/modify request headers
- add/modify response headers
- interrupt the request
- make HTTP calls to foreign services
- write to traefik log
- encode/decode json
## Usage example
```lua
-- middleware_example.lua
local traefik = require('traefik')
local log = require('log')
local h, err = traefik.getRequestHeader('X-Some-Header')
if err ~= nil then
log.warn('error get header ' .. err)
return
end
if h == '' then
traefik.interrupt(401, 'HTTP Header empty or not exists')
return
end
traefik.setRequestHeader('Authorized', 'SUCCESS')
log.info('continue')
```
Functions may return an error as a last variable.
It is a string with an error message or `nil`, if no error
## Benchmark
> See into `benchmark` folder in this repo
Backend is a simple go application
```go
package main
import (
"log"
"net/http"
)
var ok = []byte("ok")
func handler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Write(ok)
}
func main() {
http.HandleFunc("/", handler)
log.Printf("listen 2000")
http.ListenAndServe("127.0.0.1:2000", nil)
}
```
Run load testing with [vegeta](https://github.com/tsenart/vegeta)
```bash
echo "GET http://localhost/" | vegeta attack -rate 2000 -duration=60s | tee results.bin | vegeta report
```
**With LUA**
A Traefik config
```yaml
http:
routers:
router1:
rule: "Host(`localhost`)"
service: service1
middlewares:
- example
middlewares:
example:
luascript:
script: middleware.lua
services:
service1:
loadBalancer:
servers:
- url: "http://127.0.0.1:2000"
```
A Lua script
```lua
local traefik = require('traefik')
traefik.setRequestHeader('X-Header', 'Example')
traefik.setResponseHeader('X-Header', 'Example')
```
A Result
```
Requests [total, rate, throughput] 120000, 2000.02, 2000.00
Duration [total, attack, wait] 59.999868062s, 59.999484357s, 383.705µs
Latencies [mean, 50, 95, 99, max] 471.058µs, 365.053µs, 993.75µs, 1.26782ms, 18.771475ms
Bytes In [total, mean] 240000, 2.00
Bytes Out [total, mean] 0, 0.00
Success [ratio] 100.00%
Status Codes [code:count] 200:120000
Error Set:
```
**Without LUA**
A Traefik config
```yaml
http:
routers:
router1:
rule: "Host(`localhost`)"
service: service1
services:
service1:
loadBalancer:
servers:
- url: "http://127.0.0.1:2000"
```
A Result
```
Requests [total, rate, throughput] 120000, 2000.02, 2000.01
Duration [total, attack, wait] 59.999708481s, 59.999466875s, 241.606µs
Latencies [mean, 50, 95, 99, max] 257.527µs, 227.055µs, 339.606µs, 520.189µs, 28.70824ms
Bytes In [total, mean] 240000, 2.00
Bytes Out [total, mean] 0, 0.00
Success [ratio] 100.00%
Status Codes [code:count] 200:120000
Error Set:
```
## Installation from sources and run
Download the Traefik sources and go to the directory
```bash
git clone https://github.com/traefik/traefik
cd traefik
```
Add this repo as a submodule
```bash
git submodule add https://github.com/negasus/traefik2-luascript pkg/middlewares/luascript
```
Add the code for the middleware config to the file `pkg/config/dynamic/middleware.go`
```go
type Middleware struct {
// ...
LuaScript *LuaScript `json:"luascript,omitempty" toml:"luascript,omitempty" yaml:"luascript,omitempty"`
// ...
}
// ...
// +k8s:deepcopy-gen=true
// LuaScript config
type LuaScript struct {
Script string `json:"script,omitempty" toml:"script,omitempty" yaml:"script,omitempty"`
}
```
Add the code for register a middleware to the file `pkg/server/middleware/middlewares.go`
```go
import (
// ...
"github.com/traefik/traefik/v2/pkg/middlewares/luascript"
// ...
)
// ...
func (b *Builder) buildConstructor(ctx context.Context, middlewareName string, config config.Middleware) (alice.Constructor, error) {
// ...
// BEGIN LUASCRIPT BLOCK
if config.LuaScript != nil {
if middleware == nil {
middleware = func(next http.Handler) (http.Handler, error) {
return luascript.New(ctx, next, *config.LuaScript, middlewareName)
}
} else {
return nil, badConf
}
}
// END LUASCRIPT BLOCK
if middleware == nil {
return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName)
}
return tracing.Wrap(ctx, middleware), nil
}
```
Build the Traefik
```bash
make build
```
Create a config file `config.yml`
```yml
log:
level: warn
entryPoints:
web:
address: ":8080"
providers:
file:
filename: "providers.yml"
```
and `providers.yml`
```yml
http:
routers:
router1:
rule: "Host(`localhost`)"
service: service1
middlewares:
- example
middlewares:
example:
luascript:
script: example.lua
services:
service1:
loadBalancer:
servers:
- url: "https://api.github.com/users/octocat/orgs"
```
Create a lua script `example.lua`
```lua
local traefik = require('traefik')
local log = require('log')
log.warn('Hello from LUA script')
traefik.setResponseHeader('X-New-Response-Header', 'Woohoo')
```
Run the traefik
```bash
./dist/traefik --configFile=config.yml
```
Call the traefik (from another terminal)
```bash
curl -v http://localhost:8080
```
And, as result, we see a traefik log
```
WARN[...] Hello from LUA script middlewareName=file.example-luascript middlewareType=LuaScript
```
A response from the github API with our header
```
...
< X-New-Response-Header: Woohoo
...
```
Done!
## API
### Traefik
`Traefik` module allows get information about current request. Add request/response headers, or interrupt the request.
Usage:
```lua
traefik = require('traefik')
```
**Get Request Header**
> getRequestHeader(**name** string) **value** string, **error**
If header not exists, returns no error and empty string value!
```lua
local traefik = require('traefik')
local log = require('log')
local h, err = traefik.getRequestHeader('X-Authorization')
if err ~= nil then
log.debug('error get header' .. err)
end
```
**Set Request Header**
> setRequestHeader(**name** string, **value** string) **error**
Set header for pass to backend
```lua
err = traefik.setRequestHeader('X-Authorization', 'SomeSecretToken')
```
**Get Request Body**
> getRequestBody() **value** string, **error**
```lua
local body, err = traefik.getRequestBody()
if err ~= nil then
log.debug('error get body ' .. err)
end
```
**Set Request Body**
> setRequestBody(**value** string) **error**
Set body for pass to backend
```lua
err = traefik.setRequestBody('{"foo": "bar"}')
```
**Set Response Header**
> setResponseHeader(**name** string, **value** string) **error**
Set header for return to client
```lua
err = traefik.setResponseHeader('X-Authorization', 'SomeSecretToken')
```
** Interrupt the request and return StatusCode and Body
> interrupt(**code** int, [**message** string]) **error**
```lua
err = traefik.interrupt(403)
-- or
err = traefik.interrupt(422, 'Validation Error')
```
**Get Request Query Argument**
> getQueryArg(**name** string) **value** string, **error**
Get value from query args
```lua
-- Get 'foo' for URL http://example.com/?token=foo
v, err = traefik.getQueryArg('token')
```
**Get Request**
> getRequest() **value** table
Get request info
```lua
info = traefik.getRequest()
{
method = 'GET',
uri = '...',
host = '...',
remoteAddr = '...',
referer = '...',
headers = {
key = 'value',
...
}
}
```
### LOG
Send a message to a traefik logger
> error(message string)
> warn(message string)
> info(message string)
> debug(message string)
```lua
local log = require('log')
log.error('an error occured')
log.debug('header ' .. h .. ' not exist')
```
### HTTP
Set HTTP requests to remote services
Usage:
```lua
http = request('http')
```
**request**
> request(**** table) **response**[, **error** string]
Send a request
**OPTIONS** is a table with request options
```
{
method = 'POST', -- http method. By default: GET
url = '', -- URL
body = '', -- request body. By default: empty
timeout = 100, -- timeout in milliseconds. By default: 250 (ms)
headers = { -- request heders
key = 'value',
...
}
}
```
**RESPONSE**
```
{
status = 200, -- response status code
body = '', -- response body
headers = { -- response headers
key = value,
...
}
}
```
**get, post, put, delete**
> get('url', [OPTIONS]) **response**[, **error** string]
> post('url', [OPTIONS]) **response**[, **error** string]
> put('url', [OPTIONS]) **response**[, **error** string]
> delete('url', [OPTIONS]) **response**[, **error** string]
Aliases for `request` with predefined Method and URL
### JSON
**Decode**
Decodes a JSON string. Returns nil and an error string if the string could not be decoded
> decode(**str** string) **value** value, **error**
The following types are supported:
Lua | JSON
-------- | --------
table | object: when table is non-empty and has only string keys; array: when table is empty, or has only sequential numeric keys starting from 1
string | string
number | number
nil | null
```lua
local json = require('json')
value, err = json.decode('{"foo": "bar"}')
```
**Encode**
Encodes a value into a JSON string. Returns nil and an error string if the value could not be encoded
> encode(**value** value) **str** string, **error**
```lua
local json = require('json')
t = {}
t[1] = "first"
str, err = json.encode(t)
```