mirror of
https://github.com/apache/httpd.git
synced 2025-07-04 05:22:30 +03:00
This commit was made thanks to the tool and PR created by Lajos Veres (vlajos) on github. PR: https://github.com/apache/httpd/pull/6 Tool: https://github.com/vlajos/misspell_fixer git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1780210 13f79535-47bb-0310-9956-ffa450edef68
586 lines
19 KiB
XML
586 lines
19 KiB
XML
<?xml version='1.0' encoding='UTF-8' ?>
|
|
<!DOCTYPE manualpage SYSTEM "../style/manualpage.dtd">
|
|
<?xml-stylesheet type="text/xsl" href="../style/manual.en.xsl"?>
|
|
|
|
<!-- $LastChangedRevision$ -->
|
|
|
|
<!--
|
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
|
contributor license agreements. See the NOTICE file distributed with
|
|
this work for additional information regarding copyright ownership.
|
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
|
(the "License"); you may not use this file except in compliance with
|
|
the License. You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
-->
|
|
|
|
<manualpage metafile="lua.xml.meta">
|
|
<parentdocument href="./">Developer</parentdocument>
|
|
|
|
<title>Creating hooks and scripts with mod_lua</title>
|
|
|
|
<summary>
|
|
<p>This document expands on the <module>mod_lua</module> documentation and explores
|
|
additional ways of using mod_lua for writing hooks and scripts.</p>
|
|
</summary>
|
|
|
|
<seealso><a href="../mod/mod_lua.html">mod_lua</a></seealso>
|
|
<seealso><a href="modguide.html">Developing modules for Apache 2.4</a></seealso>
|
|
<seealso><a href="request.html">Request Processing in Apache 2.4</a></seealso>
|
|
<seealso><a href="hooks.html">Apache 2.x Hook Functions</a></seealso>
|
|
|
|
<section id="introduction"><title>Introduction</title>
|
|
<section id="what"><title>What is mod_lua</title>
|
|
<p>
|
|
Stuff about what <module>mod_lua</module> is goes here.
|
|
</p>
|
|
</section>
|
|
<section id="contents"><title>What we will be discussing in this document</title>
|
|
<p>
|
|
This document will discuss several cases where <module>mod_lua</module> can be used
|
|
to either ease up a phase of the request processing or create more transparency in
|
|
the logic behind a decision made in a phase.
|
|
</p>
|
|
|
|
</section>
|
|
|
|
<section id="prerequisites"><title>Prerequisites</title>
|
|
<p>
|
|
First and foremost, you are expected to have a basic knowledge of how the Lua
|
|
programming language works. In most cases, we will try to be as pedagogical
|
|
as possible and link to documents describing the functions used in the
|
|
examples, but there are also many cases where it is necessary to either
|
|
just assume that "it works" or do some digging yourself into what the hows
|
|
and whys of various function calls.
|
|
</p>
|
|
|
|
|
|
</section>
|
|
</section>
|
|
|
|
<section id="enabling"><title>Optimizing mod_lua for production servers</title>
|
|
|
|
<section><title>Setting a scope for Lua states</title>
|
|
<p>
|
|
Setting the right <directive module="mod_lua">LuaScope</directive> setting
|
|
for your Lua scripts can be essential to your server's
|
|
performance. By default, the scope is set to <code>once</code>, which means
|
|
that every call to a Lua script will spawn a new Lua state that handles that
|
|
script and is destroyed immediately after. This option keeps the memory
|
|
footprint of mod_lua low, but also affects the processing speed of a request.
|
|
If you have the memory to spare, you can set the scope to <code>thread</code>,
|
|
which will make mod_lua spawn a Lua state that lasts the entirety of a thread's
|
|
lifetime, speeding up request processing by 2-3 times. Since mod_lua will create
|
|
a state for each script, this may be an expensive move, memory-wise, so to
|
|
compromise between speed and memory usage, you can choose the <code>server</code>
|
|
option to create a pool of Lua states to be used. Each request for a Lua script or
|
|
a hook function will then acquire a state from the pool and release it back when it's
|
|
done using it, allowing you to still gain a significant performance increase, while
|
|
keeping your memory footprint low. Some examples of possible settings are:
|
|
</p>
|
|
<highlight language="config">
|
|
LuaScope once
|
|
LuaScope thread
|
|
LuaScope server 5 40
|
|
</highlight>
|
|
<p>
|
|
As a general rule of thumb: If your server has none to low usage, use <code>once</code>
|
|
or <code>request</code>, if your server has low to medium usage, use the <code>server</code>
|
|
pool, and if it has high usage, use the <code>thread</code> setting. As your server's
|
|
load increases, so will the number of states being actively used, and having your scope
|
|
set to <code>once/request/conn</code> will stop being beneficial to your memory footprint.
|
|
</p>
|
|
<p>
|
|
<strong>Note:</strong> The <code>min</code> and <code>max</code> settings for the
|
|
<code>server</code> scope denotes the minimum and maximum states to keep in a pool per
|
|
server <em>process</em>, so keep this below your <code>ThreadsPerChild</code> limit.
|
|
</p>
|
|
</section>
|
|
|
|
<section><title>Using code caching</title>
|
|
<p>
|
|
By default, <module>mod_lua</module> stats each Lua script to determine whether a reload
|
|
(and thus, a re-interpretation and re-compilation) of a script is required. This is managed
|
|
through the <directive module="mod_lua">LuaCodeCache</directive> directive. If you are running
|
|
your scripts on a production server, and you do not need to update them regularly, it may be
|
|
advantageous to set this directive to the <code>forever</code> value, which will cause mod_lua
|
|
to skip the stat process and always reuse the compiled byte-code from the first access to the
|
|
script, thus speeding up the processing. For Lua hooks, this can prove to increase peformance,
|
|
while for scripts handled by the <code>lua-script</code> handler, the increase in performance
|
|
may be negligible, as files httpd will stat the files regardless.
|
|
</p>
|
|
</section>
|
|
|
|
<section><title>Keeping the scope clean</title>
|
|
<p>
|
|
For maximum performance, it is generally recommended that any initialization of libraries,
|
|
constants and master tables be kept outside the handle's scope:
|
|
</p>
|
|
<highlight language="lua">
|
|
--[[ This is good practice ]]--
|
|
require "string"
|
|
require "someLibrary"
|
|
local masterTable = {}
|
|
local constant = "Foo bar baz"
|
|
|
|
function handle(r)
|
|
do_stuff()
|
|
end
|
|
</highlight>
|
|
<highlight language="lua">
|
|
--[[ This is bad practice ]]--
|
|
require "string"
|
|
|
|
function handle(r)
|
|
require "someLibrary"
|
|
local masterTable = {}
|
|
local constant = "Foo bar baz"
|
|
do_stuff()
|
|
end
|
|
</highlight>
|
|
</section>
|
|
|
|
</section>
|
|
|
|
<section id="basic_remap"><title>Example 1: A basic remapping module</title>
|
|
<p>
|
|
These first examples show how mod_lua can be used to rewrite URIs in the same
|
|
way that one could do using <directive module="mod_alias">Alias</directive> or
|
|
<directive module="mod_rewrite">RewriteRule</directive>, but with more clarity
|
|
on how the decision-making takes place, as well as allowing for more complex
|
|
decisions than would otherwise be allowed with said directives.
|
|
</p>
|
|
|
|
<highlight language="config">
|
|
LuaHookTranslateName /path/too/foo.lua remap
|
|
</highlight>
|
|
|
|
|
|
<!-- BEGIN EXAMPLE CODE -->
|
|
<highlight language="lua">
|
|
--[[
|
|
Simple remap example.
|
|
This example will rewrite /foo/test.bar to the physical file
|
|
/internal/test, somewhat like how mod_alias works.
|
|
]]--
|
|
|
|
function remap(r)
|
|
-- Test if the URI matches our criteria
|
|
local barFile = r.uri:match("/foo/([a-zA-Z0-9]+)%.bar")
|
|
if barFile then
|
|
r.filename = "/internal/" .. barFile
|
|
end
|
|
return apache2.OK
|
|
end
|
|
</highlight>
|
|
<!-- END EXAMPLE CODE -->
|
|
|
|
|
|
<!-- BEGIN EXAMPLE CODE -->
|
|
<highlight language="lua">
|
|
--[[
|
|
Advanced remap example.
|
|
This example will evaluate some conditions, and based on that,
|
|
remap a file to one of two destinations, using a rewrite map.
|
|
This is similar to mixing AliasMatch and ProxyPass, but
|
|
without them clashing in any way. Assuming we are on example.com, then:
|
|
|
|
http://example.com/photos/test.png will be rewritten as /uploads/www/test.png
|
|
http://example.com/ext/foo.html will be proxied to http://www.external.com/foo.html
|
|
URIs that do not match, will be served by their respective default handlers
|
|
]]--
|
|
|
|
local map = {
|
|
photos = {
|
|
source = [[^/photos/(.+)\.png$]],
|
|
destination = [[/uploads/www/$1.png]],
|
|
proxy = false
|
|
},
|
|
externals = {
|
|
source = [[^/ext/(.*)$]],
|
|
destination = [[http://www.external.com/$1]],
|
|
proxy = true
|
|
}
|
|
}
|
|
|
|
function interpolateString(s,v)
|
|
return s:gsub("%$(%d+)", function(a) return v[tonumber(a)] end)
|
|
end
|
|
|
|
function remap(r)
|
|
-- browse through the rewrite map
|
|
for key, entry in pairs(map) do
|
|
-- Match source regex against URI
|
|
local match = r:regex(entry.source, r.uri) then
|
|
if match and match[0] then
|
|
r.filename = interpolateString(entry.destination, match)
|
|
-- Is this a proxied remap?
|
|
if entry.proxy then
|
|
r.handler = "proxy-server" -- tell mod_proxy to handle this
|
|
r.proxyreq = apache2.PROXYREQ_REVERSE -- We'll want to do a reverse proxy
|
|
r.filename = "proxy:" .. r.filename -- Add the proxy scheme to the destination
|
|
end
|
|
return apache2.OK
|
|
end
|
|
end
|
|
return apache2.DECLINED
|
|
end
|
|
</highlight>
|
|
<!-- END EXAMPLE CODE -->
|
|
|
|
<p>
|
|
bla bla
|
|
</p>
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section id="mass_vhost"><title>Example 2: Mass virtual hosting</title>
|
|
<p>
|
|
As with simple and advanced rewriting, you can use mod_lua for dynamically
|
|
assigning a hostname to a specific document root, much like
|
|
<module>mod_vhost_alias</module> does, but with more control over what goes
|
|
where. This could be as simple as a table holding the information about which
|
|
host goes into which folder, or more advanced, using a database holding the
|
|
document roots of each hostname.
|
|
</p>
|
|
|
|
<highlight language="config">
|
|
LuaHookTranslateName /path/too/foo.lua mass_vhost
|
|
</highlight>
|
|
|
|
|
|
<!-- BEGIN EXAMPLE CODE -->
|
|
<highlight language="lua">
|
|
--[[
|
|
Simple mass vhost script
|
|
This example will check a map for a virtual host and rewrite filename and
|
|
document root accordingly.
|
|
]]--
|
|
|
|
local vhosts = {
|
|
{ domain = "example.com", home = "/www/example.com" },
|
|
{ domain = "example.org", home = "/nfs/ext1/example.org" }
|
|
}
|
|
|
|
function mass_vhost(r)
|
|
-- Match against our hostname
|
|
for key, entry in pairs(vhosts) do
|
|
-- match against either host or *.host:
|
|
if apache2.strcmp_match(r.hostname, entry.domain) or
|
|
apache2.strcmp_match(r.hostname, "*." .. entry.domain) then
|
|
-- If it matches, rewrite filename and set document root
|
|
local filename = r.filename:sub(r.document_root:len()+1)
|
|
r.filename = entry.home .. filename
|
|
apahce2.set_document_root(entry.home)
|
|
return apache2.OK
|
|
end
|
|
end
|
|
return apache2.DECLINED
|
|
end
|
|
</highlight>
|
|
<!-- END EXAMPLE CODE -->
|
|
|
|
|
|
<!-- BEGIN EXAMPLE CODE -->
|
|
<highlight language="lua">
|
|
--[[
|
|
Advanced mass virtual hosting
|
|
This example will query a database for vhost entries and save them for
|
|
60 seconds before checking for updates. For best performance, such scripts
|
|
should generally be run with LuaScope set to 'thread' or 'server'
|
|
]]--
|
|
|
|
local cached_vhosts = {}
|
|
local timeout = 60
|
|
|
|
-- Function for querying the database for saved vhost entries
|
|
function query_vhosts(r)
|
|
local host = r.hostname
|
|
if not cached_vhosts[host] or (cached_vhosts[host] and cached_vhosts[host].updated < os.time() - timeout) then
|
|
local db,err = ap.dbopen(r,"mod_dbd")
|
|
local _host = db:escape(r,host)
|
|
local res, err = db:query(r, ("SELECT `destination` FROM `vhosts` WHERE `hostname` = '%s' LIMIT 1"):format(_host) )
|
|
if res and #res == 1 then
|
|
cached_vhosts[host] = { updated = os.time(), destination = res[1][1] }
|
|
else
|
|
cached_vhosts[host] = { updated = os.time(), destination = nil } -- don't re-query whenever there's no result, wait a while.
|
|
end
|
|
db:close()
|
|
end
|
|
if cached_vhosts[host] then
|
|
return cached_vhosts[host].destination
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
function mass_vhost(r)
|
|
-- Check whether the hostname is in our database
|
|
local destination = query_vhosts(r)
|
|
if destination then
|
|
-- If found, rewrite and change document root
|
|
local filename = r.filename:sub(r.document_root:len()+1)
|
|
r.filename = destination .. filename
|
|
ap.set_document_root(r,destination)
|
|
return apache2.OK
|
|
end
|
|
return apache2.DECLINED
|
|
end
|
|
</highlight>
|
|
<!-- END EXAMPLE CODE -->
|
|
|
|
<p>
|
|
|
|
</p>
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section id="basic_auth"><title>Example 3: A basic authorization hook</title>
|
|
<p>
|
|
With the authorization hooks, you can add custom auth phases to your request
|
|
processing, allowing you to either add new requirements that were not previously
|
|
supported by httpd, or tweaking existing ones to accommodate your needs.
|
|
</p>
|
|
<highlight language="config">
|
|
LuaHookAuthChecker /path/too/foo.lua check_auth
|
|
</highlight>
|
|
|
|
<!-- BEGIN EXAMPLE CODE -->
|
|
<highlight language="lua">
|
|
--[[
|
|
A simple authentication hook that checks a table containing usernames and
|
|
passwords of two accounts.
|
|
]]--
|
|
local accounts = {
|
|
bob = 'somePassword',
|
|
jane = 'Iloveponies'
|
|
}
|
|
|
|
-- Function for parsing the Authorization header into a username and a password
|
|
function parse_auth(str)
|
|
local user,pass = nil, nil
|
|
if str and str:len() > 0 then
|
|
str = apache2.base64_decode(auth):sub(7));
|
|
user, pass = auth:match("([^:]+)%:([^:]+)")
|
|
end
|
|
return user, pass
|
|
end
|
|
|
|
-- The authentication hook
|
|
function check_auth(r)
|
|
local user, pass = parse_auth(r.headers_in['Authorization'])
|
|
local authenticated = false
|
|
if user and pass then
|
|
if accounts[user] and accounts[user] == pass then
|
|
authenticated = true
|
|
r.user = user
|
|
end
|
|
end
|
|
r.headers_out["WWW-Authenticate"] = 'Basic realm="Super secret zone"'
|
|
if not authenticated then
|
|
return 401
|
|
else
|
|
return apache2.OK
|
|
end
|
|
end
|
|
</highlight>
|
|
<!-- END EXAMPLE CODE -->
|
|
|
|
|
|
<!-- BEGIN EXAMPLE CODE -->
|
|
<highlight language="lua">
|
|
--[[
|
|
An advanced authentication checker with a database backend,
|
|
caching account entries for 1 minute
|
|
]]--
|
|
|
|
local timeout = 60 -- Set account info to be refreshed every minute
|
|
local accounts = {}
|
|
|
|
-- Function for parsing the Authorization header into a username and a password
|
|
function parse_auth(str)
|
|
local user,pass = nil, nil
|
|
if str and str:len() > 0 then
|
|
str = apache2.base64_decode(auth):sub(7));
|
|
user, pass = auth:match("([^:]+)%:([^:]+)")
|
|
end
|
|
return user, pass
|
|
end
|
|
|
|
-- Function for querying the database for the account's password (stored as a salted SHA-1 hash)
|
|
function fetch_password(user)
|
|
if not accounts[user] or (accounts[user] and accounts[user].updated < os.time() - timeout) then
|
|
local db = apache2.dbopen(r, "mod_dbd")
|
|
local usr = db:escape(user)
|
|
local res, err = db:query( ("SELECT `password` FROM `accounts` WHERE `user` = '%s' LIMIT 1"):format(usr) )
|
|
if res and #res == 1 then
|
|
accounts[user] = { updated = os.time(), password = res[1][1] }
|
|
else
|
|
accounts[user] = nil
|
|
end
|
|
db:close()
|
|
end
|
|
if accounts[user] then
|
|
return accounts[user].password
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
-- The authentication hook
|
|
function check_auth(r)
|
|
local user, pass = parse_auth(r.headers_in['Authorization'])
|
|
local authenticated = false
|
|
if user and pass then
|
|
pass = apache2.sha1("addSomeSalt" .. pass)
|
|
local stored_pass = fetch_password(user)
|
|
if stored_pass and pass == stored_pass then
|
|
authenticated = true
|
|
r.user = user
|
|
end
|
|
end
|
|
r.headers_out["WWW-Authenticate"] = 'Basic realm="Super secret zone"'
|
|
if not authenticated then
|
|
return 401
|
|
else
|
|
return apache2.OK
|
|
end
|
|
end
|
|
</highlight>
|
|
<!-- END EXAMPLE CODE -->
|
|
|
|
|
|
</section>
|
|
|
|
<section id="authz"><title>Example 4: Authorization using LuaAuthzProvider</title>
|
|
<p>
|
|
If you require even more advanced control over your authorization phases,
|
|
you can add custom authz providers to help you manage your server. The
|
|
example below shows you how you can split a single htpasswd file into
|
|
groups with different permissions:
|
|
</p>
|
|
<highlight language="config">
|
|
LuaAuthzProvider rights /path/to/lua/script.lua rights_handler
|
|
<Directory "/www/private">
|
|
Require rights member
|
|
</Directory>
|
|
<Directory "/www/admin">
|
|
Require rights admin
|
|
</Directory>
|
|
</highlight>
|
|
|
|
<highlight language="lua">
|
|
--[[
|
|
This script has two user groups; members and admins, and whichever
|
|
is referred to by the "Require rights" directive is checked to see
|
|
if the authenticated user belongs to this group.
|
|
]]--
|
|
|
|
local members = { "rbowen", "humbedooh", "igalic", "covener" }
|
|
local admins = { "humbedooh" }
|
|
|
|
function rights_handler(r, what)
|
|
if r.user == nil then
|
|
return apache2.AUTHZ_AUTHZ_DENIED_NO_USER
|
|
end
|
|
if what == "member" then
|
|
for k, v in pairs(members) do
|
|
if r.user == v then
|
|
return apache2.AUTHZ_GRANTED
|
|
end
|
|
end
|
|
elseif what == "admin" then
|
|
for k, v in pairs(admins) do
|
|
if r.user == v then
|
|
return apache2.AUTHZ_GRANTED
|
|
end
|
|
end
|
|
end
|
|
return apache2.AUTHZ_DENIED
|
|
end
|
|
</highlight>
|
|
</section>
|
|
|
|
|
|
<section id="loadbalancing"><title>Example 5: A rudimentary load balancer</title>
|
|
<p>
|
|
This is an example of how you can create a load balancing mechanism.
|
|
In this example, we will be setting/getting the number of requests served
|
|
by each backend using IVM variables, and preferring the backend with least
|
|
requests served in total:
|
|
</p>
|
|
<highlight language="config">
|
|
LuaHookTranslateName /path/to/script.lua proxy_handler
|
|
</highlight>
|
|
|
|
<highlight language="lua">
|
|
--[[
|
|
This script uses a basic IVM table to determine where to
|
|
send the request.
|
|
]]--
|
|
|
|
local backends = {
|
|
"http://backend1.foo.com/",
|
|
"http://backend2.foo.com/",
|
|
"http://backend3.foo.com/"
|
|
}
|
|
|
|
function pick_backend(r)
|
|
local chosen_backend = 1 -- default to backend1
|
|
local lowest_count = nil
|
|
for i = 1, #backends, 1 do -- Loop through all backends
|
|
local count = r:ivm_get("proxy_request_count_" .. i)
|
|
if not count then -- If this backend hasn't been used at all, prefer it
|
|
chosen_backend = i
|
|
lowest_count = 0
|
|
break
|
|
end
|
|
if not lowest_count or lowest_count > count then -- If this backend has had less requests, pick it for now
|
|
chosen_backend = i
|
|
lowest_count = count
|
|
end
|
|
end
|
|
lowest_count = lowest_count + 1
|
|
r:ivm_set("proxy_request_count_" .. chosen_backend, lowest_count)
|
|
return chosen_backend
|
|
end
|
|
|
|
function proxy_handler(r)
|
|
local backend = pick_backend(r) -- Pick a backend based on no. of requests served
|
|
r.handler = "proxy-server"
|
|
r.proxyreq = apache2.PROXYREQ_REVERSE
|
|
r.filename = "proxy:" .. backends[backend] .. r.uri
|
|
return apache2.DECLINED -- let the proxy handler do this instead
|
|
end
|
|
</highlight>
|
|
</section>
|
|
|
|
<section id="map_handler"><title>Example 6: Overlays using LuaMapHandler</title>
|
|
<p>
|
|
Coming soon!
|
|
</p>
|
|
<highlight language="config">
|
|
LuaMapHandler ^/portal/([a-z]+)/ /path/to/lua/script.lua handle_$1
|
|
</highlight>
|
|
</section>
|
|
|
|
<section id="mod_status_lua"><title>Example 6: Basic Lua scripts</title>
|
|
<p>
|
|
Also coming soon
|
|
</p>
|
|
</section>
|
|
|
|
|
|
</manualpage>
|