Fullmoon是基於RedBean的快速而簡約的Web框架,該框架是一種便攜式,單文件分發的Web服務器。
開發和分發所需的一切都在一個沒有外部依賴項的單個文件中,並且在Windows,Linux或MacOS上包裝了RedBean運行後。以下是全月應用程序的完整示例:
local fm = require " fullmoon "
fm . setTemplate ( " hello " , " Hello, {%& name %} " )
fm . setRoute ( " /hello/:name " , function ( r )
return fm . serveContent ( " hello " , { name = r . params . name })
end )
fm . run ()
用RedBean打包後,可以使用./redbean.com
啟動它,該服務器啟動了將“ Hello,World”返回到http(S)請求發送到http:// localhost:8080/hello/hello/world的服務器。
RedBean是一款具有獨特和強大質量的單文件分發跨平台Web服務器。儘管有幾個基於LUA的Web框架(Lapis,Lor,Sailor,Pegasus等),但它們都沒有與Redbean集成(儘管有實驗框架ANPAN)。
Fullmoon是一個輕巧,簡約的Web框架,它是從展示RedBean提供的所有功能的角度來寫入的,它通過以最簡單和最有效的方式擴展和增強它們來擴展它們。它運行迅速,並帶有包括電池(路線,模板,JSON Generation等)。
Fullmoon遵循LUA哲學,並提供了一組最少的工具,可以根據需要組合併用作建立基礎的基礎。
fork
, socket
,共享內存等通過運行以下命令下載RedBean的副本(如果在Windows上運行這些命令,則跳過第二個命令):
curl -o redbean.com https://redbean.dev/redbean-2.2.com
chmod +x redbean.com
最新版本號可以通過以下請求檢索:
curl https://redbean.dev/latest.txt
另一個選擇是通過遵循源構建的說明來構建RedBean。
fullmoon.lua
到.lua/
目錄.init.lua
的文件(例如,描述中顯示的LUA代碼)。另一個選項是將應用程序代碼放入單獨的文件(例如.lua/myapp.lua
)中,並添加require "myapp"
到.init.lua
。這就是所有包含的示例的方式。
zip redbean.com .init.lua .lua/fullmoon.lua
如果將應用程序代碼存儲在單獨的LUA文件中,如上所述,請確保將其放在.lua/
Directory中,並將其放在該文件中。
./redbean.com
如果此命令是在Linux上執行的,並引發了有關未找到解釋器的錯誤,則應通過運行以下命令來修復該命令(儘管請注意,它可能無法在系統重新啟動時倖存下來):
sudo sh -c " echo ':APE:M::MZqFpD::/bin/sh:' >/proc/sys/fs/binfmt_misc/register "
如果此命令在使用RedBean 2.x時會在WSL或葡萄酒上產生令人困惑的錯誤,則可以通過禁用BINFMT_MISC來確定它們:
sudo sh -c ' echo -1 >/proc/sys/fs/binfmt_misc/status '
啟動指向http:// localhost:8080/hello/world的瀏覽器,它應該返回“ Hello,World”(假設應用程序使用了引言中顯示的代碼或使用段部分中的代碼)。
最簡單的示例需要(1)加載模塊,(2)配置一個路線,(3)運行應用程序:
local fm = require " fullmoon " -- (1)
fm . setRoute ( " /hello " , function ( r ) return " Hello, world " end ) -- (2)
fm . run () -- (3)
此應用程序對返回的“ Hello,World”內容(和200個狀態代碼)的任何請求/hello
URL響應,並使用所有其他請求的404狀態代碼響應。
setRoute(route[, action])
:註冊路線。如果route
是字符串,則將其用作路由表達式,以將請求路徑與之比較。如果是一個表,則其元素是用作路由的字符串,其哈希值是對路由相反的條件。如果第二個參數是一個函數,則如果滿足所有條件,則執行它。如果是字符串,則將其用作路由表達式,並將請求處理好像在指定的路由上發送(充當內部重定向)一樣。如果不滿足任何條件,則將檢查下一個路線。路由表達式可以具有多個參數和可選零件。操作處理程序接受請求表,該表提供了對請求和路由參數的訪問,以及標題,cookie和會話。
setTemplate(name, template[, parameters])
:註冊一個帶有指定名稱或目錄中的模板集的模板。如果template
是字符串,則將其編譯到模板處理程序中。如果是一個函數,則在請求模板的渲染時將其存儲並調用。如果是表,則其第一個元素是模板或函數,其餘的用作選項。例如,將ContentType
指定為一個選項之一,為生成的內容設置了Content-Type
標題。默認情況下提供了幾個模板( 500
, json
等),並且可以被覆蓋。 parameters
是一個表,具有存儲為名稱/值對的模板參數(在模板中稱為變量)。
serveResponse(status[, headers][, body])
:使用提供的status
, headers
和body
值發送HTTP響應。 headers
是一個可選的表,填充了HTTP標頭名稱/值對。如果提供,這組標頭將在處理相同請求期間更早設置的所有其他標頭。標題名稱是不敏感的,但是提供帶破折號的標題名稱的別名是案例敏感的: {ContentType = "foo"}
是{["Content-Type"] = "foo"}
的替代形式。 body
是一個可選的字符串。
serveContent(name, parameters)
:使用提供的參數呈現模板。 name
是一個字符串,該字符串名稱模板(如setTemplate
調用設置),並且parameters
是一個表格,該表具有存儲為名稱/值對的模板參數(在模板中稱為變量)。
run([options])
:使用配置的路由運行服務器。默認情況下,服務器在LocalHost和port 8080上聽。可以通過在options
表中設置addr
和port
值來更改這些值。
運行示例需要在.init.lua
文件中包含一個require
語句,該文件將模塊加載每個示例代碼,因此對於showcase.lua
, .init.lua
中實現的示例示例,包括以下內容:
-- this is the content of .init.lua
require " showcase "
-- this loads `showcase` module from `.lua/showcase.lua` file,
-- which also loads its `fullmoon` dependency from `.lua/fullmoon.lua`
展示示例展示了幾個全月功能:
serveAsset
)serveRedirect
)需要將以下文件添加到RedBean可執行/存檔:
.init.lua-需要“展示” .lua/fullmoon.lua .lua/showcase.lua
TechEmpower示例使用Fullmoon和內存中的SQLITE數據庫實現了Web框架基準測試的各種測試類型。
此示例演示了幾個滿月/redbean功能:
需要將以下文件添加到RedBean可執行/存檔:
.init.lua-要求“ TechBench” .lua/fullmoon.lua .lua/techbench.lua
HTMX板示例演示了一個簡單的應用程序,該應用程序生成了使用HTMX庫傳遞給客戶端的HTML片段。
此示例演示了幾個滿月/redbean功能:
需要將以下文件添加到RedBean可執行/存檔:
.init.lua-要求“ htmxboard” .lua/fullmoon.lua .lua/htmxboard.lua 資產/樣式 tmpl/* - 示例/htmxboard/tmpl目錄的所有文件
注意1:由於所有數據都存儲在內存中,因此此示例是在單次處理模式下執行的。
注2:此示例從外部資源中檢索HTMX,Hyperscript和可排序的庫,但是這些庫也可以存儲為本地資產,從而提供了完全自給自足的便攜式分發包。
HTMX SSE示例演示了一種可以將服務器式事件(SSE)傳輸到客戶端的方法(使用HTMX庫及其SSE擴展名顯示結果)。
此示例演示了幾個滿月/redbean功能:
streamContent
)需要將以下文件添加到RedBean可執行/存檔:
.init.lua-要求“ htmxsse” .lua/fullmoon.lua .lua/htmxse.lua
每個滿月應用程序都遵循相同的基本流,並具有五個主要組成部分:
讓我們看一下從請求路由開始的每個組件。
滿月使用相同的過程處理每個HTTP請求:
false
或nil
從操作處理程序中返回(並繼續該過程),則可以提供響應(否則)通常,路由定義將請求URL(以及一組條件)綁定到操作處理程序(這是常規LUA函數)。每個條件均以與路由定義相匹配的每個URL的隨機順序檢查。一旦任何條件失敗,路由處理就會中止,並在一個例外檢查下一個路線:任何條件都可以設置otherwise
值,該值觸發了使用指定的狀態代碼的響應。
如果沒有路由與請求匹配,則會觸發默認的404處理,可以通過註冊自定義404模板( fm.setTemplate("404", "My 404 page...")
)來自定義。
每條路線都採用完全匹配的路徑,因此路由"/hello"
匹配/hello
和不匹配/hell
, /hello-world
或/hello/world
的請求。下面的路線以“你好,世界!”做出回應。對於所有針對/hello
路徑的請求,並返回所有其他請求的404。
fm . setRoute ( " /hello " , function ( r ) return " Hello, World! " end )
要匹配/hello
只是其中的一部分的路徑,可以使用可選參數和SPLAT。
除固定路線外,任何路徑還可以包括參數的佔位符,這些參數由A :
立即識別為參數名稱:
fm . setRoute ( " /hello/:name " ,
function ( r ) return " Hello, " .. ( r . params . name ) end )
每個參數都匹配一個或多個字符/
的字符,因此"/hello/:name"
匹配/hello/alice
/hello/bob
, /hello/123
hello/hello/hello/ /hello/bob/and/alice
(由於不匹配的前向斜線)或/hello/
(因為要匹配的片段的長度為零)。
參數名稱只能包括字母數字字符和_
。
可以使用請求表及其params
表訪問參數,以便可以使用r.params.name
從早期示例中獲取name
參數的值。
任何指定的路由片段或參數都可以通過將其包裹到括號中來聲明為可選:
fm . setRoute ( " /hello(/:name) " ,
function ( r ) return " Hello, " .. ( r . params . name or " World! " ) end )
在上面的示例中, /hello
和/hello/Bob
都被接受,但 /Hello /not /hello/
,因為Tailding Slash是可選片段的一部分,並且:name
仍然期望一個或多個字符。
任何無與倫比的可選參數都會false
以為其值,因此在上面的“你好,世界!”的情況下。返回/hello
請求URL。
可以指定多個可選參數,並且可以嵌套可選的片段,因此"/posts(/:pid/comments(/:cid))"
和"/posts(/:pid)/comments(/:cid)"
是有效的路由值。
還有另一種稱為splat的參數,它寫為*
,匹配零或更多字符,包括前向斜杠( /
)。 SPLAT還存儲在splat
名稱下的params
表中。例如,路由"/download/*"
匹配/download/my/file.zip
和splat獲取my/file.zip
的值。如果在同一路線中需要多個夾子,則可以為類似於其他參數的名稱分配: /download/*path/*fname.zip
(雖然可以使用/download/*path/:fname.zip
可以實現相同的結果,作為第一個SPLAT捕獲除文件名之外的所有路徑部分)。
所有參數(包括SPLAT)都可以出現在路徑的任何部分中,並且可以被其他文本包圍,這些文本需要完全匹配。這意味著路由"/download/*/:name.:ext"
匹配/download/my/path/file.zip
和params.name
get file
, params.ext
get zip
和params.splat
獲取my/path
值。
使用SPLAT的另一個原因是允許多個路由具有相同的路徑在系統中註冊。當前的實現覆蓋具有相同名稱的路由,並避免可以使用指定的SPLAT來創建唯一的路徑。例如,
fm . setRoute ( " /*dosomething1 " , function ( r ) return " something 1 " end )
fm . setRoute ( " /*dosomething2 " , function ( r ) return " something 2 " end )
當需要在操作處理程序中檢查一組條件時,可以在情況下使用此情況,雖然可以將這兩種路線組合到一條路線時,有時會更乾淨以使它們分開。
參數的默認值是一個或多個長度的所有字符( / /
)。要指定不同的有效字符集,可以在變量名稱的末尾添加;例如,使用:id[%d]
而不是:id
更改參數以匹配數字。
fm . setRoute ( " /hello(/:id[%d]) " ,
function ( r ) return " Hello, " .. ( r . params . id or " World! " ) end )
支持以下LUA字符類: %w
, %d
, %a
, %l
, %u
和%x
;任何標點符號(包括%
和]
)也可以通過%
逃脫。不支持負類(用LUA AS %W
編寫),但支持不合時宜的語法,因此[^%d]
匹配一個不包含任何數字的參數。
請注意,重複的數量無法更改(因此:id[%d]*
不是接受零或摩爾數字的有效方法),因為僅允許集合,並且值仍然接受一個或多個字符。如果需要更靈活地描述可接受的格式,則可以使用自定義驗證器來擴展匹配邏輯。
可以使用傳遞給每個操作處理程序的request
表中的params
表以與路徑參數表的方式訪問查詢和表單參數。請注意,如果參數和查詢/表單名稱之間存在衝突,則參數名稱優先。
有一種特殊情況可能會導致返回的表而不是字符串值:如果查詢/表單參數名稱在[]
中結束,則所有匹配結果(一個或多個)作為表返回。例如,對於查詢字符串a[]=10&a[]&a[]=12&a[]=
params["a[]"]
的值為{10, false, 12, ""}
。
由於編寫這些參數名稱可能需要幾個括號, params.a
可以用作params["a[]"]
的快捷方式,兩種表單都返回同一表。
當要求時,也可以處理多個參數,並且可以使用參數表以使用params
表以相同的方式訪問。例如,可以使用params.simple
和params.more
從帶有multipart/form-data
Content類型的消息中檢索具有名稱simple
且more
的參數。
由於某些Multipart內容可能包括這些標題內的其他標頭和參數,因此可以使用params
表的multipart
字段訪問它們:
fm . setRoute ({ " /hello " , simple = " value " }, function ( r )
return " Show " .. r . params . simple .. " " .. r . params . multipart . more . data )
end )
multipart
表包含多部分消息的所有部分(因此可以在使用ipairs
上迭代),但它還允許使用參數名稱( params.multipart.more
)訪問。每個元素也是一個包含以下字段的表:
nil
如果不是。nil
如果不是。該多部分處理會消耗任何多部分子類型並處理遞歸多部分消息。它還將匹配start
參數匹配的Content-ID
值插入到第一個位置。
儘管所有較早的示例顯示了一條路線,但在實際應用中很少情況。當存在多個路線時,始終按照註冊的順序進行評估。
當一個setRoute
呼叫具有相同的條件集並共享相同的操作處理程序時,也可以設置多個路線:
fm . setRoute ({ " /route1 " , " /route2 " }, handler )
這相當於兩個單獨設置每個路由的兩個呼叫:
fm . setRoute ( " /route1 " , handler )
fm . setRoute ( " /route2 " , handler )
鑑於按照設置的順序評估路線,需要首先設置更多選擇性的路線,否則它們可能沒有機會得到評估:
fm . setRoute ( " /user/bob " , handlerBob )
fm . setRoute ( " /user/:name " , handlerName )
如果以相反的順序設置路線,則只要"/user/:name"
操作處理程序返回一些非false
結果,就永遠不會檢查/user/bob
。
如前所述,如果均無匹配的路線,則返回具有404狀態代碼的響應。在某些情況下,這是不可取的;例如,當應用程序包含LUA腳本以處理未明確註冊為路由的請求時。在那些情況下,可以添加一條接收路線,以實現默認的redbean處理(SPLAT參數的名稱僅用於將此路線限制在其他地方可以使用的其他/*
路由上):
fm . setRoute ( " /*catchall " , fm . servePath )
每個路由都可以提供一個可選名稱,該名稱在需要基於特定參數值生成時引用該路由時很有用。提供的makePath
功能接受路由名稱或路由URL本身以及參數表,並返回帶有填充參數佔位符的路徑:
fm . setRoute ( " /user/:name " , handlerName )
fm . setRoute ({ " /post/:id " , routeName = " post " }, handlerPost )
fm . makePath ( " /user/:name " , { name = " Bob " }) -- > /user/Bob
fm . makePath ( " /post/:id " , { id = 123 }) -- > /post/123
fm . makePath ( " post " , { id = 123 }) -- > /post/123, same as the previous one
如果兩個路由使用相同的名稱,則該名稱與上次註冊的路由相關聯,但仍然存在這兩個路線。
路由名稱也可以與僅用於URL生成的外部/靜態路由一起使用。
如果路由僅用於路徑生成,那麼它甚至不需要一個路由處理程序:
fm . setRoute ({ " https://youtu.be/:videoid " , routeName = " youtube " })
fm . makePath ( " youtube " , { videoid = " abc " }) -- > https://youtu.be/abc
在路線匹配過程中,沒有任何動作處理程序的路線會跳過。
內部路由允許將一組URL重定向到另一組URL。目標URL可以指向靜態資源或.lua
腳本。例如,如果需要將一個位置/new-blog/
請求重定向到另一個/blog/
,則只要存在目標資源:
fm . setRoute ( " /blog/* " , " /new-blog/* " )
只要存在/new-blog/post1
Asset,此路線接受/blog/post1
的請求,並提供/new-blog/post1
POST1作為其響應。如果資產不存在,則將檢查下一條路線。同樣,使用fm.setRoute("/static/*", "/*")
導致對/static/help.txt
的請求提供資源/help.txt
。
這兩個URL都可以包含填充的參數,如果解決了:
fm . setRoute ( " /blog/:file " , " /new-blog/:file.html " ) -- <<-- serve "nice" URLs
fm . setRoute ( " /new-blog/:file.html " , fm . serveAsset ) -- <<-- serve original URLs
此示例解決了服務“ HTML”版本的“ NICE” URL。請注意,這不會通過返回3xx
狀態代碼來觸發客戶端重定向,而是內部處理重新路由。還要注意,要使用“原始” URL需要第二個規則,因為它們不是由第一個規則處理的,因為如果請求是/blog/mylink.html
,則重定向的URL為/new-blog/mylink.html.html
,不可能存在,因此該路線被跳過並檢查下一個路線。如果還需要處理路徑分離器,則可以使用*path
代替:file
,因為*
允許路徑分離器。
如果應用程序需要根據請求屬性的特定值(例如一種方法)執行不同的函數,則該庫提供了兩個主要選項:(1)檢查屬性值一個操作處理程序(例如,使用request.method == "GET"
檢查)和(2)添加一個條件,該條件過濾了請求,以便僅使用指定屬性值到達操作處理程序的請求。本節更詳細地描述了第二個選項。
默認情況下,每條註冊的路由均響應所有HTTP方法(獲取,put,發布等),但是可以配置每個路由以僅響應特定的HTTP方法:
fm . setRoute ( fm . GET " /hello(/:name) " ,
function ( r ) return " Hello, " .. ( r . params . name or " World! " ) end )
在這種情況下,語法fm.GET"/hello(/:name)"
配置僅接受GET
請求的路由。該語法等同於通過路由和任何其他過濾條件的表格通過表格:
fm . setRoute ({ " /hello(/:name) " , method = " GET " },
function ( r ) return " Hello, " .. ( r . params . name or " World! " ) end )
如果需要指定多個方法,則可以傳遞具有方法列表的表,而不是一個字符串值:
fm . setRoute ({ " /hello(/:name) " , method = { " GET " , " POST " }},
function ( r ) return " Hello, " .. ( r . params . name or " World! " ) end )
每條允許GET
請求的路線(隱式)允許HEAD
請求,並且該請求是通過返回所有標題而無需發送身體本身來處理的。如果由於某種原因不需要此隱式處理,則將HEAD = false
添加到方法表(如method = {"GET", "POST", HEAD = false}
)。
請注意,使用非匹配方法的請求不會被拒絕,而是通過其他路線檢查並觸發如果不匹配的404狀態代碼(一個例外)。
除了method
外,還可以使用host
, clientAddr
, serverAddr
, scheme
,請求標頭和參數應用其他條件。例如,指定name = "Bob"
作為條件之一,可確保為“ bob”的name
參數的值,以便將其調用為“ bob”。
可以使用標題名稱作為密鑰檢查任何請求標頭,因此,如果Content-Type
標頭的值為multipart/form-data
則可以滿足ContentType = "multipart/form-data"
。請注意,標題值可能包括其他元素(作為Content-Type
值的一部分)的其他元素(邊界或字符集),並且僅比較實際的媒體類型。
由於標題,參數和屬性的名稱可以重疊,因此按以下順序檢查它們:
ContentType
,method
, port
, host
等),還首先檢查了Host
標頭(儘管是一個單詞),因此,基於標題Host
引用Host
過濾器,同時根據屬性host
引用host
過濾器。
字符串值不是在條件路由中使用的唯一值。如果一個以上的值是可以接受的,則通過表可以提供可接受的值列表。例如,如果Bob
和Alice
是可接受的值,則name = {Bob = true, Alice = true}
將其表示為條件。
表中傳遞的兩個特殊值允許應用正則驗證或模式驗證:
regex
:接受具有正則表達式的字符串。例如, name = {regex = "^(Bob|Alice)$"}
結果與本節前面顯示的哈希檢查相同pattern
:接受帶有LUA模式表達式的字符串。例如, name = {pattern = "^%u%l+$"}
接受以大寫字符開始的值,然後是一個或多個小寫字符。這兩項檢查可以與表格存在檢查結合: name = {Bob = true, regex = "^Alice$"}
接受Bob
和Alice
值。如果第一個表格檢查失敗,則返回regex
則pattern
的結果。
自定義驗證器的最後類型是函數。提供的功能接收到驗證的值,其結果被評估為false
或true
。例如,通過id = tonumber
確保id
值為數字。作為另一個示例, clientAddr = fm.isLoopbackIp
確保客戶端地址是環回IP地址。
fm . setRoute ({ " /local-only " , clientAddr = fm . isLoopbackIp },
function ( r ) return " Local content " end )
由於可以動態生成驗證器函數,因此這也有效:
local function isLessThan ( n )
return function ( l ) return tonumber ( l ) < n end
end
fm . setRoute ( fm . POST { " /upload " , ContentLength = isLessThan ( 100000 )},
function ( r ) ... handle the upload ... end )
重要的是要記住,驗證器函數實際上返回了在應用支票請求期間調用的函數。在上一個示例中,返回的函數接受標頭值,並將其與創建期間通過的限制進行比較。
在某些情況下,不滿足條件是一個足夠的理由,可以在不檢查其他路線的情況下返回對客戶的響應。在這樣的情況下,將otherwise
設置為一個數字或功能將返回具有指定狀態的響應或函數結果:
local function isLessThan ( n )
return function ( l ) return tonumber ( l ) < n end
end
fm . setRoute ( fm . POST { " /upload " ,
ContentLength = isLessThan ( 100000 ), otherwise = 413
}, function ( r ) ... handle the upload ... end )
在此示例中,路由引擎匹配路由,然後驗證兩個條件,將方法值與POST
和Content-Length
標頭的值與isLessThan
函數的結果進行了比較。如果其中一個條件不匹配,則在其餘響應的其餘部分中返回了由otherwise
值指定的狀態代碼。
如果otherwise
條件只需要應用於ContentLength
檢查,則可以將otherwise
值以及驗證器函數以及與ContentLength
Check關聯的表移動:
fm . setRoute ( fm . POST { " /upload " ,
ContentLength = { isLessThan ( 100000 ), otherwise = 413 }
}, function ( r ) ... handle the upload ... end )
最後兩個示例之間的區別在於,在此示例中,只有ContentLength
檢查失敗觸發了413響應(並且所有其他方法都落在其他路線上),而在上一個方法中, method
和ContentLength
檢查失敗觸發了相同的413響應。
請注意,當檢查值為nil
,對錶的檢查被認為是有效的,並且該路由被接受。例如,如果params.name
的值為nil
,則針對字符串( name = "Bo"
)的可選參數的檢查失敗,但是如果對錶進行了相同的檢查( name = {Bo=true, Mo=true}
),包括正則撥號/模式檢查。如果這不是可取的,則可以自定義驗證器函數明確檢查預期值。
考慮以下示例:
fm . setRoute ({ " /hello(/:name) " ,
method = { " GET " , " POST " , otherwise = 405 }},
function ( r ) return " Hello, " .. ( r . params . name or " World! " ) end )
在這種情況下,如果使用PUT
方法訪問此端點,則不再檢查其他路由(因為不滿足method
條件),而是返回405狀態代碼,則以otherwise
的價值配置為配置。如其他地方的記錄,此路線也接受了HEAD
請求(即使未列出),因為接受了GET
請求。
當返回405(不良方法)狀態代碼並且未設置Allow
頭時,將其設置為路由允許的方法列表。在上面的情況下,它設置為GET, POST, HEAD, OPTIONS
值,因為這些方法是此配置允許的方法。如果otherwise
值為函數(而不是數字),則返回適當的結果並設置Allow
標頭是此功能的責任。
otherwise
值也可以將其設置為一個函數,該函數提供的靈活性不僅僅是設置狀態代碼。例如,設置otherwise = fm.serveResponse(413, "Payload Too Large")
會觸發使用指定的狀態代碼和消息的響應。
處理表單驗證通常需要指定相同參數的一組條件,並且在不滿足條件時可能需要返回的自定義錯誤消息,並且由makeValidator
函數返回的特殊驗證器提供:
local validator = fm . makeValidator {
{ " name " , minlen = 5 , maxlen = 64 , msg = " Invalid %s format " },
{ " password " , minlen = 5 , maxlen = 128 , msg = " Invalid %s format " },
}
fm . setRoute ( fm . POST { " /signin " , _ = validator }, function ( r )
-- do something useful with name and password
return fm . serveRedirect ( 307 , " / " )
end )
在此示例中,將驗證器配置為檢查兩個參數:“名稱”和“密碼” - 最小長度和最大長度,並在其中一個參數失敗時返回消息。
由於失敗的檢查會導致要跳過的路由,因此提供otherwise
值允許返回錯誤作為響應的一部分:
local validator = fm . makeValidator {
{ " name " , minlen = 5 , maxlen = 64 , msg = " Invalid %s format " },
{ " password " , minlen = 5 , maxlen = 128 , msg = " Invalid %s format " },
otherwise = function ( error )
return fm . serveContent ( " signin " , { error = error })
end ,
}
在這種情況下, otherwise
處理程序會收到錯誤消息(或帶有消息(通過傳遞下面涵蓋的all
選項)的表格),然後可以作為模板參數提供並返回給客戶端。
另一個選項是直接在操作處理程序中調用驗證器函數並返回其結果:
local validator = fm . makeValidator {
{ " name " , minlen = 5 , maxlen = 64 , msg = " Invalid %s format " },
{ " password " , minlen = 5 , maxlen = 128 , msg = " Invalid %s format " },
}
fm . setRoute ( fm . POST { " /signin " }, function ( r )
local valid , error = validator ( r . params )
if valid then
return fm . serveRedirect ( " / " ) -- status code is optional
else
return fm . serveContent ( " signin " , { error = error })
end
end )
在此示例中,驗證器被直接調用,並通過所有參數值傳遞一個表( r.params
),以允許驗證器函數根據指定的規則檢查值。
然後,驗證器函數true
返回到信號成功或nil, error
以發出未能檢查其中一個規則的錯誤。如果腳本需要立即返回錯誤,則可以將驗證器調用包裝到assert
中:
assert ( validator ( r . params )) -- throw an error if validation fails
return fm . serveRedirect ( 307 , " / " ) -- return redirect in other cases
可用以下驗證器檢查:
minlen
:(整數)檢查字符串的最小長度。maxlen
:(整數)檢查字符串的最大長度。test
:(函數)調用通過一個參數傳遞的函數,並有望返回true
或nil | false [, error]
。oneof
:( value | { table of values to be compared against }
)檢查該參數是否匹配了提供的值之一。pattern
:(字符串)檢查參數是否匹配LUA模式表達式。除支票外,規則還包括選項:
optional
:( bool)在nil
時可選。默認情況下需要所有參數,因此此選項允許在未提供參數時跳過規則。如果參數不是零,則所有規則仍然適用。msg
:(字符串)如果其中一張檢查失敗,則為此添加一個客戶消息,從而覆蓋了單個檢查中的消息。該消息可能包括一個佔位符( %s
),該佔位符將被參數名稱替換。驗證器本身還接受幾個選項,這些選項修改瞭如何返回或處理生成的錯誤:
otherwise
:(函數)設置一個錯誤處理程序,該處理程序在其中一項檢查失敗時被調用。該函數接收由檢查觸發的錯誤。all
:( bool)配置驗證器以返回所有錯誤,而不僅僅是第一個錯誤。默認情況下,只有一個(第一個)錯誤作為字符串返回,因此,如果請求所有錯誤,則將它們作為表返回,每個錯誤是單獨的項目。key
:( bool)將驗證器配置為在哈希表(而不是元素)中的值返回錯誤,其中鍵是參數名稱。這對於將表格傳遞到一個模板中很有用,然後可以顯示errors.name
。名稱和errors.password
錯誤消息在其輸入字段旁邊。Action處理程序會收到針對特定路線過濾的所有傳入的HTTP請求。到目前為止顯示的每個示例都包括一個動作處理程序,該操作人員作為setRoute
方法的第二個參數傳遞。
可以在處理一個請求的過程中執行多個操作處理程序,並且一家處理程序返回將其評估為非false
值的結果,路由處理過程就會結束。從操作處理程序中返回false
或nil
繼續進行處理,該處理允許實施一些適用於多個路線的常見處理(類似於其他框架中使用“前”過濾器進行的操作):
local uroute = " /user/:id "
fm . setRoute ({ uroute .. " /* " , method = { " GET " , " POST " , otherwise = 405 }},
function ( r )
-- retrieve user information based on r.params.id
-- and store in r.user (as one of the options);
-- return error if user is not found
return false -- continue handling
end )
fm . setRoute ( fm . GET ( uroute .. " /view " ), function ( r ) ... end )
fm . setRoute ( fm . GET ( uroute .. " /edit " ), function ( r ) ... end )
fm . setRoute ( fm . POST ( uroute .. " /edit " ), function ( r ) ... end )
在此示例中,第一個路線可以生成三個結果:
method
檢查)不匹配,則返回405狀態代碼。false
,該false繼續處理其他路線,或者未能檢索用戶並返回錯誤。通常,操作處理程序可以返回以下任何值:
true
:這停止了任何進一步的處理,設置了到目前為止已指定的標頭,並返回生成或設置的響應主體。false
或nil
:這停止了當前路線的處理,並繼續前往下一條路線。Content-Type
是基於身體內容(使用原始啟發式)設置的,如果未明確設置。serve*
方法之一的調用):這將執行請求的方法並返回一個空字符串或true
以發出處理結束的信號。true
(並記錄了警告)。 通常,任何導致LUA錯誤的處理都將以服務器錯誤響應(帶有500個狀態代碼)返回客戶端。為了協助本地調試,錯誤消息包括堆棧跟踪,但前提是從loopback或私人IP發送請求(或使用-E
命令行選項啟動RedBean)。
可能希望通過多層函數調用返回特定響應,在這種情況下,錯誤可能是用函數值而不是字符串值觸發的。例如,執行error(fm.serve404)
會導致返回404狀態代碼,該狀態代碼類似於使用return fm.serve404
,但可以在“動作處理程序”(僅來自操作處理程序內部)的函數中執行。
這是一個更複雜的示例,如果未獲取記錄,則返回404狀態代碼(假設有一個帶有字段id
的表test
):
local function AnyOr404(res, err)
if not res then error(err) end
-- serve 404 when no record is returned
if res == db.NONE then error(fm.serve404) end
return res, err
end
fm.setRoute("/", function(r)
local row = AnyOr404(dbm:fetchOne("SELECT id FROM test"))
return row.id
end)
此示例使用serve404
函數,但是也可以使用任何其他服務*方法。
每個操作處理程序都接受一個請求表,其中包含以下屬性:
method
:請求http方法(獲取,發布和其他方法)。host
:請求主機(如果提供)或綁定地址。serverAddr
:偵聽服務器套接字的地址。remoteAddr
:客戶端IP4地址編碼為數字。這考慮了反向代理方案。使用formatIp
函數轉換為代表地址的字符串。scheme
:請求URL方案(如果有)。path
:請求保證以/
開頭的URL路徑。authority
:請求使用方案,主機和端口出席的URL。url
:請求URL作為ASCII字符串,具有編碼非法字符的百分比。body
:請求消息主體(如果存在)或一個空字符串。date
:請求日期作為UNIX時間戳。time
:當前時間為具有0.0001s精度的UNIX時間戳。該請求表還具有多個實用程序功能,以及標題,cookie和會話表,可檢索請求標題,cookie,會話以及響應中包含的標題和cookie的設置以及設置。
同一請求表作為參數作為參數(匹配)的操作處理程序,因此可以用作在這些操作處理程序之間傳遞值的機制,因為所有其他操作處理程序中都可以在一個處理程序中分配為字段的任何值。
headers
表提供了對請求標頭的訪問。例如, r.headers["Content-Type"]
返回Content-Type
標頭的值。這種形式的標頭訪問是不敏感的。還提供了較短的表格( r.headers.ContentType
),但僅適用於註冊的標題,並且對大寫字母敏感。
也可以使用相同的語法設置請求標題。例如, r.headers.MyHeader = "value"
設置MyHeader: value
響應標頭。由於標題設置在操作處理程序處理的末尾,因此也可以通過分配nil
值來刪除標題。
也可以通過逗號分隔的值分配可重複的標頭: r.headers.Allow = "GET, POST"
。
cookies
表提供了對請求cookie的訪問。例如, r.cookies.token
返回token
cookie的值。
cookie也可以使用相同的語法設置。例如, r.cookies.token = "new value"
將token
cookie設置為new value
。如果cookie也需要設置其屬性,則值和屬性需要作為表格傳遞: r.cookies.token = {"new value", secure = true, httponly = true}
。
支持以下cookie屬性:
expires
:將cookie的最大壽命設置為HTTP日期時間戳。可以在RFC1123(String)格式或UNIX TIMESTAMP(秒數)中指定為日期。maxage
:設置秒數,直到Cookie到期。零或負數立即到期。如果設置了expires
和maxage
,則maxage
具有優先級。domain
:設置將要發送cookie的主機。path
:設置請求URL中必須存在的路徑,否則客戶端不會發送cookie標頭。secure
:( bool)請求cookie僅在使用HTTPS:scheme請求時僅發送到服務器。httponly
:(布爾)禁止JavaScript訪問cookie。samesite
:( Strict
, Lax
或None
)控制cookie是否是帶有跨原基請求的餅乾,為跨站點請求偽造攻擊提供了一些保護。請注意,默認設置了httponly
和samesite="Strict"
;可以使用傳遞給運行方法的cookieOptions
提供一組默認值。任何具有表格覆蓋默認值的屬性,因此,如果需要啟用Secure
,請確保還通過httponly
和samesite
選項。
要刪除cookie,請將其值設置為false
:例如, r.cookies.token = false
刪除token
cookie的值。
session
表提供了可用於設置或檢索會話值的會話表的訪問。例如, r.session.counter
返回以前設置的counter
值。也可以使用相同的語法設置會話值。例如, r.session.counter = 2
將counter
值設置為2
。
會話允許存儲嵌套值和其他LUA值。如果需要刪除會話,則可以將其設置為空表格或nil
值。每個會話都有一個應用程序秘密簽名,默認情況下為隨機字符串分配,可以通過設置會話選項更改。
以下功能可作為請求函數(作為請求表中的字段)和庫函數可用:
makePath(route[, parameters])
:通過使用參數表中的值填充其參數來創建從路由名稱或路徑字符串的路徑(提供)。該路徑不需要是URL的路徑組件,也可以是完整的URL。如果可選零件包括未提供的參數,則將刪除。makeUrl([url,] options)
:使用所提供的值和options
表中提供的一組URL參數創建一個URL:方案,用戶,通行證,主機,端口,端口,路徑和片段。 url
參數是可選的;如果未指定url
,則使用當前請求URL。可以提供或刪除任何選項(使用false
作為值)。例如, makeUrl({scheme="https"})
將當前URL的方案設置為https
。escapeHtml(string)
:通過用HTML實體對應物( &><"'
)替換HTML實體( &><"'
)。escapePath(path)
:應用URL編碼( %XX
)逃脫路徑不安全的字符(除-.~_@:!$&'()*+,;=0-9A-Za-z/
formatHttpDateTime(seconds)
:將UNIX TIMESTAMP(以秒為單位)轉換為RFC1123字符串( Mon, 21 Feb 2022 15:37:13 GMT
)。模板提供了一種簡單便捷的方法來返回預定義和參數化的內容,而不是通過一部分生成它。
隨附的模板引擎支持將任意文本與包裹在{% %}
標籤中的LUA語句/表達式混合。模板中的所有代碼都使用常規的LUA語法,因此沒有新的語法要學習。有三種包含一些LUA代碼的方法:
{% statement %}
:用於LUA語句。例如, {% if true then %}Hello{% end %}
渲染Hello
。{%& expression %}
:用於呈現為HTML-SAFE文本的LUA表達式。例如, {%& '2 & 2' %}
渲染2 & 2
。{%= expression %}
:用於呈現AS-IS的LUA表達式(無需逃脫)。例如, {%= 2 + 2 %}
渲染4
。請小心,因為HTML不會使用{%= }
逃脫,因此由於XSS攻擊的潛力,應仔細使用。模板引擎提供了兩個主要功能,可用於模板:
setTemplate(name, text[, parameters])
:註冊帶有提供名稱和文本的模板(並將parameters
用作其默認參數)。在特殊情況下, name
或text
參數可能不是字符串,其中一些情況在“加載模板”部分中涵蓋。 parameters
是一個具有模板參數為名稱/值對的表(在模板中稱為變量)。render(name, parameters)
:使用parameters
表渲染一個註冊的模板以在模板中設置值(在模板中分配給名稱/值的表中的鍵/值)。只有一個帶有給定名稱的模板,因此註冊具有現有名稱的模板替換了此先前註冊的模板。這可能很少需要,但是可以用來覆蓋默認模板。
這是一個示例,使Hello, World!
到輸出緩衝區:
fm . setTemplate ( " hello " , " Hello, {%& title %}! " )
fm . render ( " hello " , { title = " World " })
使用語句語法使用表達式語法或表達式渲染語句是在註冊模板時報告的語法錯誤。函數調用可以與任何一種語法一起使用。
任何模板錯誤(語法或運行時)都包含模板名稱和模板中的行號。例如,調用fm.setTemplate("hello", "Hello, {%& if title then end %}!")
導致hello:1: unexpected symbol near 'if'
表達語法)。
也可以使用相同的setTemplate
功能從文件或目錄加載模板,該功能後來在“加載模板”部分中進行了描述。
有幾個方面值得注意,因為它們可能與其他框架中的模板的處理方式有所不同:
json
和sse
模板。每個模板都接受可以在其渲染邏輯中使用的參數。參數可以通過兩種方式傳遞:(1)在註冊模板時以及(2)渲染模板時。在註冊過程中傳遞參數允許設置如果在渲染過程中未提供參數,則使用默認值。例如,
fm . setTemplate ( " hello " , " Hello, {%& title %}! " , { title = " World " })
fm . render ( " hello " ) -- renders `Hello, World!`
fm . render ( " hello " , { title = " All " }) -- renders `Hello, All!`
nil
或false
值將作為空字符串渲染而不會丟棄任何錯誤,但是在nil
值上的任何操作都可能導致LUA錯誤。例如,做{%& title .. '!' %}
(沒有title
集)會導致attempt to concatenate a nil value (global 'title')
錯誤。
可以將哪些值傳遞給模板沒有限制,因此可以傳遞任何LUA值然後在模板中使用。
除了可以傳遞給模板的值外,還有兩個特殊表可以訪問跨模板值:
vars
:提供對在setTemplateVar
註冊的值的訪問,以及block
:提供對其他模板可以覆蓋的模板片段的訪問。通過setTemplateVar
註冊的任何值都可以通過vars
表從任何模板訪問。在下面的示例中, vars.title
值由較早的setTemplateVar('title', 'World')
呼叫:
fm . setTemplateVar ( ' title ' , ' World ' )
fm . setTemplate ( " hello " , " Hello, {%& vars.title %}! " )
fm . render ( " hello " ) -- renders `Hello, World!`
雖然默認情況下將未定義的值渲染為空字符串(在大多數情況下可能很方便),但仍然存在一些情況,即不允許不允許不確定的值靜靜地處理。在此中,可以設置一個特殊的模板變量( if-nil
)來處理這些情況以丟棄錯誤或記錄消息。例如,以下代碼會引發錯誤,因為missing
值不確定,這會觸發if-nil
處理程序:
fm . setTemplateVar ( ' if-nil ' , function () error " missing value " end )
fm . setTemplate ( " hello " , " Hello, {%& vars.missing %}! " )
fm . render ( " hello " ) -- throws "missing value" error
還可以通過使用render
函數從其他模板中渲染模板,這在每個模板中可用:
fm . setTemplate ( " hello " , " Hello, {%& title %}! " )
fm . setTemplate ( " header " , " <h1>{% render('hello', {title = title}) %}</h1> " )
---- -----------------------------└──────────────────────────────┘----------
fm . render ( " header " , { title = ' World ' }) -- renders `<h1>Hello, World!</h1>`
對於如何從其他模板呈現模板沒有限制,但也沒有進行循環檢查,因此在模板渲染中具有圓形引用(當模板A呈現A模板B時,又一次呈現A)將再次呈現A)導致LUA錯誤。
值得注意的是, render
函數不會返回其呈現的模板的值,而是將其直接放入輸出緩衝區中。
從其他模板中渲染模板的這種能力允許產生任何復雜性的佈局。有兩種方法:
要動態選擇要在渲染時間使用的模板,模板名稱本身可以作為參數傳遞:
fm . setTemplate ( " hello " , " Hello, {%& title %}! " )
fm . setTemplate ( " bye " , " Bye, {%& title %}! " )
fm . setTemplate ( " header " , " <h1>{% render(content, {title = title}) %}</h1> " )
fm . render ( " header " , { title = ' World ' , content = ' hello ' })
此示例呈現<h1>Hello, World!</h1>
或<h1>Bye, World!</h1>
取決於content
參數的值。
使用塊允許定義可以(選項)從其他模板(通常稱為“兒童”或“繼承”模板)覆蓋的模板片段。以下示例演示了這種方法:
fm . setTemplate ( " header " , [[
<h1>
{% function block.greet() %} -- define a (default) block
Hi
{% end %}
{% block.greet() %}, -- render the block
{%& title %}!
</h1>
]] )
fm . setTemplate ( " hello " , [[
{% function block.greet() %} -- overwrite the `header` block (if any)
Hello
{% end %}
{% render('header', {title=title}) %}!
]] )
fm . setTemplate ( " bye " , [[
{% function block.greet() %} -- overwrite the `header` block (if any)
Bye
{% end %}
{% render('header', {title=title}) %}!
]] )
-- normally only one of the three `render` calls is needed,
-- so all three are shown for illustrative purposes only
fm . render ( " hello " , { title = ' World ' }) -- renders <h1>Hello, World!</h1>
fm . render ( " bye " , { title = ' World ' }) -- renders `<h1>Bye, World!</h1>`
fm . render ( " header " , { title = ' World ' }) -- renders `<h1>Hi, World!</h1>`
在此示例中, header
模板變為“佈局”,並用Hi
定義了greet
Block的內容。該塊定義為block
表中的函數,並具有所需的內容。隨後是對block.greet
函數的調用,將其內容包括在模板中。
這很重要,要強調,除了定義一個塊外,還需要在預期渲染的點上從基本/佈局模板調用它。
hello
模板還定義了block.greet
函數,具有不同的內容,然後呈現header
模板。當渲染header
模板時,它使用hello
模板中定義的block.greet
函數的內容。通過這種方式,子模板“重新定義”具有自己的內容的“重新定義” greet
塊,將其插入適當的位置中。
它可以為bye
和header
模板以相同的方式工作。除了在block
表中定義它們以外,這些“塊”功能沒有什麼特別的。
此概念對於任何深度的模板組成都是有用的。例如,讓我們定義一個帶有標頭和帶有動作按鈕的頁腳的模態模板:
fm . setTemplate ( " modal " , [[
<div class="modal">
<div class="modal-title">
{% function block.modal_title() %}
Details
{% end %}
{% block.modal_title() %}
</div>
<div class="modal-content">
{% block.modal_content() %}
</div>
<div class="modal-actions">
{% function block.modal_actions() %}
<button>Cancel</button>
<button>Save</button>
{% end %}
{% block.modal_actions() %}
</div>
</div>
]] )
現在,在呈現模式的模板中,可以覆蓋塊以自定義內容:
fm . setTemplate ( " page " , [[
{% function block.modal_title() %}
Insert photo
{% end %}
{% function block.modal_content() %}
<div class="photo-dropzone">Upload photo here</div>
{% end %}
{% render('modal') %}
]] )
這使易於構建可組合的佈局和組件,例如標題和頁腳,卡片,模態或其他任何需要動態自定義其他模板中段的能力的東西。
這是一個示例來說明嵌套塊如何一起工作:
-- base/layout template
{ % function block . greet () % } -- 1. defines default "greet" block
Hi
{ % end % }
{ % block . greet () % } -- 2. calls "greet" block
-- child template
{ % function block . greet () % } -- 3. defines "greet" block
Hello
{ % end % }
{ % render ( ' base ' ) % } -- 4. renders "base" template
-- grandchild template
{ % function block . greet () % } -- 5. defines "greet" block
Bye
{ % end % }
{ % render ( ' child ' ) % } -- 6. renders "child" template
在此示例中,“子”模板“擴展了”基本模板和任何block.greet
。子模板中定義的任何塊內容都在“ base”模板內渲染(何時和何時命名為block.greet()
函數) 。默認block.greet
塊不需要在基本模板中定義,但是當存在時(步驟1)時,它將設置要渲染的內容(步驟2),如果在子模板中未覆蓋塊,並且需要在block.greet
函數之前定義。
同樣,在(步驟3)渲染基本模板之前,需要定義子模板中的block.greet
(步驟4)以具有所需的效果。
如果當前渲染樹中的一個模板之一沒有定義塊,則將使用後來定義的塊。例如,如果孫子模板未在步驟5中定義塊,則在渲染孫子模板時將使用子模板的greet
塊。
如果沒有定義block.greet
函數,則block.greet()
失敗(在base
模板中)。要使塊可選,請在調用之前檢查功能。例如, block.greet and block.greet()
。
在可能仍需要渲染“覆蓋”塊的情況下,可以直接從定義它的模板中引用該塊,如以下示例所示:
fm . setTemplate ( " header " , [[
<h1>
{% function block.greet() %}
Hi
{% end %}
{% block.greet() %}, {%& title %}!
</h1>
]] )
fm . setTemplate ( " bye " , [[
{% block.header.greet() %},
{% function block.greet() %}
Bye
{% end %}
{% render('header', {title=title}) %}!
]] )
fm . render ( " bye " , { title = ' World ' }) -- renders `<h1>Hi, Bye, World!</h1>`
在這種情況下, {% block.header.greet() %}
在bye
模板中呈現從header
模板呈現greet
器塊。這僅適用於當前正在渲染的模板,並旨在模擬“超級”參考(儘管具有顯式模板參考)。此調用的一般語法為block.<templatename>.<blockname>()
。
由於塊僅僅是常規的LUA函數,因此對塊如何嵌套到其他塊中或相對於模板片段或模板中包含的其他LUA語句的定義沒有限制。
除了從字符串中註冊模板外,模板還可以使用相同的setTemplate
功能從文件或目錄中加載並註冊,但是將帶有目錄的表以及從文件擴展名到加載模板類型的映射列表。例如,調用fm.setTemplate({"/views/", tmpl = "fmt"})
從/views/
directory(及其子目錄)加載所有*.tmpl
文件,並將每個文件註冊為fmt
模板是默認模板類型。僅加載了匹配擴展名的那些文件,並且可以在一個呼叫中指定多個擴展映射。
每個已加載的模板根據指定目錄開始的完整路徑獲取其名稱:文件/views/hello.tmpl
註冊/views/greet/bye.tmpl
註冊為帶有名稱為“ entry /bye”的模板(這是加載模板的確切名稱)。
有兩個值得一提的警告,兩者都與目錄處理有關。第一個與傳遞給setTemplate
目錄名稱中的尾斜線有關。建議提供一個,因為指定值用作前綴,因此,如果指定/view
,則將匹配/view/
and /views/
directories(如果存在),這可能是或可能不會是預期結果。
第二個警告與模板搜索過程中的外部目錄的使用方式有關。由於RedBean使用-D
選項或directory
選項配置時允許訪問外部目錄(有關詳細信息,請參見運行應用程序),因此可用的同一模板可能有多個位置。搜索模板遵循以下步驟:
setTemplate
調用中指定);這允許在開發過程中通過文件系統修改和處理模板的工作副本(假設使用-D
選項),而無需在存檔中修改其副本。
即使使用fm.render
足以使模板呈現,但要與其他服務*函數保持一致,但庫提供了serveContent
函數,該功能與fm.render
相似,但允許操作處理程序在服務內容後完成:
fm . setTemplate ( " hello " , " Hello, {%& name %} " )
fm . setRoute ( " /hello/:name " , function ( r )
return fm . serveContent ( " hello " , { name = r . params . name })
end )
在服務靜態模板時, render
和serveContent
方法之間也有一個細微的區別。可能會直接呈現靜態模板,以響應以下內容的路線:
fm . setTemplate ( " hello " , " Hello, World! " )
-- option 1:
fm . setRoute ( " /hello " , fm . render ( " hello " ))
---- ---------------------└─────┘-------- not going to work
-- option 2:
fm . setRoute ( " /hello " , fm . serveContent ( " hello " ))
---- ---------------------└───────────┘-- works as expected
第一種方法是行不通的,因為在調用setRoute
時將要撥打fm.render
(僅設置路線)而不是在處理請求時進行的。當使用serveContent
方法(第二個選項)時,它將以延遲處理的方式實現,直到處理請求,從而避免問題。如果模板內容取決於請求中的某些值,則必須將serverContent
調用包裝到一個函數中以接受並傳遞這些變量(如較早的/hello/:name
路由示例中所示)。
在大多數情況下,庫配置專注於處理傳入請求,但是在某些情況下,觸發和處理內部事件可能是可取的。該庫支持使用CRON語法的作業計劃,並在計劃的時間執行了配置的作業(只要RedBean實例正在運行)。可以使用setSchedule
方法註冊新的時間表:
---- ----------- ┌─────────── minute (0-59)
---- ----------- │ ┌───────── hour (0-23)
---- ----------- │ │ ┌─────── day of the month (1-31)
---- ----------- │ │ │ ┌───── month (1-12 or Jan-Dec)
---- ----------- │ │ │ │ ┌─── day of the week (0-6 or Sun-Mon)
---- ----------- │ │ │ │ │ --
---- ----------- │ │ │ │ │ --
fm . setSchedule ( " * * * * * " , function () fm . logInfo ( " every minute " ) end )
支持所有標準和一些非標準的cron表達式:
*
:描述允許範圍內的任何值。,
:用於形成項目列表,例如1,2,3
。-
:創建(包容)範圍;例如, 1-3
等於1,2,3
。也允許開放範圍,因此-3
等於1-3
個月,在幾分鐘和小時內0-3
。/
:描述範圍的步驟。它使用步驟值選擇範圍內值的子集;例如, 2-9/3
等於2,5,8
(從2開始,然後添加一個步驟值以獲取5和8)。任何大寫字母都支持非數字值數月( Jan-Dec
)和一周的天數( Sun-Mon
)。也支持使用7
用於Sun
。
默認情況下,所有功能均在單獨的(分叉)過程中執行。如果需要在同一過程中執行,則可以將setSchedule
傳遞給第三個參數(表),以將sameProc
值設置為一個選項之一: {sameProc = true}
。
一些需要注意的警告:
OnServerHeartbeat
掛鉤,因此RedBean的版本應使用(v2.0.16+)。and
(而不是or
)結合在一起,因此,當指定兩者時,在滿足兩者時(而不是指定兩個指定)時執行作業。換句話說, * * 13 * Fri
僅在13日(星期五)有效,而在任何週五都沒有有效。如果需要or
行為,則可以將時間表分成兩個以分別處理每個條件。sameProc = true
選項以避免分叉。Sun
可以在兩端(為0或7)上可用,因此在這種情況下,最好使用封閉範圍以避免歧義。6-100
範圍可校正至6-12
。每個操作處理程序都會生成某種響應,以將其發送回客戶。除字符串外,該應用程序還可以返回以下結果:
serveResponse
),serveContent
),serveRedirect
),serveAsset
),serveError
),serveIndex
)和servePath
)。這些方法中的每一種都可以用作操作處理程序的返回值。 serveAsset
, servePath
和serveIndex
方法也可以直接用作操作處理程序:
fm . setRoute ( " /static/* " , fm . serveAsset )
fm . setRoute ( " /blog/ " , fm . serveIndex ( " /new-blog/ " ))
第一條路線配置所有現有資產要從/static/*
位置提供;第二個路由配置/blog/
url,從/new-blog/
Directory返回索引( index.lua
或index.html
資源)。
serveResponse(status[, headers][, body])
:使用提供的status
, headers
和body
值發送HTTP響應。 headers
是一個可選的表,填充了HTTP標頭名稱/值對。如果提供,這組標頭將在處理同一請求時卸下所有其他標題。類似於使用request.headers
字段設置的標題,這些名稱對案例不敏感,但是提供帶破折號的標頭名稱的別名對案例敏感: {ContentType = "foo"}
是{["Content-Type"] = "foo"}
的替代形式{["Content-Type"] = "foo"}
。 body
是一個可選的字符串。
考慮以下示例:
return fm . serveResponse ( 413 , " Payload Too Large " )
這將返回413狀態代碼,並將返回消息的正文設置為Payload Too Large
(未指定標題表)。
如果僅需要設置狀態代碼,則該庫使用serve###
語法提供了一個簡短的表單:
return fm . serve413
它也可以用作動作處理程序本身:
fm . setRoute ( fm . PUT " /status " , fm . serve402 )
serveContent(name, parameters)
使用提供的參數呈現模板。 name
是一個名稱模板名稱的字符串(如setTemplate
調用設置),而parameters
是帶有模板參數的表(在模板中稱為變量)。
Fullmoon的功能makeStorage
是一種連接並使用SQLite3
數據庫的方式。 makeStorage
返回一個數據庫管理表,該表包含一組可用於連接數據庫的功能。
run
方法執行已配置的應用程序。默認情況下,該服務器是在Localhost和port 8080上啟動的。可以通過傳遞addr
和port
選項更改這兩個值:
fm . run ({ addr = " localhost " , port = 8080 })
支持以下選項;默認值在括號中顯示,並通過傳遞表格來設置多個值的mult
和選項:
addr
:設置地址以在(多)上偵聽brand
:設置Server
標題值( "redbean/v# fullmoon/v#"
)cache
:為所有靜態資產配置Cache-Control
並Expires
(以秒為單位)。負值禁用標題。零值表示沒有緩存。certificate
:設置TLS證書值(多)directory
:設置本地目錄以提供資產,此外還可以從可執行文件本身內的存檔中提供資產(多)headers
:通過使用HTTP標題名稱/值對的表格通過表格,將默認標題設置為每個響應logMessages
:啟用響應標頭的記錄logBodies
:啟用請求實體的記錄(post/put/etc)。logPath
:在本地文件系統上設置日誌文件路徑pidPath
:在本地文件系統上設置PID文件路徑port
:設置要在(8080)上偵聽的端口號privateKey
:設置TLS私鑰值(多)sslTicketLifetime
:設置SSL票(86400)的持續時間(以秒為單位)trustedIp
:將IP地址配置為信任(多)。此選項接受兩個值(IP和CIDR值),因此需要將它們作為表格傳遞,以指定多個參數的表中: trustedIp = {{ParseIp("103.31.4.0"), 22}, {ParseIp("104.16.0.0"), 13}}
tokenBucket
:啟用DDOS保護。此選項接受零至5個值(作為表中的表格傳遞);可以傳遞一個空表以使用默認值: tokenBucket = {{}}
每個選項都可以接受一個簡單的值( port = 80
),值列表( port = {8080, 8081}
)或參數列表。由於值列表和參數列表都作為表傳遞,因此值列表符合優先級,因此,如果需要將參數列表傳遞給一個選項(例如trustedIp
),則必須將其包裝到表中: trustedIp = {{ParseIp("103.31.4.0"), 22}}
。如果只需要傳遞一個參數,則trustedIp = {ParseIp("103.31.4.0")}
和trustedIp = ParseIp("103.31.4.0")
都可以使用。
可以使用getAsset
方法填充key
和certificate
字符串值,該方法可以訪問Web服務器檔案中包裝的兩個資產和存儲在文件系統中的資產。
也有默認的cookie和會話選項可以使用下面描述的cookieOptions
和sessionOptions
表分配。
cookieOptions
設置了使用request.cookie.name = value
語法( {httponly=true, samesite="Strict"}
)。仍然可以使用表分配覆蓋默認值: request.cookie.name = {value, secure=false}
。
sessionOptions
設置了使用request.session.attribute = value
語法( {name="fullmoon_session", hash="SHA256", secret=true, format="lua"}
)。如果將secret
值設置為true
,則每次啟動服務器時都會分配一個隨機密鑰;如果啟用了冗長的記錄(通過為redbean添加-v
選項或使用fm.setLogLevel(fm.kLogVerbose)
調用),則記錄消息,以說明如何應用當前隨機值以使其永久性。
將此值設置為false
或一個空字符串無需秘密鍵即可應用哈希。
所示的結果來自與已發布的RedBean基準測試的同一環境中的運行和相同的硬件(感謝@Jart執行測試!)。即使這些測試使用了1.5版RedBean和0.10版的Fullmoon版本,但當前版本的Redbean/Fullmoon有望提供相似的性能。
這些測試使用的是與引言中顯示的完全相同的代碼,其中一個小更改:使用{%= name %}
而不是模板中的{%& name %}
,它跳過了HTML逃脫。該代碼演示了路由,參數處理和模板處理。
$ wrk -t 12 -c 120 http://127.0.0.0.1:8080/user/paul 運行10S測試 @ http://127.0.0.1:8080/user/paul 12個線程和120個連接 線程統計AVG STDEV MAX +/- STDEV 延遲312.06us 4.39ms 207.16ms 99.85% REQ/SEC 32.48K 6.69K 71.37K 82.25% 3913229在10.10中的請求,783.71MB閱讀 請求/秒: 387477.76 轉移/秒:77.60MB
以下測試使用了相同的配置,但是RedBean已與MODE=optlinux
選項編譯:
$ wrk -t 12 -c 120 http://127.0.0.0.1:8080/user/paul 運行10S測試 @ http://127.0.0.1:8080/user/paul 12個線程和120個連接 線程統計AVG STDEV MAX +/- STDEV 延遲346.31us 5.13ms 207.31ms 99.81% REQ/SEC 36.18K 6.70K 90.47K 80.92% 4359909在10.10中的請求,0.85GB閱讀 請求/秒: 431684.80 轉移/秒:86.45MB
以下兩個測試證明了全月的請求處理和RedBean提供靜態資產的延遲(無並發):
$ wrk -t 1 -C 1 http://127.0.0.0.1:8080/user/paul 運行10S測試 @ http://127.0.0.1:8080/user/paul 1個線程和1個連接 線程統計AVG STDEV MAX +/- STDEV 潛伏期15.75U 7.64U 272.00US 93.32% REQ/SEC 65.54K 589.15 66.58K 74.26% 658897在10.10中的請求,131.96MB閱讀 請求/秒:65241.45 轉移/秒:13.07MB
以下是Redbean本身在靜態壓縮資產上的結果:
$ wrk -h'接受編碼:gzip'-t 1 -C 1 htt://10.10.10.124:8080/tool/net/net/demo/index.html 運行10S測試 @ htt://10.10.10.124:8080/tool/net/net/demo/index.html 1個線程和1個連接 線程統計AVG STDEV MAX +/- STDEV 延遲7.40us 1.95US 252.00US 97.05% REQ/SEC 129.66K 3.20K 135.98K 64.36% 1302424在10.10中的請求,1.01GB閱讀 請求/秒:128963.75 轉移/秒:102.70MB
Berwyn Hoyt在他的LUA服務器基準測試結果中包括了Redbean的結果,這表明RedBean優於可比的Nginx/OpenRISTY實現。
高度實驗性的一切都會發生變化。
自v0.3以來,核心組件更穩定,很少更新。通常,記錄的接口比無證件的接口要穩定得多。那些修改某些接口的COMPAT
標記標記了一些接口,因此可以輕鬆地確定以查看任何兼容性問題。
某些過時的方法仍然存在(使用時記錄了警告)以後要刪除。
Paul Kulchenko([email protected])
請參閱許可證。