Waffle is a fast, asynchronous, express-inspired web framework for Lua/Torch built on top of ASyNC.
Waffle's performance is impressive. On this test, given in examples/fib.lua, Waffle reaches over 20,000 requests/sec (2-4 x Node+express, 1/2 x multithreaded Go). With automatic caching enabled, Waffle can reach over 26,000 requests/sec, equaling single-threaded Go.
This project depends on htmlua for HTML templating.
> (sudo) luarocks install https://raw.githubusercontent.com/benglard/htmlua/master/htmlua-scm-1.rockspec > (sudo) luarocks install https://raw.githubusercontent.com/benglard/waffle/master/waffle-scm-1.rockspec localapp=require('waffle') app.get('/', function(req, res) res.send('Hello World!') end) app.listen()app.get('/', function(req, res) res.send('Getting...') end) app.post('/', function(req, res) res.send('Posting...') end) app.put('/', function(req, res) res.send('Putting...') end) app.delete('/', function(req, res) res.send('Deleting...') end)app.get('/user/(%d+)', function(req, res) localuserId=tonumber(req.params[1]) localusers={[1] ='Lua', [2] ='JavaScript', [3] ='Python' } res.send(string.format('Hello, %s', users[userId] or'undefined')) end) app.get('/user/(%a+)', function(req, res) localname=req.params[1] res.send(string.format('Hello, %s', name)) end)There are two options for html rendering. The first involves writing actual html and using the string interp utility provided, ${variable-name}.
<html><head></head><body><h3>Welcome, ${name}</h3><p>Time: ${time}</p></body></html>app.get('/render/(%a+)', function(req, res) res.render('./examples/template.html',{name=req.params[1], time=os.time() }) end)The second, preferable, more powerful way involves writing htmlua scripts, either as separate template files, or inline in view functions.
-- luatemp.htmllocalbase=extends'examples/baseluatemp.html'returnblock(base, 'content'){h3'Welcome, ${name}', p'Time: ${time}', ul(each([[${users}]], li)), img{src='https://www.google.com/images/srpr/logo11w.png' } }-- htmlua.lua-- Templateapp.get('/', function(req, res) res.htmlua('luatemp.html',{name='waffle', time=os.time(), users={'lua', 'python', 'javascript'} }) end) -- Inlineapp.get('/i', function(req, res) res.send(html{head{title'Title' }, body{p'Hello World!' } }) end)The htmlua page provides further documentation and examples.
app.get('/m', function(req, res) res.send(html{body{form{action='/m', method='POST', enctype='multipart/form-data', p{input{type='text', name='firstname', placeholder='First Name' }}, p{input{type='text', name='lastname', placeholder='Last Name' }}, p{input{type='file', name='file' }}, p{input{type='submit', 'Upload' }} }}}) end) app.post('/m', function(req, res) localname=string.format('%s %s', req.form.firstname, req.form.lastname) localpath=paths.add(os.getenv('HOME'), req.form.file.filename) req.form.file:save{path=path} res.send('Saved to ' ..path) end)You can easily transform an uploaded image into a typical torch tensor like so:
app.post('/', function(req, res) localimg=req.form.file:toImage() localm=img:mean() res.send('Image mean: ' ..m) end)To implement a websocket server, call app.ws with a url path and a function accepting a single table. You can then define checkorigin, onopen, onmessage, onpong, and onclose for that table, to control the server-side websocket connection.
Benchmarking websockets is tricky, but on first attempts, waffle seems competitive with similar node libraries.
localapp=require('waffle') localjs=[[var ws = new WebSocket("ws://127.0.0.1:8080/ws/");function print(){console.log(ws.readyState)}ws.onopen = function(){ console.log("opened"); print(); ws.send("Hello");}ws.onmessage = function(msg){ console.log(msg); setTimeout(function(){ws.close()}, 1000);}ws.onclose = function(event){ console.log(event); console.log("closed"); print();}]]app.get('/', function(req, res) res.send(html{body{p'Hello, World', script{type='text/javascript', js } }}) end) app.ws('/ws', function(ws) ws.checkorigin=function(origin) returnorigin=='http://127.0.0.1:8080' endws.onopen=function(req) print('/ws/opened') endws.onmessage=function(data) print(data) ws:write('World') ws:ping('test') endws.onpong=function(data) print(data) endws.onclose=function(req) print('/ws/closed') endend)You can broadcast a message to all open websockets on a url like this:
ws:broadcast('message')or like this:
app.ws.broadcast(req.url.path, 'message')app.get('/search', function(req, res) localsearch=req.url.args.qres.redirect('https://www.google.com/search?q=' ..search) end)localapp=require('waffle') app.set('public', '.') app.listen()app.error(404, function(description, req, res) localurl=string.format('%s%s', req.headers.host, req.url.path) res.status(404).send('No page found at ' ..url) end) app.error(500, function(description, req, res) ifapp.debugthenres.status(500).send(description) elseres.status(500).send('500 Error') endend)app.get('/cookie', function(req, res) localc=req.cookies.counteror-1res.cookie('counter', tonumber(c) +1) res.send('#' ..c) end)Waffle has both in-memory and redis sessions using redis-async.
localapp=require('../waffle').CmdLine() app.get('/', function(req, res) ifapp.session.type=='memory' thenlocaln=app.session.nor0res.send('#' ..n) ifn>19thenapp.session.n=nilelseapp.session.n=n+1endelseapp.session:get('n', function(n) res.send('#' ..n) ifn>19thenapp.session:delete('n') elseapp.session.n=n+1endend, 0) endend) app.listen()app.get('/', function(req, res) res.json{test=true} end)-- Add a name parameter, e.g. 'test'app.get('/test', function(req, res) res.send('Hello World!') end, 'test') -- Retreive url corresponding to route named 'test'localurl=app.urlfor('test')Modules let you group routes together by url and name (really by function)
app.module('/', 'home') -- Home Routes .get('', function(req, res) res.send'Home' end, 'index') .get('test', function(req, res) res.send'Test' end, 'test') app.module('/auth', 'auth') -- Authentication Routes .get('', function(req, res) res.redirect(app.urlfor('auth.login')) end, 'index') .get('/login', function(req, res) res.send'Login' end, 'login') .get('/signup', function(req, res) res.send'Signup' end, 'signup')Allows you to write every currently possible waffle application property as a command line option, and have it handled seamlessly.
localapp=require('waffle').CmdLine()> th examples/cmdline.lua --debug --print --port 3000 --templates examples/ app=require('waffle') a=1b=2c=3app.repl() app.listen()th>async=require'async' [0.0133s] th>async.repl.connect({host='127.0.0.1', port=8081}) [0.0005s] th>async.go() 127.0.0.1:8081>a1127.0.0.1:8081>b2127.0.0.1:8081>c3127.0.0.1:8081>app{... } 127.0.0.1:8081>_G{... }The wafflemaker executable can be used:
- to create project directories in MVC style, like so:
wafflemaker --create name_of_project - to serve static files akin to running
python -m SimpleHTTPServer, but with much, much, much better performance (almost 20x requests/sec).
cd folder/i/want/to/serve wafflemaker --serve When autocache is set to true, waffle will automatically store the response body, headers, and status code, and reuse them when a request is sent to the same http method/url. So, for instance, when a request is sent to GET/10 in the example below, it will only have to compute fib(10) once. Note that app.urlCache is set by default to cache the data of the last 20 method/url requests.
localapp=require('../waffle'){debug=true, autocache=true } fib=function(n) ifn==0thenreturn0elseifn==1thenreturn1elsereturnfib(n-1) +fib(n-2) endendapp.get('/(%d+)', function(req, res) localn=req.params[1] localresult=fib(tonumber(n)) res.header('Content-Type', 'text/html') res.send('ASyNC + Waffle<hr> fib(' ..n..'): ' ..result) end) app.listen()- Named URL route parameters
- Automatic caching of static files
- Secure cookies & cookie based sessions