local cjson = require "cjson" local redirect_follow = require "sm.service.redirect_follow" local _M = {} local function parse_upstream_status(status) local parsed = tostring(status or ""):match("(%d%d%d)") return tonumber(parsed) or ngx.HTTP_MOVED_TEMPORARILY end local function decode_site_config() local site_json = ngx.var.site if not site_json or site_json == "" then return nil, "site config missing" end local ok, hostname = pcall(cjson.decode, site_json) if not ok or type(hostname) ~= "table" then return nil, hostname end return hostname, nil end local function apply_response_headers(headers) for header_name, header_value in pairs(headers or {}) do local lower_name = tostring(header_name):lower() if lower_name ~= "connection" and lower_name ~= "keep-alive" and lower_name ~= "transfer-encoding" and lower_name ~= "age" and lower_name ~= "agc-cache-status" and lower_name ~= "agc-cache-policy" and lower_name ~= "agc-swift-cachetime" and lower_name ~= "agc-swift-savetime" then ngx.header[header_name] = header_value end end end function _M.exit_redirect(status, location) if location and location ~= "" then ngx.header["Location"] = location end return ngx.exit(status) end function _M.resolve_follow_mode(hostname) local route_lane = ngx.var.route_lane or "" if route_lane:match("follow$") and hostname["redirect_follow"] == "true" then return "follow" end return "passthrough" end function _M.apply_follow_result(result) ngx.header["AGC-Redirect-Follow"] = tostring(result.followed or 0) if result.kind == "redirect" then return _M.exit_redirect(result.status or ngx.HTTP_MOVED_TEMPORARILY, result.location) end ngx.status = result.status or ngx.HTTP_OK apply_response_headers(result.headers) ngx.ctx.follow_cache_meta = result.cache_meta if result.body and result.body ~= "" then ngx.print(result.body) end end function _M.redirect_dispatch() local redirect_location = ngx.var.upstream_http_location local redirect_status = parse_upstream_status(ngx.var.upstream_status) local hostname, err = decode_site_config() if not hostname then ngx.log(ngx.ERR, "[Content] Failed to decode site config: " .. tostring(err)) return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end if not redirect_location or redirect_location == "" then ngx.log(ngx.ERR, "[Content] No redirect location found") return ngx.exit(ngx.HTTP_BAD_GATEWAY) end local follow_mode = _M.resolve_follow_mode(hostname) ngx.log(ngx.INFO, "[Content] redirect dispatch lane=" .. tostring(ngx.var.route_lane) .. " mode=" .. follow_mode .. " status=" .. redirect_status .. " location=" .. redirect_location) if follow_mode ~= "follow" then return _M.exit_redirect(redirect_status, redirect_location) end local backend_protocol = ngx.var.backend_protocol local orig_scheme = ngx.var.scheme or "http" if backend_protocol and backend_protocol ~= "" then orig_scheme = backend_protocol:gsub("://", "") end local result, follow_err = redirect_follow.follow({ route_lane = ngx.var.route_lane, status = redirect_status, location = redirect_location, orig_scheme = orig_scheme, orig_host = ngx.var.host, orig_args = ngx.var.args, orig_headers = ngx.req.get_headers(), method = ngx.req.get_method(), max_redirects = hostname["redirect_follow_max"], keep_args = hostname["redirect_follow_keep_args"], keep_headers = hostname["redirect_follow_keep_headers"], redirect_follow_host = hostname["redirect_follow_host"], cache_config = ngx.var.cache_config, cache_key = ngx.var.cache_key, }) if not result then ngx.log(ngx.ERR, "[Content] Redirect follow failed: " .. tostring(follow_err)) return ngx.exit(ngx.HTTP_BAD_GATEWAY) end return _M.apply_follow_result(result) end return _M