Git Product home page Git Product logo

waffle's Introduction

Waffle

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.

Installation

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

Hello World

local app = require('waffle')

app.get('/', function(req, res)
   res.send('Hello World!')
end)

app.listen()

Requests

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)

URL Parameters

app.get('/user/(%d+)', function(req, res)
   local userId = tonumber(req.params[1])
   local users = {
      [1] = 'Lua',
      [2] = 'JavaScript',
      [3] = 'Python'
   }
   res.send(string.format('Hello, %s', users[userId] or 'undefined'))
end)

app.get('/user/(%a+)', function(req, res)
   local name = req.params[1]
   res.send(string.format('Hello, %s', name))
end)

HTML Rendering

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.html
local base = extends 'examples/baseluatemp.html'
return block(base, 'content'){
   h3 'Welcome, ${name}',
   p 'Time: ${time}',
   ul(each([[${users}]], li)),
   img {
      src = 'https://www.google.com/images/srpr/logo11w.png'
   }
}
-- htmlua.lua
-- Template
app.get('/', function(req, res)
   res.htmlua('luatemp.html', {
      name = 'waffle',
      time = os.time(),
      users = {'lua', 'python', 'javascript'}
   })
end)

-- Inline
app.get('/i', function(req, res)
   res.send(html {
      head { title 'Title' },
      body { p 'Hello World!' }
   })
end)

The htmlua page provides further documentation and examples.

Form Parsing

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)
   local name = string.format('%s %s', req.form.firstname, req.form.lastname)
   local path = 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)
   local img = req.form.file:toImage()
   local m = img:mean()
   res.send('Image mean: ' .. m)
end)

Websockets

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.

local app = require('waffle')
local js = [[
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) return origin == 'http://127.0.0.1:8080' end
   ws.onopen = function(req) print('/ws/opened') end
   ws.onmessage = function(data)
      print(data)
      ws:write('World')
      ws:ping('test')
   end
   ws.onpong = function(data) print(data) end
   ws.onclose = function(req) print('/ws/closed') end
end)

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

Query Paramaters

app.get('/search', function(req, res)
   local search = req.url.args.q
   res.redirect('https://www.google.com/search?q=' .. search)
end)

Static Files

local app = require('waffle')
app.set('public', '.')
app.listen()

Error Handling

app.error(404, function(description, req, res)
   local url = 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)
   if app.debug then
      res.status(500).send(description)
   else
      res.status(500).send('500 Error')
   end
end)

Cookies

app.get('/cookie', function(req, res)
   local c = req.cookies.counter or -1
   res.cookie('counter', tonumber(c) + 1)
   res.send('#' .. c)
end)

Sessions

Waffle has both in-memory and redis sessions using redis-async.

local app = require('../waffle').CmdLine()

app.get('/', function(req, res)
   if app.session.type == 'memory' then
      local n = app.session.n or 0
      res.send('#' .. n)
      if n > 19 then app.session.n = nil
      else app.session.n = n + 1 end
   else
      app.session:get('n', function(n)
         res.send('#' .. n)
         if n > 19 then app.session:delete('n')
         else app.session.n = n + 1 end
      end, 0)
   end
end)

app.listen()

JSON

app.get('/', function(req, res)
   res.json{test=true}
end)

urlfor and Modules

-- 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'
local url = 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')

Command Line Options

Allows you to write every currently possible waffle application property as a command line option, and have it handled seamlessly.

local app = require('waffle').CmdLine()
> th examples/cmdline.lua --debug --print --port 3000 --templates examples/

Async Debugging

app = require('waffle')
a = 1
b = 2
c = 3
app.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> a
1  
127.0.0.1:8081> b
2  
127.0.0.1:8081> c
3  
127.0.0.1:8081> app
{ ... }
127.0.0.1:8081> _G
{ ... }

wafflemaker (executable)

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

Larger example (with autocache)

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.

local app = require('../waffle') {
   debug = true,
   autocache = true
}

fib = function(n)
   if n == 0 then return 0
   elseif n == 1 then return 1
   else return fib(n-1) + fib(n-2)
   end
end

