Skip to content

negasus/traefik2-luascript

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Traefik2 LuaScript

LuaScript is middleware for Traefik v2 for execute lua script with access to API

Under cover used LUA VM from Yusuke Inuzuka

An issue

Post on the community portal

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

-- 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

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

echo "GET http://localhost/" | vegeta attack -rate 2000 -duration=60s | tee results.bin | vegeta report

With LUA

A Traefik config

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

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

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

git clone https://github.com/traefik/traefik
cd traefik

Add this repo as a submodule

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

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

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

make build

Create a config file config.yml

log:
  level: warn

entryPoints:
  web:
    address: ":8080"

providers:
  file:
    filename: "providers.yml"

and providers.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

local traefik = require('traefik')
local log = require('log')

log.warn('Hello from LUA script')
traefik.setResponseHeader('X-New-Response-Header', 'Woohoo')

Run the traefik

./dist/traefik --configFile=config.yml

Call the traefik (from another terminal)

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:

traefik = require('traefik')

Get Request Header

getRequestHeader(name string) value string, error

If header not exists, returns no error and empty string value!

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

err = traefik.setRequestHeader('X-Authorization', 'SomeSecretToken')

Get Request Body

getRequestBody() value string, error

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

err = traefik.setRequestBody('{"foo": "bar"}')

Set Response Header

setResponseHeader(name string, value string) error

Set header for return to client

err = traefik.setResponseHeader('X-Authorization', 'SomeSecretToken')

** Interrupt the request and return StatusCode and Body

interrupt(code int, [message string]) error

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

-- Get 'foo' for URL http://example.com/?token=foo
v, err = traefik.getQueryArg('token')

Get Request

getRequest() value table

Get request info

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)

local log = require('log')

log.error('an error occured')
log.debug('header ' .. h .. ' not exist')

HTTP

Set HTTP requests to remote services

Usage:

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
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

local json = require('json')
t = {}
t[1] = "first" 
str, err = json.encode(t)

About

LuaScript middleware for Traefik v2

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published