-- prettify the raw XML error messages from Amazon S3, using browser-based XSLT
-- requires HAProxy 1.6 built with embedded Lua
-- in the haproxy global config, load this lua file
-- global
-- lua-load /etc/haproxy/s3-error-handler.lua # that's this file, btw -- save it there
-- then, in the back-end facing Amazon S3, fire the Lua action if the response status code >= 400
-- backend my-s3-backend
-- http-response lua.s3_error_xsl if { status ge 400 }
-- remember that to proxy a request to S3, the Host: header sent to S3 must be something that
-- S3 can associate with the bucket; if the incoming host header doesn't match, just replace it;
-- this allows any request that your proxy sends to be recognized by S3:
-- http-request set-header host example-bucket.s3.amazonaws.com
-- the lua script will modify the resulting XML and call on the browser to render the page using
-- the XSL stylesheet at /error.xsl, in the bucket, which must be publicly-readable, and must
-- have Content-Type: text/xsl
-- for your own sanity, you may also want to (at least for testing) configure /error.xsl with...
-- Cache-Control: private, no-cache, no-store
-- so that you don't have to worry about the browser caching it while you're testing
function s3addxsl(txn)
-- primitive, yet effective.
-- modify haproxy's output buffer when an S3 error message is returned (typically with 403/404),
-- containing raw XMl -- add a link to an xsl stylesheet for a browser-implemented transformation
-- note that this is a rawwwwwwwwww http response buffer we're working with; HAProxy Lua support is
-- new, and evolving, so there may be simpler mechanisms in the future, but as written,
-- since S3 will be using Transfer-Encoding: chunked, we need to capture the first chunk size,
-- which is a number in hex immediatedly following the headers; we add the length of our new string,
-- and modify that chunk size directly in the buffer after converting back to hex
local buff = txn.res:get();
-- capture the http status code for inclusion in the XML
-- there should be a better way, but txn.sf:status() is not available here
local status_code = buff.gsub(buff,"^HTTP/1%.%d (%d+) .*","%1",1) or "Unknown";
local stylesheet = "\n";
-- assumes your system clock is UTC, of course
local more_xml = "" .. os.date("%Y-%m-%dT%H:%M:%SZ") .. "" ..
"" .. status_code .. "";
local newstrlen = string.len(stylesheet) + string.len(more_xml);
-- find the length of the chunked body, a hex value immediately following the header;
-- add the length of our new payload to it, and replace it back into the buffer
buff = string.gsub(buff,"\r\n\r\n(%x+)\r\n", function (chk)
local chunklen = tonumber(chk,16) + newstrlen
return "\r\n\r\n" .. string.format("%x",chunklen) .. "\r\n"
end, 1);
-- before tag, insert the stylesheet directive, and after, the addtional XML
buff = string.gsub(buff,"",stylesheet .. "" .. more_xml);
txn.res:send(buff);
txn:done();
end
-- still part of the Lua script, we need to register our new action on HAProxy
-- startup to call the function above; the "http-response lua...." directive won't
-- parse as valid unless the action has been registered successfully
core.register_action('s3_error_xsl', { 'http-res' }, s3addxsl);
-- 2015-10-13 by michael@sqlbot.net -- provided as-is, no warranty -- consultation and custom development services are available
-- http://stackoverflow.com/questions/33107902/aws-s3-gracefully-handle-403-after-getsignedurl-expired/33109592#33109592
-- eof --