0

I am trying to rewrite FastCGI extension which was written for Lua Resty module of NGINX by benagricola, but it keeps on failing to connect to any PHP-FastCGI server (throws fastCGI : recv header error: closed which means that FastCGI is not available) i tried adjusting the timeout but it didn't work

I am using the extension like this

set $cgi_script_name ''; location ~ ^/@FastCGI(/+)?((([a-zA-Z0-9\_\-]+(/+))+)?([a-zA-Z0-9\-\_]+\.[a-zA-Z0-9]+))? { internal; if_modified_since off; content_by_lua_block { local fastcgi = require "fastcgi" local fcgi = setmetatable({}, fastcgi) fcgi:connect("127.0.0.1", 25680) local ok, err = fcgi:request({ script_filename = ngx.var["document_root"] .. ngx.var["cgi_script_name"], script_name = ngx.var["cgi_script_name"], document_root = ngx.var["document_root"], server_port = ngx.var["balancer_port"], path_info = ngx.var["fastcgi_path_info"], query_string = ngx.var["query_string"], request_uri = ngx.var["request_uri"], document_uri = ngx.var["request_uri"], server_protocol = ngx.var["server_protocol"], request_method = ngx.var["request_method"], geoip2_data_country_code = ngx.var["geoip2_data_country_code"], geoip2_data_country_name = ngx.var["geoip2_data_country_name"], geoip2_data_city_name = ngx.var["geoip2_data_city_name"] }, { cache_dict = "fastcgiCache", cache_valid = 300, keepalive = true, keepalive_timeout = 120000, keepalive_pool_size = 100, hide_headers = { "X-Powered-By", "X-Page-Speed", "X-Application-Version", "X-Varnish", "Last-Modified", "Cache-Control", "Vary", "X-CF-Powered-By" }, intercept_errors = true, read_timeout = 60000, cacheMethods = { "GET" }, header_chunk_size = 50 * 1024, body_chunk_size = 30 * 1024 }) if not ok then ngx.exit(ngx.HTTP_BAD_GATEWAY) end } include /etc/nginx/fastcgi_params; access_log on; } 

and in my PATH Resolver (off-topic, but I have to include it in my question)

local uri = ngx.var["request_uri"] or "/" if type(uri) ~= "string" then ngx.log(ngx.ERR, "URI is not a string: ", type(uri)) uri = "/" end ngx.log(ngx.DEBUG, "Request URI: ", uri or "Unknown!") ngx.log(ngx.DEBUG, "URI: ", ngx.var["uri"] or "Unknown!") local ____PATH = ngx.var["document_root"] .. uri local ___PATH = string.match(____PATH, "^[^?]*") if not ___PATH or ___PATH == 1 then ___PATH = ____PATH end local file, err = io.open(___PATH, "rb") if not file then ngx.log(ngx.ERR, "Failed to open file: " .. err) ngx.status = ngx.HTTP_NOT_FOUND ngx.exit(ngx.HTTP_NOT_FOUND) return end file:close() ngx.var["cgi_script_name"] = ngx.var["uri"] local res = ngx.location.capture("/@FastCGI", { -- method = ngx.HTTP_GET, args = ngx.var["args"], }) ngx.status = res.status for k, v in pairs(res.header) do ngx.header[k] = v end ngx.print(res.body) ngx.log(ngx.DEBUG, "#1 : " .. uri) 

and my extension fork

