Project

General

Profile

LuaExamples

X-Sendfile version 1

This config snippet inserts a lua filter to check for a "X-Sendfile" header; if a non-empty value is found, it will be used as pathname to a file which replaces the content.
The drawback is that "XSendfile:handle" will be called for every data block going to the user, even if there was no "X-Sendfile" header (lua doesn't read the data, it just forwards the chunks, but it still is not optimal).
But this filter can be inserted at any point in your config, even before the content handler.

-- create new class XSendfile
local XSendfile = { }
XSendfile.__index = XSendfile

-- "classmethod" to create new instance
function XSendfile:new(vr)
--    vr:debug("New XSendfile instance")
    local o = { handling = 0  }
    setmetatable(o, self)
    return o
end

-- normal method to handle content
function XSendfile:handle(vr, outq, inq)
--    vr:debug("XSendfile:handle "..self.handling)
    if self.handling == 1 then
        -- already sent the file, ignore further input (we closed it already)
        inq:skip_all()
        return lighty.HANDLER_GO_ON
    elseif self.handling == -1 then
        -- no X-Sendfile header, so just forward content and make sure to close chunkqueues if done
        outq:steal_all(inq)
        if inq.is_closed then -- no more input from backend
            outq.is_closed = true
        elseif outq.is_closed then -- client (or other filter after us) closed stream
            inq.is_closed = true
        end
        return lighty.HANDLER_GO_ON
    else
        if outq.is_closed then -- client (or other filter after us) closed stream
            inq:skip_all()
            inq.is_closed = true
            return lighty.HANDLER_GO_ON
        end

        -- check x-sendfile header
        local xs = vr.resp.headers["X-Sendfile"]
        if xs and xs ~= "" then -- file specified
            vr.resp.headers["X-Sendfile"] = nil -- remove header from response

            -- Add checks for the pathname here

            vr:debug("XSendfile:handle: pushing file '" .. xs .. "' as content")
            outq:add({ filename = xs })
            outq.is_closed = true
            inq:skip_all()
            inq.is_closed = true
            self.handling = 1
        else
            self.handling = -1
        end
        return lighty.HANDLER_GO_ON
    end
end

actions = lighty.filter_out(XSendfile)

X-Sendfile version 2

This config snippet waits for the response headers, and replaces the content with the filename found in the "X-Sendfile" header (if it isn't empty).
This version does not need much cpu; it is assumed that the real backend doesn't send the content anyway, and file is sent in one step, so "XFilterDrop:handle" is probably not called very often.
But this version will wait for the response headers in the action, so you need a content handler before it (static, fastcgi, ...).

-- create new class XFilterDrop
local XFilterDrop = { }
XFilterDrop.__index = XFilterDrop

-- "classmethod" to create new instance
function XFilterDrop:new(vr)
--    vr:debug("New XSendfile instance")
    local o = { }
    setmetatable(o, self)
    return o
end

-- normal method to handle content
function XFilterDrop:handle(vr, outq, inq)
    -- drop further input (we closed it already)
    inq:skip_all()
    return lighty.HANDLER_GO_ON
end

-- create a new filter which drops all input and adds it to the vrequest
-- returns the filter object so you can insert your own content in f.out (it is already closed)
local function add_drop_filter(vr)
    local f = vr:add_filter_out(XFilterDrop:new())
    f['in'].is_closed = true
    f['in']:skip_all()
    f.out.is_closed = true
    return f
end

local function handle_x_sendfile(vr)
--    vr:debug("handle x-sendfile")
    -- wait for response
    if not vr.has_response then
        if vr.is_handled then
--            vr:debug("x-sendfile: waiting for response headers")
            return lighty.HANDLER_WAIT_FOR_EVENT
        else
--            vr:debug("No response handler yet, cannot handle X-Sendfile")
            return lighty.HANDLER_GO_ON
        end
    end
--    vr:debug("handle x-sendfile: headers available")
    -- add filter if x-sendfile header is not empty
    local xs = vr.resp.headers["X-Sendfile"]
    if xs and xs ~= "" then
        -- make sure to drop all other content from the backend
        local f = add_drop_filter(vr)

        vr.resp.headers["X-Sendfile"] = nil -- remove header from response

        -- Add checks for the pathname here

        vr:debug("XSendfile:handle: pushing file '" .. xs .. "' as content")
        f.out:add({ filename = xs })
    end
end

actions = handle_x_sendfile

Updated by stbuehler over 4 years ago · 4 revisions