A web framework for luvit 2.0 and lit
Weblit is a collection of lit packages that together form a nice web framework.
This is the core of the framework. It's export value is the app itself. The config functions can be chained off this config for super terse syntax.
require('weblit-app')
.bind({
host = "0.0.0.0",
port = 8080
})
.use(require('weblit-logger'))
.use(require('weblit-auto-headers'))
.use(require('weblit-etag-cache'))
.route({
method = "GET",
path = "/do/:user/:action",
domain = "*.myapp.io"
}, function (req, res, go)
-- Handle route
end)
.start()
Use this to configure your server. You can bind to multiple addresses and
ports. For example, the same server can listen on port 8080
using normal HTTP
while also listening on port 8443
using HTTPS.
-- Listen on port 8080 internally using plain HTTP.
.bind({
host = "127.0.0.1",
port = 8080
})
-- Also listen on port 8443 externally using encrypted HTTPS.
.bind({
host = "0.0.0.0",
port = 8443,
tls = {
cert = module:load("cert.pem"),
key = module:load("key.pem"),
}
})
The host
option defaults to "127.0.0.1"
. The default port depends on if
you're running as root and if the connection is TLS encrypted.
Root | User | |
---|---|---|
HTTP | 80 | 8080 |
HTTPS | 442 | 8443 |
This adds a raw middleware to the chain. It's signature is:
.use(function (req, res, go)
-- Log the request table
p("request", req)
-- Hand off to the next layer.
return go()
end)
The req
table will contain information about the HTTP request. This includes
several fields:
socket
- The raw libuvuv_tty_t
socket.method
- The HTTP request method verb likeGET
orPOST
.path
- The raw HTTP request path (including query string).headers
- A list of headers. Each header is a table with two entries for key and value. For convenience, there are special__index
and__newindex
metamethods that let you treat this like a case insensitive key/value store.version
- The HTTP version (Usually either1.0
or1.1
).keepAlive
- A flag telling you if this should be a keepalive connection.body
- The request body as a string. In the future, this may also be a stream.
The res
table also has some conventions used to form the response a piece at a
time. Initially it contains:
code
- The response status code. Initially contains404
.headers
- Another special headers table like inreq
.body
- The response body to send. Initially contains"Not Found\n"
.
The go
function is to be called if you wish to not handle a request. This
allows other middleware layers to get a chance to respond to the request. Use a
tail call if there is nothing more to do.
Otherwise do further processing after go
returns. At this point, all inner
layers have finished and a response is ready in res
.
Route is like use, but allows you to pre-filter the requests before the middleware is called.
.route({
method = "PUT",
path = "/upload/:username"
}, function (req, res, go)
local url = saveFile(req.params.username, req.body)
res.code = 201
res.headers.Location = url
end)
The route options accept several parameters:
method
- This is a simple filter on a specific HTTP method verb.path
- This is either an exact match or can contain patterns. Segments looking like:name
will match single path segments while:name:
will match multiple segments. The matches will go intoreq.params
. Also any query string will be stripped off, parsed out, and stored inreq.query
.host
- Will filter against theHost
header. This can be an exact match or a glob match like*.mydomain.org
.filter
- Filter is a custom lua function that acceptsreq
and returnstrue
orfalse
.
If the request matches all the requirements, then the middleware is called the
same as with use
.
Bind to the port(s), listen on the socket(s) and start accepting connections.
This is a simple middleware that logs the request method, url and user agent. It also includes the response status code.
Make sure to use it at the top of your middleware chain so that it's able to see the final response code sent to the client.
.use(require('weblit-logger'))
This implements lots of conventions and useful defaults that help your app implement a proper HTTP server.
You should always use this near the top of the list. The only middleware that goes before this is the logger.
.use(require('weblit-auto-headers'))
This caches responses in memory keyed by etag. If there is no etag, but there is a response body, it will use the body to generate an etag.
Put this in your list after auto-headers, but before custom server logic.
.use(require('weblit-etag-cache'))
This middleware serves static files to the user. Use this to serve your client- side web assets.
Usage is pretty simplistic for now.
local static = require('weblit-static')
app.use(static("path/to/static/assets"))
If you want to only match a sub-path, use the router.
app.route({
path = "/blog/:path:"
}, static(pathJoin(module.dir, "articles")))
The path
param will be used if it exists and the full path will be used
otherwise.
This implements a websocket upgrade handler. You can choose the subprotocol and other routing information.
app.websocket({
path = "/v2/socket", -- Prefix for matching
protocol = "virgo/2.0", -- Restrict to a websocket sub-protocol
}, function (req, read, write)
-- Log the request headers
p(req)
-- Log and echo all messages
for message in read do
write(message)
end
-- End the stream
write()
end)
This is the metapackage that simply includes the other modules.
It exposes the other modules as a single exports table.
return {
app = require('weblit-app'),
autoHeaders = require('weblit-auto-headers'),
etagCache = require('weblit-etag-cache'),
logger = require('weblit-logger'),
static = require('weblit-static'),
websocket = require('weblit-websocket'),
}