Created
August 26, 2012 14:36
-
-
Save daurnimator/3480374 to your computer and use it in GitHub Desktop.
Lpeg pattern to parse a HTTP Accept Header
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
do | |
local function helper ( allowed , want ) | |
return ( not want.type or allowed.type == "*" or ( allowed.type == want.type and | |
( not want.subtype or allowed.subtype == "*" or allowed.subtype == want.subtype ) ) | |
) and allowed.q ~= 0 | |
end | |
function choose_type ( allowed_list , wanted_list ) | |
-- Remove items from allowed list that have no match (retains client ordering) | |
local res = { } | |
local allowed_wanted_map = { } | |
for i , allowed in ipairs ( allowed_list ) do | |
for j , want in ipairs ( wanted_list ) do | |
if helper ( allowed , want ) then | |
res [ #res + 1 ] = allowed | |
allowed_wanted_map [ allowed ] = want | |
break | |
end | |
end | |
end | |
-- Swap values to the wanted list so the caller knows what to serve | |
for i , v in ipairs ( res ) do | |
res [ i ] = allowed_wanted_map [ v ] | |
end | |
return res | |
end | |
end | |
local accept_list = header_parsers.accept ( "text/*;q=0.3, text/html;q=0.7, text/html;level=1,\r\n text/html;level=2;q=0.4, */*;q=0.5" ) | |
local content_type = choose_type ( accept_list , { | |
{ type = "text" ; subtype = "html" } ; | |
{ type = "application" ; subtype = "json" } ; | |
} )[1] | |
if not content_type then | |
return 404 | |
else | |
content_type = content_type.type .. "/" .. content_type.subtype | |
end | |
ngx.header["Content-Type"] = content_type | |
if content_type == "application/json" then | |
..... | |
else | |
...... | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local parsers = { } | |
local lpeg = require "lpeg" | |
local P = lpeg.P | |
local R = lpeg.R | |
local S = lpeg.S | |
local C = lpeg.C | |
local Cf = lpeg.Cf | |
local Cg = lpeg.Cg | |
local Cs = lpeg.Cs | |
local Ct = lpeg.Ct | |
local identity = function(...) return ... end | |
local CHAR = R("\0\127") | |
local CTL = R("\0\31")+P("\127") | |
local LWS = P("\r\n")^-1 * S(" \t")^1 | |
local seperators = S("()<>@,;:\\\"/[]?={} \t") | |
local token = (CHAR-CTL-seperators)^1 | |
local qdtext = P(1)-P('"') | |
local quoted_pair = Cs ( P("\\") * C(1) / identity ) | |
local quoted_string = Cs ( P('"') * C ( ( qdtext + quoted_pair )^0 ) * P'"' / identity ) | |
local attribute = token | |
local value = token + quoted_string | |
local parameter = C(attribute) *LWS^0* P("=") *LWS^0* C(value) | |
local type = C(token) | |
local subtype = C(P("*")+token) | |
local media_range = ( Cg(C("*"),"type")*P("/")*Cg(C("*"),"subtype") + Cg(type,"type")*LWS^0*P("/")*LWS^0*Cg(subtype,"subtype") ) * | |
-- Collect parameters in a table | |
Cg( Cf ( Ct(true)*( P(";") *LWS^0* ( | |
-#(P("q")*LWS^0* P("=")) -- The first "q" parameter (if any) separates the media-range parameter(s) from the accept-params | |
) * Cg(parameter) )^1 , rawset ) , "parameters" )^-1 | |
local qvalue = ( P("0")*(P(".")*R("09")^-3)^-1 + P("1")*(P(".")*P("0")^-3)^-1 ) / tonumber | |
local accept_extention = P(";") *LWS^0* C(token) *LWS^0* ( P("=") *LWS^0* C ( token + quoted_string ) )^-1 | |
local accept_params = P(";") *LWS^0* Cg ( P("q") *LWS^0* P("=") *LWS^0* qvalue , "q" ) * | |
-- Collect extensions in a table | |
Cg( Cf ( Ct(true)* LWS^0* (Cg(accept_extention)*LWS^0)^1 , rawset ) , "extensions" )^-1 | |
local media_mt | |
local item = Ct ( media_range *LWS^0* accept_params^-1 ) / function ( m ) return setmetatable ( m , media_mt ) end | |
local accept = LWS^0* ( item * ( LWS^0* P(",") *LWS^0* item )^0 )^-1 | |
local function count ( t ) | |
local i = 0 | |
for k , v in pairs ( t ) do | |
i = i + 1 | |
end | |
return i | |
end | |
-- Higher specificity wins | |
local function item_compare (a,b) | |
local aq , bq = a.q or 1 , b.q or 1 | |
if aq == bq then | |
if a.parameters then | |
if not b.parameters then | |
return true | |
end | |
local acount , bcount = count ( a ) , count ( b ) | |
if acount ~= bcount then | |
return acount > bcount | |
end | |
elseif b.parameters then | |
return false | |
end | |
if a.subtype ~= "*" then | |
if b.subtype == "*" then | |
return true | |
end | |
-- type can only be "*" if subtype is | |
if a.type ~= "*" then | |
if b.type == "*" then | |
return true | |
end | |
elseif b.type ~= "*" then | |
return false | |
end | |
elseif b.subtype ~= "*" then | |
return false | |
end | |
-- Need to give a consistent comparison | |
return tostring(aq) > tostring(bq) | |
else | |
return aq > bq | |
end | |
end | |
media_mt = { | |
__tostring = function ( m ) | |
return m.type .. "/" .. m.subtype | |
end ; | |
__gt = item_compare ; | |
__lt = function ( a , b ) return item_compare ( b , a ) end ; | |
} | |
parsers.accept = function ( h ) | |
local r = { accept:match ( h ) } | |
table.sort ( r , item_compare ) | |
return r | |
end | |
return parsers |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local accept = require "header_parsers".accept | |
for i , test in ipairs { | |
"audio/*; q=0.2, audio/basic" ; | |
"text/plain; q=0.5, text/html,\r\n text/x-dvi; q=0.8, text/x-c" ; | |
"text/*, text/html, text/html;level=1, */*" ; | |
"text/*;q=0.3, text/html;q=0.7, text/html;level=1,\r\n text/html;level=2;q=0.4, */*;q=0.5" ; | |
"text/plain; stuff=asd; q=0.00; foo=bar" ; | |
} do | |
print("TESTING",test) | |
for j , v in ipairs { accept:match(test) } do | |
print(j,v) | |
for k,vv in pairs(v) do | |
if _G.type ( vv ) == "table" then | |
print("",k) | |
for kk,vvv in pairs(vv) do | |
print("","",kk,vvv) | |
end | |
else | |
print("",k,vv) | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment