Skip to content

Instantly share code, notes, and snippets.

@marklr
Created April 25, 2017 11:43
Show Gist options
  • Save marklr/ae0c2f1eb61855d13cde6cef6bf63541 to your computer and use it in GitHub Desktop.
Save marklr/ae0c2f1eb61855d13cde6cef6bf63541 to your computer and use it in GitHub Desktop.
Lua Preproc
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;
}
}
}
-- 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