events { worker_connections 1024; } http { client_body_temp_path /etc/nginx/data/temp; proxy_temp_path /etc/nginx/data/temp; fastcgi_temp_path /etc/nginx/data/temp; uwsgi_temp_path /etc/nginx/data/temp; scgi_temp_path /etc/nginx/data/temp; proxy_cache_path /etc/nginx/data/cache levels=1:2 keys_zone=api_cache:10m max_size=100g inactive=365d; error_log /dev/stdout warn; log_format main '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" cache:$upstream_cache_status'; access_log /dev/stdout main; resolver 127.0.0.11 valid=60s; # Raw url= value from request (stops at next & so %26 in encoded URLs is preserved) map $request_uri $url_encoded { default ""; "~*[?&]url=((?:[^&%]|%[0-9A-Fa-f][0-9A-f])*)(?:&|$)" $1; } server { listen 3000; location / { # Return immediately for OPTIONS so cache/proxy are never involved (avoids "if" affecting cache) if ($request_method = 'OPTIONS') { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH"; add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-Cache,X-Cache-Purge,X-Status"; add_header Access-Control-Max-Age 86400; return 204; } set $backend_base ""; set $body_for_cache_key ""; # Decode url, build upstream URL, strip our "url=". Read body for cache key (so POST is keyed by body). rewrite_by_lua_block { ngx.req.read_body() local body = ngx.req.get_body_data() if not body and ngx.req.get_body_file() then local f = io.open(ngx.req.get_body_file(), "rb") if f then body = f:read("*a"); f:close() end end ngx.var.body_for_cache_key = ngx.md5(body or "") local enc = ngx.var.url_encoded local decoded = (enc and enc ~= "") and ngx.unescape_uri(enc) or ngx.var.arg_url or "" if decoded == "" then ngx.exec("/index.html") return end local args = ngx.var.args or "" local rest = args:gsub("^url=[^&]*&?", ""):gsub("&url=[^&]*", ""):gsub("^url=[^&]*$", "") local full = decoded if rest ~= "" then local sep = decoded:find("?") and "&" or "?" full = decoded .. sep .. rest end local scheme, host, pathquery = full:match("^(https?)://([^/]+)(.*)$") if not host then ngx.status = 400 ngx.say("invalid url") return ngx.exit(400) end if pathquery == "" then pathquery = "/" end local path = pathquery:match("^([^?]*)") or "/" local query = pathquery:match("%?(.*)$") or "" ngx.var.backend_base = scheme .. "://" .. host ngx.req.set_uri(path) ngx.req.set_uri_args((query:gsub("^%?", ""))) } proxy_pass $backend_base$uri$is_args$args; proxy_http_version 1.1; proxy_set_header Host $proxy_host; proxy_ssl_server_name on; # Cache key is built from variables; must fit in proxy_buffer_size (body part is MD5 in Lua) proxy_buffer_size 16k; proxy_buffering on; proxy_buffers 4 256k; proxy_busy_buffers_size 256k; proxy_max_temp_file_size 1024m; # Strip upstream CORS and Set-Cookie so we only send our own CORS and don't leak cookies proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Set-Cookie; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; # CORS headers — replace with our own * add_header Access-Control-Allow-Origin * always; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH" always; add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-Cache,X-Cache-Purge,X-Status" always; add_header Access-Control-Expose-Headers "X-Cache-Status" always; proxy_cache api_cache; proxy_cache_methods GET HEAD POST; proxy_cache_key $backend_base$request_method$uri$is_args$args$body_for_cache_key; proxy_cache_valid 200 201 202 203 204 205 206 207 208 226 365d; # Ignore headers that would prevent storing the response proxy_ignore_headers Cache-Control Expires Set-Cookie Vary X-Accel-Expires X-Accel-Redirect; # X-Cache-Purge: bypass cache for this request (don't serve cached) but DO store the new response (overwrites that key) set $bypass_cache 0; if ($http_x_cache_purge != "") { set $bypass_cache 1; } proxy_cache_bypass $bypass_cache; proxy_no_cache 0; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; add_header X-Cache-Status $upstream_cache_status always; } location = /index.html { root /usr/share/nginx/html; # CORS headers for HTML interface add_header Access-Control-Allow-Origin * always; add_header Access-Control-Allow-Methods "GET, OPTIONS" always; add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type" always; # Handle preflight OPTIONS requests if ($request_method = 'OPTIONS') { return 204; } } } }