app.get('/(%d+)', function(req, res)
   local n = req.params[1]
   local result = fib(tonumber(n))
   res.header('Content-Type', 'text/html')
   res.send('ASyNC + Waffle<hr> fib(' .. n .. '): ' .. result)
end)

app.listen()

TODO

  • Named URL route parameters
  • Automatic caching of static files
  • Secure cookies & cookie based sessions

waffle's People

Contributors

benglard avatar christopher5106 avatar joostvdoorn avatar tgeorgy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

waffle's Issues

How to use uploaded image?

In the readme section there is a code snippet -

app.post('/', function(req, res)
   local img = req.form.file:toImage()
   local m = img:mean()
   res.send('Image mean: ' .. m)
end)

It is supposed to work with images. I can't get it to work. How should I send the image via the POST? I am using postman to send POST objects to the server.

I can't understand what this code (req.form.file:toImage()) means.

getting image from a POST

Hi there,

Thanks for this nice library. I am trying to receive images as a REST API. I have one version that works with base64 encoded images like so -

app.post('/image/', function(req, res)
   local img = base64.decode(req.body)
   local tmpfile = getname(req.body)
   local tmphandle = assert(io.open(app.datadir .. tmpfile, 'wb'))

   tmphandle:write(img)
   io.close(tmphandle)
   res.json{file_id=tmpfile}
end)

But this is too slow.. I'm trying to get another version working with multipart image so it works faster.
I'm using the example shown for multipart forms but I am not able to get it to work with this code -

app.post('/m/', function(req, res)
    local tmpfile = getname(req.body)

    print("got file " .. tmpfile ..'!')
    req.form.file:save{path = app.datadir .. tmpfile}
    res.json{file_id = tmpfile}
end)

It doesn't seem to work for binary image posts. Have you tried this before?

Thanks,

request.lua:67: attempt to index local 'ctype'

I got this kind of error during a POST

Request from POST/api/v1/
/root/torch/install/bin/luajit: /root/torch/install/share/lua/5.1/waffle/request.lua:67: attempt to index local 'ctype' (a nil value)
stack traceback:
/root/torch/install/share/lua/5.1/waffle/request.lua:67: in function '_getform'
/root/torch/install/share/lua/5.1/waffle/request.lua:140: in function 'Request'
/root/torch/install/share/lua/5.1/waffle/app.lua:93: in function 'handler'
/root/torch/install/share/lua/5.1/async/http.lua:161: in function </root/torch/install/share/lua/5.1/async/http.lua:110>
[C]: in function 'execute'
/root/torch/install/share/lua/5.1/async/http.lua:224: in function 'cb'
/root/torch/install/share/lua/5.1/async/handle.lua:33: in function </root/torch/install/share/lua/5.1/async/handle.lua:32>
[C]: in function 'run'
/root/torch/install/share/lua/5.1/async/init.lua:35: in function 'go'
/root/torch/install/share/lua/5.1/waffle/app.lua:143: in function 'listen'
api/init.lua:88: in main chunk
[C]: in function 'dofile'
/root/torch/install/lib/luarocks/rocks/trepl/scm-1/bin/th:145: in main chunk
[C]: at 0x00406670

Thanks a lot for your help

buffer not in rockspec

buffer seems to be a package dependency at runtime, but it isn't a listed dependency in the .rockspec

Server hanging when making parallel requests for static content

We define an entry point for static content like this
app.set('public','/path/ImagesDownSampled/down.3/')

and then we make a few parallel requests with curl (these are images, ranging in size 5k to 10k bytes and the server often (easy to reproduce) hangs on some of the requests. This doesn't seem to happen when we serialize the calls (so far we have not hit an issue). When this error occurs we see "error on client - code: 13".

"Aborted" error on any code.

I'm running this on an Arch linux chroot environment on Termux. I had to compile Async myself due to implicit fallthrough errors. Any and all code I try to run that uses this library errors out.

app = require('waffle')

app.get('/', function(req, res)
   res.send('Hello World!')
end)

app.listen()
[19:12 torch-site ]$ th server.lua
Listening on 127.0.0.1:8080
Aborted

loop or previous error loading module 'buffer'

Hi,

when I run "require 'waffle' in torch I get this error:

th> require 'waffle'
...petrfiala/torch/install/share/lua/5.1/trepl/init.lua:383: ...petrfiala/torch/install/share/lua/5.1/trepl/init.lua:383: ...petrfiala/torch/install/share/lua/5.1/trepl/init.lua:383: ...petrfiala/torch/install/share/lua/5.1/trepl/init.lua:383: ...petrfiala/torch/install/share/lua/5.1/trepl/init.lua:383: ...petrfiala/torch/install/share/lua/5.1/trepl/init.lua:383: loop or previous error loading module 'buffer'
stack traceback:
...petrfiala/torch/install/share/lua/5.1/trepl/init.lua:500: in function <...petrfiala/torch/install/share/lua/5.1/trepl/init.lua:493>
[C]: in function 'error'
...petrfiala/torch/install/share/lua/5.1/trepl/init.lua:383: in function 'require'
[string "_RESULT={require 'waffle'}"]:1: in main chunk
[C]: in function 'xpcall'
...petrfiala/torch/install/share/lua/5.1/trepl/init.lua:650: in function 'repl'
.../torch/install/lib/luarocks/rocks/trepl/scm-1/bin/th:199: in main chunk
[C]: ?
[0.0375s]
th>

I have OS X, El Capitan.

Allow changing content type for static files

Hi there,

Thanks for the amazing package. It has really sped up my torch development.

I am trying to serve a specific kind of file and say what it is in the content type. In other words, I want to say for file x.myfile the content type is 'application/myfile'.

Is the best way to do this to modify the code at https://github.com/benglard/waffle/blob/master/waffle/response.lua#L47 ?

Apologies as I'm new to lua and not quite sure what the best way to do this is.

Thanks,

ipv6

Is it possbile to use waffle in ipv6 only envirement?

I tried different --host params but can't recieve a result.

th daemon.lua --debug --print --port 8091 --host ::1
Listening on ::1:8091

curl -g -6 "http://[::1]:8091/m"
curl: (7) couldn't connect to host

Cannot make the request from the other domain.

I have the other php server and want to make the http get to this waffle lua server. But it always displays the following error:
XMLHttpRequest cannot load http://[LUA SERVER]/query?q=hi. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://[PHP SERVER]' is therefore not allowed access. The response had HTTP status code 502.

How to make the lua support the Access-Control-Allow-Origin?

toImage()

Requires graphicsmagick

luarocks install graphicsmagick

Please advise it somewhere

Web server not working, waffle.lua module not found

Dear Developer,

I really liked your module for the waffle web server, but when I run the examples, the waffle.lua module is not found for some reason. Downloaded the entire repository, run the command:

C:\xampp\htdocs\waffle-master\examples>lua C:\xampp\htdocs\waffle-master\examples\form.lua
lua: C:\xampp\htdocs\waffle-master\examples\form.lua:1: module '../waffle' not found:
no field package.preload['../waffle']
no file '.\/waffle.lua'
no file 'C:\Program Files (x86)\Lua\5.1\lua\/waffle.lua'
no file 'C:\Program Files (x86)\Lua\5.1\lua\/waffle\init.lua'
no file 'C:\Program Files (x86)\Lua\5.1\/waffle.lua'
no file 'C:\Program Files (x86)\Lua\5.1\/waffle\init.lua'
no file 'C:\Program Files (x86)\Lua\5.1\lua\/waffle.luac'
no file 'C:\Program Files (x86)\Lua\5.1\clibs\/waffle.dll'
no file 'C:\Program Files (x86)\Lua\5.1\clibs\luasql\/waffle.dll'
no file 'C:\Program Files (x86)\Lua\5.1\clibs.dll'
no file 'C:\Program Files (x86)\Lua\5.1\clibs\luasql.dll'
stack trace:
[C]: in function 'require'
C:\xampp\htdocs\waffle-master\examples\form.lua:1: in main chunk
[C]: ?

Help, please, to understand.

Sincerely,
Alexei

async after-the-request action

Hi,

How would you perform an action at each request, that does not delay rendering the answer to the request ?

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.