local ngx = require "ngx" local bit = require "bit" local binutil = require 'resty.binutil' local _M = {} _M.__index = _M local FCGI = { HEADER_LEN = 0x08, VERSION_1 = 0x01, BEGIN_REQUEST = 0x01, ABORT_REQUEST = 0x02, END_REQUEST = 0x03, PARAMS = 0x04, STDIN = 0x05, STDOUT = 0x06, STDERR = 0x07, DATA = 0x08, GET_VALUES = 0x09, GET_VALUES_RESULT = 0x10, UNKNOWN_TYPE = 0x11, MAXTYPE = 0x11, BODY_MAX_LENGTH = 32768, KEEP_CONN = 0x01, NO_KEEP_CONN = 0x00, NULL_REQUEST_ID = 0x00, RESPONDER = 0x01, AUTHORIZER = 0x02, FILTER = 0x03 } local FCGI_HEADER_FORMAT = { { "version", 1, FCGI.VERSION_1 }, { "type", 1, nil }, { "request_id", 2, 1 }, { "content_length", 2, 0 }, { "padding_length", 1, 0 }, { "reserved", 1, 0 } } local function _pack(format, params) local bytes = "" for unused, field in ipairs(format) do local fieldname = field[1] local fieldlength = field[2] local defaulval = field[3] local value = params[fieldname] or defaulval if value == nil then ngx.log(ngx.ERR, "fastCGI : Missing value for field: " .. fieldname) return nil end bytes = bytes .. binutil.ntob(value, fieldlength) end return bytes end local function _pack_header(params) local align = 8 params.padding_length = bit.band(-(params.content_length or 0), align - 1) return _pack(FCGI_HEADER_FORMAT, params), params.padding_length end local FCGI_BEGIN_REQ_FORMAT = { { "role", 2, FCGI.RESPONDER }, { "flags", 1, 0 }, { "reserved", 5, 0 } } local FCGI_PREPACKED = { end_params = _pack_header({ type = FCGI.PARAMS, }), begin_request = _pack_header({ type = FCGI.BEGIN_REQUEST, request_id = 1, content_length = FCGI.HEADER_LEN, }) .. _pack(FCGI_BEGIN_REQ_FORMAT, { role = FCGI.RESPONDER, flags = 1, }), abort_request = _pack_header({ type = FCGI.ABORT_REQUEST, }), empty_stdin = _pack_header({ type = FCGI.STDIN, content_length = 0, }), } local FCGI_END_REQ_FORMAT = { { "status", 4, nil }, { "protocolStatus", 1, nil }, { "reserved", 3, nil } } local FCGI_PADDING_BYTES = { string.char(0), string.char(0, 0), string.char(0, 0, 0), string.char(0, 0, 0, 0), string.char(0, 0, 0, 0, 0), string.char(0, 0, 0, 0, 0, 0), string.char(0, 0, 0, 0, 0, 0, 0), } local function _pad(bytes) if bytes == 0 then return "" else return FCGI_PADDING_BYTES[bytes] end end local function _unpack_hdr(format, str) -- If we received nil, return nil if not str then return nil end local res, idx = {}, 1 -- Extract bytes based on format. Convert back to number and place in res rable for _, field in ipairs(format) do res[field[1]] = bton(str_sub(str, idx, idx + field[2] - 1)) idx = idx + field[2] end return res end local function _format_stdin(stdin) local chunk_length local to_send = {} local stdin_chunk = { "", "", "" } local header = "" local padding, idx = 0, 1 local stdin_length = #stdin -- We could potentially need to send more than one records' worth of data, so -- loop to format. repeat -- While we still have stdin data, build up STDIN record in chunks if stdin_length > FCGI.BODY_MAX_LENGTH then chunk_length = FCGI.BODY_MAX_LENGTH else chunk_length = stdin_length end header, padding = _pack_header({ type = FCGI.STDIN, content_length = chunk_length, }) stdin_chunk[1] = header stdin_chunk[2] = string.sub(stdin, 1, chunk_length) stdin_chunk[3] = _pad(padding) to_send[idx] = table.concat(stdin_chunk) stdin = string.sub(stdin, chunk_length + 1) stdin_length = stdin_length - chunk_length idx = idx + 1 until stdin_length == 0 return table.concat(to_send) end local function _send_stdin(sock, stdin) local ok, bytes, err, chunk, partial if type(stdin) == 'function' then repeat chunk, err, partial = stdin(FCGI.BODY_MAX_LENGTH) -- If the iterator returns nil, then we have no more stdin -- Send an empty stdin record to signify the end of the request if chunk then ngx.log(ngx.DEBUG, "Request body reader yielded ", #chunk, " bytes of data - sending") ok, err = sock:send(_format_stdin(chunk)) if not ok then ngx.log(ngx.DEBUG, "Unable to send ", #chunk, " bytes of stdin: ", err) return nil, err end -- Otherwise iterator errored, return elseif err ~= nil then ngx.log(ngx.DEBUG, "Request body reader yielded an error: ", err) return nil, err, partial end until chunk == nil elseif stdin ~= nil then ngx.log(ngx.DEBUG, "Sending ", #stdin, " bytes of read data") bytes, err = sock:send(_format_stdin(stdin)) if not bytes then return nil, err end elseif stdin == nil then return end -- Send empty stdin record to signify end bytes, err = sock:send(FCGI_PREPACKED.empty_stdin) if not bytes then return nil, err end return true, nil end local function build_header(record_type, content_len, padding_len, request_id) return string.char( FCGI.VERSION_1, record_type, bit.rshift(request_id, 8), bit.band(request_id, 0xFF), bit.rshift(content_len, 8), bit.band(content_len, 0xFF), padding_len, 0 ) end local function encode_name_value(name, value) local n, v = #name, #value local parts = {} if n < 128 then parts[#parts + 1] = string.char(n) else parts[#parts + 1] = string.char( bit.bor(bit.rshift(n, 24), 0x80), bit.band(bit.rshift(n, 16), 0xFF), bit.band(bit.rshift(n, 8), 0xFF), bit.band(n, 0xFF) ) end if v < 128 then parts[#parts + 1] = string.char(v) else parts[#parts + 1] = string.char( bit.bor(bit.rshift(v, 24), 0x80), bit.band(bit.rshift(v, 16), 0xFF), bit.band(bit.rshift(v, 8), 0xFF), bit.band(v, 0xFF) ) end parts[#parts + 1] = name parts[#parts + 1] = value return table.concat(parts) end function _M:connect(host, port) self.fcgiSocket = ngx.socket.tcp() if not self.fcgiSocket then ngx.log(ngx.ERR, "fastCGI : failed to create TCP socket") return nil, "fastCGI : failed to create TCP socket" end self.request_id = 0 self.fcgiSocket:settimeout(3000) -- tmp change local ok, err = self.fcgiSocket:connect(host, port) if not ok then ngx.log(ngx.ERR, "fastCGI : connect error: " .. (err or "Unknown")) ngx.exit(ngx.HTTP_BAD_GATEWAY) return nil, "fastCGI : connect error: " .. (err or "Unknown") end return true end function _M:close() if not self.fcgiSocket then ngx.log(ngx.ERR, "fastCGI : no socket") return nil, "fastCGI : no socket" end local _, close_err = self.fcgiSocket:close() self.fcgiSocket = nil if close_err and close_err ~= "closed" then ngx.log(ngx.ERR, "fastCGI : close failed: " .. (close_err or "Unknown")) return nil, "fastCGI : close failed: " .. (close_err or "Unknown") end return true end function _M.get_reused_times(self) if not self.fcgiSocket then ngx.log(ngx.ERR, "fastCGI : no socket") return nil, "fastCGI : no socket" end return self.fcgiSocket:getreusedtimes() end function _M.set_timeout(self, timeout) if not self.fcgiSocket then ngx.log(ngx.ERR, "fastCGI : no socket") return nil, "fastCGI : no socket" end return self.fcgiSocket:settimeout(timeout) end function _M.set_keepalive(self, ...) if not self.fcgiSocket then ngx.log(ngx.ERR, "fastCGI : no socket") return nil, "fastCGI : no socket" end return self.fcgiSocket:setkeepalive(...) end function _M:request(params, opts, stdin) opts = opts or {} if not self.fcgiSocket then ngx.log(ngx.ERR, "fastCGI : not connected") return nil, "fastCGI : not connected" end self.request_id = (self.request_id % 65535) + 1 local request_id = self.request_id local function cleanup(ok) if not self.fcgiSocket then return end if ok and opts.keepalive then local ka_ok, ka_err = self.fcgiSocket:setkeepalive( opts.keepalive_timeout or 60000, opts.keepalive_pool_size or 10 ) if not ka_ok and ka_err ~= "closed" then ngx.log(ngx.ERR, "fastCGI : keepalive failed: " .. (ka_err or "Unknown")) return nil, "fastCGI : keepalive failed: " .. (ka_err or "Unknown") end else local _, close_err = self.fcgiSocket:close() self.fcgiSocket = nil if close_err and close_err ~= "closed" then ngx.log(ngx.ERR, "fastCGI : close failed: " .. (close_err or "Unknown")) return nil, "fastCGI : close failed: " .. (close_err or "Unknown") end end end local ok, err = xpcall(function(self, params, opts) local cache = nil local cache_key = nil if not (opts.cache_bypass and opts.cache_bypass()) and not ngx.var["skip_cache"] then cache = ngx.shared[opts.cache_dict or "fastcgiCache"] cache_key = table.concat({ ngx.var.scheme, ngx.var.host, ngx.var.uri, ngx.var.args or "", params.script_filename }, "|") local cached = cache:get(cache_key) if cached then ngx.status = cached.status for k, v in pairs(cached.headers) do ngx.header[k] = v end ngx.say(cached.body) return ngx.exit(ngx.HTTP_OK) end end local begin_body = string.char(0, FCGI.RESPONDER, 0, 0, 0, 0, 0, 0) local header = build_header(FCGI.BEGIN_REQUEST, #begin_body, 0, request_id) local ok, err = self.fcgiSocket:send(header .. begin_body) if not ok then ngx.log(ngx.ERR, "fastCGI : failed to send begin request: " .. (err or "Unknown")) return nil, "fastCGI : failed to send begin request: " .. (err or "Unknown") end local fcgi_params = {} if params.script_filename then fcgi_params["SCRIPT_FILENAME"] = params.script_filename local script_name = params.script_name local path_info = params.path_info if not script_name or not path_info then local _uri = params.request_uri or ngx.var["request_uri"] or "" _uri = _uri:match("^[^?]+") or "" local m, n = _uri:match("(.+%.php)(/.*)") if m then script_name = script_name or (m or _uri) path_info = path_info or n else script_name = script_name or _uri path_info = path_info or "" end end fcgi_params["SCRIPT_NAME"] = script_name or "" fcgi_params["PATH_INFO"] = path_info or "" end fcgi_params["REQUEST_METHOD"] = params.request_method or ngx.var["request_method"] fcgi_params["QUERY_STRING"] = params.query_string or ngx.var["query_string"] or "" fcgi_params["SERVER_PROTOCOL"] = params.server_protocol or ngx.var["server_protocol"] fcgi_params["REMOTE_ADDR"] = ngx.var["remote_addr"] or "" fcgi_params["REMOTE_PORT"] = ngx.var["remote_port"] or "" fcgi_params["SERVER_ADDR"] = ngx.var["server_addr"] or "" fcgi_params["SERVER_PORT"] = ngx.var["server_port"] or "" fcgi_params["SERVER_NAME"] = ngx.var["server_name"] or "" fcgi_params["DOCUMENT_ROOT"] = params.document_root or ngx.var["document_root"] or "" fcgi_params["DOCUMENT_URI"] = params.document_uri or ngx.var["request_uri"] or "" fcgi_params["COUNTRY_CODE"] = params.geoip2_data_country_code or ngx.var["geoip2_data_country_code"] or "" fcgi_params["COUNTRY_NAME"] = params.geoip2_data_country_name or ngx.var["geoip2_data_country_name"] or "" fcgi_params["CITY_NAME"] = params.geoip2_data_city_name or ngx.var["geoip2_data_city_name"] or "" fcgi_params["HTTP_PROXY"] = params.http_proxy or "" local headers = ngx.req.get_headers() if headers["Content-Type"] then fcgi_params["CONTENT_TYPE"] = headers["Content-Type"] end if ngx.var["content_length"] then fcgi_params["CONTENT_LENGTH"] = ngx.var["content_length"] end if params.fastcgi_params then for k, v in pairs(params.fastcgi_params) do fcgi_params[k] = v end end for k, v in pairs(headers) do if type(k) == "string" and type(v) == "string" then local hk = "HTTP_" .. k:upper():gsub("-", "_") if hk ~= "HTTP_CONTENT_TYPE" and hk ~= "HTTP_CONTENT_LENGTH" then fcgi_params[hk] = v end end end local all_params = {} for k, v in pairs(fcgi_params) do all_params[#all_params + 1] = encode_name_value(k, tostring(v)) end local pstr = table.concat(all_params) local pos, plen = 1, #pstr local chunk local clen, pad local bytes, sendERR while plen > 0 do chunk = pstr:sub(pos, pos + 65535 - 1) clen, pad = #chunk, (8 - (#chunk % 8)) % 8 bytes, sendERR = self.fcgiSocket:send(build_header(FCGI.PARAMS, clen, pad, request_id) .. chunk .. string.rep("\0", pad)) if not bytes then ngx.log(ngx.ERR, "fastCGI : Failed to send params: " .. (sendERR or "Unknown")) return nil, "fastCGI : Failed to send params: " .. (sendERR or "Unknown") end pos = pos + clen plen = plen - clen end self.fcgiSocket:send(build_header(FCGI.PARAMS, 0, 0, request_id)) local method = fcgi_params.REQUEST_METHOD if method == "POST" or method == "PUT" or method == "PATCH" then ngx.req.read_body() local body_sock = ngx.req.socket(true) local sendOK local chunk_ local data, recv_err, partial if body_sock then repeat data, recv_err, partial = body_sock:receive(opts.body_chunk_size or 8192) ngx.log(ngx.DEBUG, "Attempting to read end request") if not data or partial then ngx.log(ngx.ERR, "Unable to parse FCGI end request body : " .. (err or "Unknown")) return nil, "Unable to parse FCGI end request body : " .. (err or "Unknown") end chunk_ = data or partial if chunk_ then local pad = (8 - (#chunk_ % 8)) % 8 sendOK, sendERR = self.fcgiSocket:send(build_header(FCGI.STDIN, #chunk_, pad, request_id) .. chunk_ .. (pad > 0 and string.rep("\0", pad) or "")) if not sendOK then ngx.log(ngx.ERR, "Failed to send stdin: " .. (sendERR or "Unknown")) return nil, "Failed to send stdin: " .. (sendERR or "Unknown") end end until not data or recv_err end end self.fcgiSocket:send(build_header(FCGI.STDIN, 0, 0, request_id)) self.fcgiSocket:settimeout(opts.read_timeout or 60000) local stdout, stderr = "", {} local parsed_headers = false local read_bytes = "" local partial = "" local bytes_to_read, hdrByte, rcvERR local hdr, typ, rcvClen, rcvPad local sep, raw, rest local hn, hv, hName local cacheMethod local read_data while true do hdrByte, rcvERR = self.fcgiSocket:receive(opts.header_chunk_size or 8) if not hdrByte then ngx.log(ngx.ERR, "fastCGI : recv header error: " .. (rcvERR or "Unknown")) return nil, "fastCGI : recv header error: " .. (rcvERR or "Unknown") end hdr = _unpack_hdr(FCGI.HEADER_FORMAT, hdrByte) if not hdr then ngx.log(ngx.ERR, "Unable to parse FCGI record header : " .. (rcvERR or "Unknown")) return nil, "Unable to parse FCGI record header : " .. (rcvERR or "Unknown") end typ = hdr.type rcvClen = hdr.content_length rcvPad = hdr.padding_length ngx.log(ngx.DEBUG, "New content length is " .. rcvClen .. " padding ", rcvPad) if rcvClen > 0 then read_bytes, rcvERR, partial = self.fcgiSocket:receive(rcvClen) if not read_bytes or partial then ngx.log(ngx.ERR, "fastCGI : recv content error: " .. (rcvERR or "Unknown")) return nil, "fastCGI : recv content error: " .. (rcvERR or "Unknown") end end if rcvClen <= 65535 then bytes_to_read = rcvClen else bytes_to_read = 65535 end if bytes_to_read > 0 then read_data, rcvERR, partial = self.fcgiSocket:receive(bytes_to_read) if not read_data then return nil, "Unable to retrieve request body: " .. rcvERR .. ' < ' .. partial .. ' >' end rcvClen = rcvClen - bytes_to_read ngx.log(ngx.DEBUG, "Reducing content length by ", bytes_to_read, " bytes to ", rcvClen) end if typ == FCGI.STDOUT then if #read_bytes > 0 then if not parsed_headers then stdout = stdout .. read_bytes sep = stdout:find("\r\n\r\n", 1, true) if sep then raw = stdout:sub(1, sep - 1) rest = stdout:sub(sep + 4) for line in raw:gmatch("([^\r\n]+)") do hn, hv = line:match("^([^:]+):%s*(.*)") if hn then hName = hn:lower() if hName == "status" then ngx.status = tonumber(hv) or ngx.status elseif hName == "content-type" then ngx.header["Content-Type"] = hv else ngx.header[hn] = hv end end end parsed_headers = true ngx.print(rest) end else ngx.print(read_bytes) end end elseif typ == FCGI.STDERR and #read_bytes > 0 then stderr[#stderr + 1] = read_bytes ngx.log(ngx.ERR, "fastCGI : FastCGI stderr: ", (read_bytes or "Unknown")) if read_bytes:find("PHP Fatal error", 1, true) then ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR ngx.say(read_bytes) ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end elseif typ == FCGI.END_REQUEST then break else ngx.log(ngx.ERR, "fastCGI : Attempted to receive an unknown FCGI record = " .. typ) ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end if rcvClen <= 0 and rcvPad > 0 then _, rcvERR = self.fcgiSocket:receive(rcvPad) if not read_bytes then ngx.log(ngx.ERR, "fastCGI : recv content error: " .. (rcvERR or "Unknown")) return nil, "fastCGI : recv content error: " .. (rcvERR or "Unknown") end end end for _, h in ipairs(opts.hide_headers or {}) do ngx.header[h] = nil end if #stderr > 0 and opts.intercept_errors then ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR ngx.say("Internal server error") return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end if not ngx.var["skip_cache"] then cacheMethod = false for _,method in ipairs(opts.cacheMethods or {}) do if ngx.req.get_method() == method then cacheMethod = true end end if cacheMethod and ngx.status == 200 and opts.cache_valid then if not cache == nil then cache:set(cache_key, table.concat { stdout:sub((parsed_headers and 1 or 0)) }, opts.cache_valid) end end end end, debug.traceback, self, params, opts) if not ok then ngx.log(ngx.ERR, "fastCGI : execution error: ", (err or "Unknown")) end cleanup(ok) if not ok then return nil, err end local stdinOK, sendERR, stdinPartial = _send_stdin(self.sock, stdin) if not stdinOK then return nil, "fastCGI : Failed to send stdin: " .. (sendERR or "Unkown error") .. '< ' .. (stdinPartial or 'Unknown') .. ' >' end return ngx.OK, nil end return _M 

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.