-- 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 --