Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b88b6251f9 | |||
| 2b05c4e544 | |||
| 4167409ccf | |||
| 07572f8e6c | |||
| da68d078b0 |
24
Dockerfile
24
Dockerfile
@@ -1,15 +1,21 @@
|
|||||||
FROM nginx:alpine
|
FROM openresty/openresty:alpine
|
||||||
|
|
||||||
# Copy nginx configuration and HTML
|
# Copy config (OpenResty uses this path)
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf
|
COPY nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
|
||||||
|
# HTML and data dirs (config references /usr/share/nginx/html and /etc/nginx/data)
|
||||||
|
RUN mkdir -p /usr/share/nginx/html /etc/nginx/data/cache /etc/nginx/data/temp
|
||||||
COPY index.html /usr/share/nginx/html/index.html
|
COPY index.html /usr/share/nginx/html/index.html
|
||||||
|
|
||||||
# Create data directories
|
# When volume is mounted at /etc/nginx/data, ensure cache/temp exist and are writable (fix rename Permission denied)
|
||||||
RUN mkdir -p /etc/nginx/data/cache /etc/nginx/data/temp && \
|
RUN echo '#!/bin/sh' > /docker-entrypoint.sh \
|
||||||
chown -R nginx:nginx /etc/nginx/data
|
&& echo 'set -e' >> /docker-entrypoint.sh \
|
||||||
|
&& echo 'mkdir -p /etc/nginx/data/cache /etc/nginx/data/temp' >> /docker-entrypoint.sh \
|
||||||
|
&& echo 'chown -R nobody:nobody /etc/nginx/data 2>/dev/null || true' >> /docker-entrypoint.sh \
|
||||||
|
&& echo 'chmod -R 755 /etc/nginx/data' >> /docker-entrypoint.sh \
|
||||||
|
&& echo 'exec "$@"' >> /docker-entrypoint.sh \
|
||||||
|
&& chmod +x /docker-entrypoint.sh
|
||||||
|
|
||||||
# Expose port
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
# Start nginx
|
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"]
|
||||||
|
|||||||
85
nginx.conf
85
nginx.conf
@@ -18,22 +18,76 @@ http {
|
|||||||
|
|
||||||
resolver 127.0.0.11 valid=60s;
|
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 {
|
server {
|
||||||
listen 3000;
|
listen 3000;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
# If no url parameter, serve the readme
|
# Return immediately for OPTIONS so cache/proxy are never involved (avoids "if" affecting cache)
|
||||||
if ($arg_url = "") {
|
if ($request_method = 'OPTIONS') {
|
||||||
rewrite ^ /index.html last;
|
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 "";
|
||||||
proxy_pass $arg_url;
|
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_http_version 1.1;
|
||||||
proxy_set_header Host $proxy_host;
|
proxy_set_header Host $proxy_host;
|
||||||
proxy_ssl_server_name on;
|
proxy_ssl_server_name on;
|
||||||
|
|
||||||
# Strip upstream CORS so we only send our own (duplicate = browser reject)
|
# 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 Access-Control-Allow-Origin;
|
||||||
|
proxy_hide_header Set-Cookie;
|
||||||
proxy_hide_header Access-Control-Allow-Methods;
|
proxy_hide_header Access-Control-Allow-Methods;
|
||||||
proxy_hide_header Access-Control-Allow-Headers;
|
proxy_hide_header Access-Control-Allow-Headers;
|
||||||
proxy_hide_header Access-Control-Expose-Headers;
|
proxy_hide_header Access-Control-Expose-Headers;
|
||||||
@@ -42,20 +96,21 @@ http {
|
|||||||
# CORS headers — replace with our own *
|
# CORS headers — replace with our own *
|
||||||
add_header Access-Control-Allow-Origin * always;
|
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-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-NoCache,X-Status" 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;
|
add_header Access-Control-Expose-Headers "X-Cache-Status" always;
|
||||||
|
|
||||||
# Handle preflight OPTIONS requests
|
|
||||||
if ($request_method = 'OPTIONS') {
|
|
||||||
return 204;
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_cache api_cache;
|
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;
|
proxy_cache_valid 200 201 202 203 204 205 206 207 208 226 365d;
|
||||||
proxy_ignore_headers Cache-Control Expires;
|
# Ignore headers that would prevent storing the response
|
||||||
|
proxy_ignore_headers Cache-Control Expires Set-Cookie Vary X-Accel-Expires X-Accel-Redirect;
|
||||||
|
|
||||||
proxy_cache_bypass $http_x_nocache;
|
# X-Cache-Purge: bypass cache for this request (don't serve cached) but DO store the new response (overwrites that key)
|
||||||
proxy_no_cache $http_x_nocache;
|
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;
|
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
|
||||||
|
|
||||||
|
|||||||
14
test.sh
Normal file
14
test.sh
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
docker compose down
|
||||||
|
|
||||||
|
# Rebuild so current nginx.conf is copied into the image (no volume mount for config)
|
||||||
|
docker compose up -d --build
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
curl 'http://localhost:3000/?url=https://clickhouse-zkill.site.quack-lab.dev/?database=zkill&default_format=JSON' -H 'accept: */*' -H 'accept-language: en-US' -H 'authorization: Basic emtpbGxfcm9fdXNlcjp2Mk05WmdqWWtxeXB5UzRuZTlKdEs3QllwNjk0MnVqdQ==' -H 'content-type: text/plain; charset=utf-8' -H 'origin: http://localhost:8080' -H 'priority: u=1, i' -H 'referer: http://localhost:8080/' -H 'sec-ch-ua: "Not_A Brand";v="99", "Chromium";v="142"' -H 'sec-ch-ua-mobile: ?0' -H 'sec-ch-ua-platform: "Windows"' -H 'sec-fetch-dest: empty' -H 'sec-fetch-mode: cors' -H 'sec-fetch-site: cross-site' -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Cursor/2.4.21 Chrome/142.0.7444.235 Electron/39.2.7 Safari/537.36' --data-raw $'\n SELECT\n corporation_name,\n alliance_name,\n uniq(killmail_id) as kills_participated,\n uniq(character_name) as members_involved\n FROM zkill.killmail_attackers\n WHERE 1=1\n \n GROUP BY corporation_name, alliance_name\n ORDER BY kills_participated DESC\n '
|
||||||
|
curl 'http://localhost:3000/?url=https://clickhouse-zkill.site.quack-lab.dev/?database=zkill&default_format=JSON' -H 'accept: */*' -H 'accept-language: en-US' -H 'authorization: Basic emtpbGxfcm9fdXNlcjp2Mk05WmdqWWtxeXB5UzRuZTlKdEs3QllwNjk0MnVqdQ==' -H 'content-type: text/plain; charset=utf-8' -H 'origin: http://localhost:8080' -H 'priority: u=1, i' -H 'referer: http://localhost:8080/' -H 'sec-ch-ua: "Not_A Brand";v="99", "Chromium";v="142"' -H 'sec-ch-ua-mobile: ?0' -H 'sec-ch-ua-platform: "Windows"' -H 'sec-fetch-dest: empty' -H 'sec-fetch-mode: cors' -H 'sec-fetch-site: cross-site' -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Cursor/2.4.21 Chrome/142.0.7444.235 Electron/39.2.7 Safari/537.36' --data-raw $'\n SELECT\n corporation_name,\n alliance_name,\n uniq(killmail_id) as kills_participated,\n uniq(character_name) as members_involved\n FROM zkill.killmail_attackers\n WHERE 1=1\n \n GROUP BY corporation_name, alliance_name\n ORDER BY kills_participated DESC\n '
|
||||||
|
|
||||||
|
sleep 3
|
||||||
|
docker compose logs
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
head result.json
|
||||||
Reference in New Issue
Block a user