Created
April 25, 2017 11:43
-
-
Save marklr/ae0c2f1eb61855d13cde6cef6bf63541 to your computer and use it in GitHub Desktop.
Lua Preproc
This file contains 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
http { | |
# ... | |
# 51Degrees data file; should be updated regularly. | |
51D_filePath /data/51Degrees-PremiumV3_2.dat; | |
51D_cache 0; # crucial - otherwise crashes might occur | |
lua_need_request_body on; | |
upstream backend { | |
least_conn; | |
keepalive 32; | |
# configure server nodes here | |
} | |
server { | |
location / { | |
set $parsed_ua ''; | |
set $real_path ''; | |
set $max_chunk_size 10240; | |
set $max_body_size 524288; | |
content_by_lua_file conf/preproc.lua; | |
} | |
location /backend { | |
internal; | |
set_by_lua $uri_lowercase "return string.lower(ngx.var.real_path)"; | |
set_by_lua $auction_ip "return ngx.var.auction_ip"; | |
set_by_lua $auction_domain "return ngx.var.auction_domain" | |
proxy_pass_request_headers on; | |
51D_match_single x-mobile IsMobile; | |
51D_match_single x-tablet IsTablet; | |
51D_match_single x-smartphone IsSmartPhone; | |
51D_match_single x-smartwatch IsSmartWatch; | |
51D_match_single x-browsername BrowserName; | |
51D_match_single x-platformname PlatformName; | |
51D_match_single x-platformversion PlatformVersion; | |
51D_match_single x-hardwaremodel HardwareModel; | |
51D_match_single x-tv IsTv; | |
51D_match_single x-mediahub IsMediaHub; | |
51D_match_single x-console IsConsole; | |
51D_match_single x-ereader IsEReader; | |
proxy_set_header x-metrics $http_x_metrics; | |
proxy_set_header x-mobile $http_x_mobile; | |
proxy_set_header x-tablet $http_x_tablet; | |
proxy_set_header x-smartphone $http_x_smartphone; | |
proxy_set_header x-smartwatch $http_x_smartwatch; | |
proxy_set_header x-browsername $http_x_browsername; | |
proxy_set_header x-platformname $http_x_platformname; | |
proxy_set_header x-hardwaremodel $http_x_hardwaremodel; | |
proxy_set_header x-tv $http_x_tv; | |
proxy_set_header x-mediahub $http_x_mediahub; | |
proxy_set_header x-console $http_x_console; | |
proxy_set_header x-ereader $http_x_ereader; | |
proxy_set_header x-connection-type $geoip2_data_connection_type; | |
proxy_set_header x-geo-iso2 $geoip2_data_country_code; | |
proxy_set_header x-geo-iso2-reg-type $geoip2_data_country_reg_code; | |
proxy_set_header x-auction-ip $auction_ip; | |
proxy_set_header x-auction-domain $auction_domain; | |
proxy_read_timeout 2s; # adjust to 2sec for production | |
proxy_http_version 1.1; | |
proxy_set_header Connection ""; | |
proxy_pass http://backend$uri_lowercase; | |
} | |
} | |
} | |
This file contains 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
-- handle_request() parses the submitted OpenRTB (JSON) request and creates a new request with the UA value; | |
-- this is then picked up by the nginx config and passed to the backend app. | |
-- relies on https://github.com/golgote/neturl | |
local cjson = require "cjson" | |
ngx.ctx.max_chunk_size = tonumber(ngx.var.max_chunk_size) | |
ngx.ctx.max_body_size = tonumber(ngx.var.max_body_size) | |
-- Thanks, SO! #12014382 | |
string.split = function(s, p) | |
local temp = {} | |
local index = 0 | |
local last_index = string.len(s) | |
while true do | |
local i, e = string.find(s, p, index) | |
if i and e then | |
local next_index = e + 1 | |
local word_bound = i - 1 | |
table.insert(temp, string.sub(s, index, word_bound)) | |
index = next_index | |
else | |
if index > 0 and index <= last_index then | |
table.insert(temp, string.sub(s, index, last_index)) | |
elseif index == 0 then | |
temp = nil | |
end | |
break | |
end | |
end | |
return temp | |
end | |
function create_error_response(code, description) | |
local message = string.format('{"status":400,"statusReason":"Bad Request","code":%d,"exception":"","description":"%s","message":"HTTP 400 Bad Request"}', code, description) | |
ngx.status = ngx.HTTP_BAD_REQUEST | |
ngx.header.content_type = "application/json" | |
ngx.print(message) | |
ngx.exit(ngx.HTTP_OK) | |
end | |
function inflate_chunk(stream, chunk) | |
return stream(chunk, "finish") | |
end | |
function inflate_body(data) | |
local stream = require("zlib").inflate() | |
local buffer = "" | |
local chunk = "" | |
for index = 0, data:len(), ngx.ctx.max_chunk_size do | |
chunk = string.sub(data, index, index + ngx.ctx.max_chunk_size - 1) | |
local status, output, eof, bytes_in, bytes_out = pcall(stream, chunk) | |
if not status then | |
-- corrupted chunk | |
create_error_response(4001, "Corrupted GZIP body") | |
end | |
if bytes_in == 0 and bytes_out == 0 then | |
-- body is not gzip compressed | |
create_error_response(4002, "Invalid GZIP body") | |
end | |
buffer = buffer .. output | |
if bytes_out > ngx.ctx.max_body_size then | |
-- uncompressed body too large | |
create_error_response(4003, "Uncompressed body too large") | |
end | |
end | |
return buffer | |
end | |
function get_field(dict, path) | |
if dict == nil then return nil end | |
local obj = dict | |
for idx, fld in pairs(string.split(path, "%.")) do | |
if obj[fld] == nil then | |
return nil | |
end | |
obj = obj[fld] | |
end | |
return obj | |
end | |
function get_auction_domain(bidreq) | |
local bundle = get_field(bidreq, "app.bundle") | |
if bundle ~= '' and bundle ~= nil then | |
-- we don't want to treat apps as domains | |
return "" | |
end | |
local domain = get_field(bidreq, "site.domain") | |
local page = get_field(bidreq, "site.page") | |
local purl, host | |
local url = require("url") | |
if domain ~= '' and domain ~= nil and string.find(domain, ".", 1, true) ~= nil then | |
if string.sub(domain, 1, 5) ~= "http:" then domain = "http://" .. domain .. "/" end | |
purl = url.parse(domain) | |
end | |
if page ~= '' and page ~= nil and string.find(page, ".", 1, true) ~= nil then | |
if string.sub(page, 1, 5) ~= "http:" then page = "http://" .. page .. "/" end | |
purl = url.parse(page) | |
end | |
if purl ~= nil then | |
host = purl.host | |
if purl.host == nil then host = purl.authority end | |
if host ~= nil and string.sub(host, 1, 4) == "www." then | |
return string.sub(host, 5) | |
end | |
if host == nil then return "" end | |
return host | |
end | |
return "" | |
end | |
function get_request_body() | |
local content_encoding = ngx.req.get_headers()["Content-Encoding"] | |
local data = ngx.req.get_body_data() | |
if content_encoding == "gzip" then | |
if data ~= '' then | |
local new_data = inflate_body(data) | |
ngx.req.clear_header("Content-Encoding") | |
ngx.req.clear_header("Content-Length") | |
return new_data | |
end | |
end | |
return data | |
end | |
function handle_request() | |
local real_path = ngx.var.uri | |
if real_path == "/" or real_path == "/favicon.ico" then | |
ngx.status = ngx.HTTP_OK | |
ngx.header["Content-type"] = "text/plain" | |
ngx.print('okn') | |
return | |
end | |
-- Ferry GETs directly to the app | |
if ngx.var.request_method == "GET" then | |
local res = ngx.location.capture("/backend", { | |
method = ngx.HTTP_GET, | |
vars = { | |
real_path = real_path, | |
} | |
}); | |
ngx.status = res.status | |
ngx.print(res.body) | |
return | |
end | |
local new_data = get_request_body() | |
local success, bidreq = pcall(cjson.decode, new_data) | |
if success then | |
local parsed_ua = bidreq.device.ua | |
local auction_ip = bidreq.device.ip | |
ngx.req.set_body_data(new_data) | |
ngx.req.set_header("User-Agent", parsed_ua) | |
ngx.req.set_header("X-Auction-Domain", auction_domain) | |
ngx.req.set_header("X-Auction-IP", auction_ip) | |
res = ngx.location.capture("/backend", { | |
method = ngx.HTTP_POST, | |
vars = { | |
parsed_ua = parsed_ua, | |
real_path = real_path, | |
auction_ip = auction_ip, | |
auction_domain = auction_domain | |
} | |
}); | |
ngx.header["Content-type"] = "application/json" | |
ngx.status = res.status | |
ngx.print(res.body) | |
else | |
ngx.log(ngx.ERR, new_data) | |
ngx.status = ngx.HTTP_BAD_REQUEST | |
end | |
end | |
handle_request() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment