/usr/share/doc/lua-apr-doc/examples/async-webserver.lua is in lua-apr-doc 0.23.2.dfsg-4.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | --[[
Example: Asynchronous webserver
Author: Peter Odding <peter@peterodding.com>
Last Change: November 6, 2011
Homepage: http://peterodding.com/code/lua/apr/
License: MIT
We can do even better than the performance of the multi threaded webserver by
using the [APR pollset module] [pollset_module]. The following webserver uses
asynchronous network communication to process requests from multiple clients
'in parallel' without using multiple threads or processes. Here is a
benchmark of the asynchronous code listed below (again using [ApacheBench]
[ab] with the `-c` argument):
$ CONCURRENCY=4
$ POLLSET_SIZE=10
$ lua examples/async-webserver.lua $POLLSET_SIZE 8080 cheat &
$ ab -qt5 -c$CONCURRENCY http://localhost:8080/ | grep 'Requests per second\|Transfer rate'
Requests per second: 11312.64 [#/sec] (mean)
Transfer rate: 6219.74 [Kbytes/sec] received
The [single threaded webserver] [simple_server] handled 3670 requests per
second, the [multi threaded webserver] [threaded_server] handled 9210
requests per second and the asynchronous webserver below can handle 11310
requests per second. Actually in the above benchmark I may have cheated a bit
(depending on whether your goal is correct usage or performance). When I
started writing this asynchronous server example I didn't bother to add
writable sockets to the pollset, instead I handled the request and response
once the client socket was reported as readable by the pollset. Later on I
changed the code to handle writing using the pollset and I noticed that the
performance dropped. This is probably because the example is so contrived.
Anyway here's the performance without cheating:
$ lua examples/async-webserver.lua $POLLSET_SIZE 8080 &
$ ab -qt5 -c$CONCURRENCY http://localhost:8888/ | grep 'Requests per second\|Transfer rate'
Requests per second: 9867.17 [#/sec] (mean)
Transfer rate: 5425.03 [Kbytes/sec] received
Now follows the implementation of the asynchronous webserver example:
[pollset_module]: #pollset
[ab]: http://en.wikipedia.org/wiki/ApacheBench
[simple_server]: #example_single_threaded_webserver
[threaded_server]: #example_multi_threaded_webserver
]]
local pollset_size = tonumber(arg[1]) or 10
local port_number = tonumber(arg[2]) or 8080
local cheat = arg[3] == 'cheat' -- cheat to make it faster?
local template = [[
<html>
<head>
<title>Hello from Lua/APR!</title>
<style type="text/css">
body { font-family: sans-serif; }
dt { font-weight: bold; }
dd { font-family: monospace; margin: -1.4em 0 0 14em; }
</style>
</head>
<body>
<h1>Hello from Lua/APR!</h1>
<p>The headers provided by your web browser:</p>
<dl>%s</dl>
</body>
</html>
]]
-- Load the Lua/APR binding.
local apr = require 'apr'
-- Initialize the server socket.
local server = assert(apr.socket_create())
assert(server:bind('*', port_number))
assert(server:listen(pollset_size))
-- Create the pollset.
local pollset = assert(apr.pollset(pollset_size))
assert(pollset:add(server, 'input'))
-- Use a table indexed with socket objects to keep track of "sessions".
local sessions = setmetatable({}, {__mode='k'})
-- Enter the I/O loop.
print("Running webserver on http://localhost:" .. port_number .. " ..")
while true do
local readable, writable = assert(pollset:poll(-1))
-- Process requests.
for _, socket in ipairs(readable) do
if socket == server then
local client = assert(server:accept())
assert(pollset:add(client, 'input'))
else
local request = assert(socket:read(), "Failed to receive request from client!")
local method, location, protocol = assert(request:match '^(%w+)%s+(%S+)%s+(%S+)')
local headers = {}
for line in socket:lines() do
local name, value = line:match '^(%S+):%s+(.-)$'
if not name then
break
end
table.insert(headers, '<dt>' .. name .. ':</dt><dd>' .. value .. '</dd>')
end
table.sort(headers)
local content = template:format(table.concat(headers))
assert(socket:write(
protocol, ' 200 OK\r\n',
'Content-Type: text/html\r\n',
'Content-Length: ', #content, '\r\n',
'Connection: close\r\n',
'\r\n'))
if cheat then
assert(socket:write(content))
assert(pollset:remove(socket))
assert(socket:close())
else
sessions[socket] = content
assert(pollset:remove(socket))
assert(pollset:add(socket, 'output'))
end
end
end
if not cheat then
-- Process responses.
for _, socket in ipairs(writable) do
assert(socket:write(sessions[socket]))
-- I don't like that when I switch the order of these
-- calls, it breaks... Seems like a fairly big gotcha.
assert(pollset:remove(socket))
assert(socket:close())
end
end
end
-- vim: ts=2 sw=2 et
|