HAPROXY HTTP CONNECT method

I want to use HAPROXY to terminate requests made with the HTTP CONNECT method, converting them into TCP connections. Similarly I’d like to do the reverse - receive a TCP connection and then turn it into an HTTP CONNECT.

I’ve tried having an mode http frontend with a mode tcp backend, but this is not supported. I thought potentially it might be a filter, but I don’t see one.

Similar questions have been asked before, but with no response: HTTP CONNECT method handling

I’ve figured out a way of doing this with lua and the Channel API:

local function _http_response(code, message)
    return "HTTP/1.0 " .. code .. " \r\nContent-Length: " .. #message .. "\r\n\r\n" .. message
end

local function read_headers(chan)
    local headers = {}
    while true do
        local line = chan:getline()
        if line == "\r\n" then
            break
        end
        local key, value = line:match("^([^:]+):%s*([^\r\n]+)\r\n$")
        if key then
            headers[key] = value
        end
    end
    return headers
end

-- An action that parses and removes the HTTP CONNECT header from the
-- tcp-request.  It exposes the CONNECT HTTP headers to haproxy in the
-- txn.req_headers variable.  Usage:
--
--     tcp-request inspect-delay 5s
--     tcp-request content lua.terminate_http_connect
local function terminate_http_connect(txn)
    local req_chan = txn.req
    local res_chan = txn.res
    local first_line = req_chan:getline()
    local method, uri, version = first_line:match(
        "^CONNECT%s+([^%s]+)%s+HTTP/([0-9.]+)\r\n$")
    -- It's an error to not have a CONNECT header:
    if not method then
        txn.res:send(_http_response("400", "Bad Request: Missing CONNECT header"))
        txn:done()
        return
    end

    -- Read all headers and expose them to haproxy:
    txn:set_var("txn.req_headers", read_headers(req_chan))

    -- Send CONNECT response back down the pipe:
    txn.res:send("HTTP/1.0 200 Connection established\r\n\r\n")

    -- Two way communication is now established
end

core.register_action("terminate_http_connect", { "tcp-req" }, terminate_http_connect)

-- Two actions that convert a TCP request into an HTTP CONNECT request.  Usage:
--
--     tcp-request content lua.add_http_connect str('my.example.com')
--     tcp-response inspect-delay 5s
--     tcp-response content lua.strip_http_connect_response
local function add_http_connect(txn, x_hostname)
    local req_chan = txn.req
    local c = core.concat()
    c:add("CONNECT blooblah.ste:8080 HTTP/1.0\r\n")
    c:add("X-Hostname: ")
    c:add(x_hostname)
    c:add("\r\n\r\n")

    req_chan:send(c:dump())
end

core.register_action("add_http_connect", { "tcp-req" }, add_http_connect, 1)

-- An action that strips the HTTP CONNECT response header from the response:
local function strip_http_connect_response(txn)
    local res_chan = txn.res
    local first_line = res_chan:getline()
    local version, code = first_line:match("^HTTP/([0-9.]+)%s+(%d+)")
    if not version then
        txn.res:send(_http_response("500", "Internal Server Error: Missing HTTP response"))
        txn:done()
        return
    end

    -- Read all headers and expose them to haproxy:
    txn:set_var("txn.res_headers", read_headers(res_chan))
end

core.register_action("strip_http_connect_response", { "tcp-res" }, strip_http_connect_response)

I couldn’t figure out how to pass variables into the actions so it interfaces with the haproxy config via setting variables.

In my usecase I add a CONNECT header to a mode tcp stream, and then have that connect on localhost back to haproxy to a mode http frontend to be processed as HTTP. It would be nice to have a syntax for chaining backends to frontends without send the data out and back again over a socket. Maybe haproxy supports this?, but I’ve not been able to figure it out.