Skip to content

Instantly share code, notes, and snippets.

@MayankFawkes
Last active January 23, 2023 09:31
Show Gist options
  • Save MayankFawkes/2440fe8dae89b2f6a2d80aa4481c4bc4 to your computer and use it in GitHub Desktop.
Save MayankFawkes/2440fe8dae89b2f6a2d80aa4481c4bc4 to your computer and use it in GitHub Desktop.
Personal CDN with Nginx and Golang | in nginx.conf you can add 443 with SSL if you want | thanks to https://bit.ly/3XM7wQm
package main
import (
"net/http"
"io/ioutil"
"log"
"fmt"
)
var print = fmt.Println;
func send(url string) ([]byte, *http.Response, error) {
resp, err := http.Get(url)
if err!=nil {
log.Println(err)
return []byte{}, resp, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err)
return []byte{}, resp, err
}
return body, resp, nil
}
func main() {
http.HandleFunc("/", HelloServer)
log.Println("Server listening on http://0.0.0.0:8000 or http://127.0.0.1:8000")
http.ListenAndServe(":8000", nil)
}
func indexResponse(w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Welcome to CDN Proxy Node, I only support HTTPS.")
}
func notFoundResponse(w http.ResponseWriter) {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "Invalid URL")
}
func HelloServer(w http.ResponseWriter, r *http.Request) {
var path string = r.URL.Path[1:]
if path == "" {
indexResponse(w)
return
}
var url string
if path == "favicon.ico" {
url = "https://www.google.com/favicon.ico"
} else {
url = "https://" + r.URL.Path[1:]
}
body, resp, err := send(url)
if err != nil {
notFoundResponse(w)
return
}
for key, value := range resp.Header {
if key == "Set-Cookie" || key == "content-type" {
continue
}
w.Header().Set(key, value[0])
}
w.Header().Set("x-server", "Hekor-Boi-CDN/1337")
w.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"", url))
log.Println(url, resp.Status)
w.WriteHeader(resp.StatusCode)
w.Write(body)
}
load_module modules/ngx_http_headers_more_filter_module.so;
worker_processes 1;
worker_rlimit_nofile 100000;
pcre_jit on;
events {
use epoll;
worker_connections 16000;
multi_accept on;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] $status '
'"$request" $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# IP whitelist to which no conn/rate restrictions should be applied
geo $ip_whitelist {
default 0;
127.0.0.1 1;
10.225.1.0/24 1;
}
map_hash_bucket_size 256;
map $ip_whitelist $limited_ip {
0 $binary_remote_addr;
1 "";
}
limit_conn_zone $limited_ip zone=connsPerIP:20m;
limit_conn connsPerIP 30;
limit_conn_status 429;
limit_req_zone $limited_ip zone=reqsPerMinutePerIP:50m rate=500r/m;
limit_req zone=reqsPerMinutePerIP burst=700 nodelay;
limit_req_status 429;
client_max_body_size 64k;
client_header_timeout 10s;
client_body_timeout 10s;
client_body_buffer_size 16k;
client_header_buffer_size 4k;
send_timeout 10s;
connection_pool_size 512;
large_client_header_buffers 8 16k;
request_pool_size 4k;
http2_idle_timeout 60s;
http2_recv_timeout 10s;
http2_chunk_size 16k;
server_tokens off;
more_set_headers "Server: My-CDN";
include /etc/nginx/mime.types;
variables_hash_bucket_size 128;
gzip on;
gzip_static on; # searches for the *.gz file and returns it directly from disk (compression is provided by our extra process in the background)
gzip_disable "msie6";
gzip_min_length 4096;
gzip_buffers 16 64k;
gzip_vary on;
gzip_proxied any;
gzip_types image/svg+xml text/plain text/css application/json application/x-javascript application/javascript text/xml application/xml application/xml+rss text/javascript text/x-component font/truetype font/opentype image/x-icon;
gzip_comp_level 4;
output_buffers 1 32k;
postpone_output 1460;
sendfile on;
sendfile_max_chunk 1m;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 10 10;
ignore_invalid_headers on;
reset_timedout_connection on;
open_file_cache max=50000 inactive=30s;
open_file_cache_valid 10s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 64 16k;
proxy_temp_path /var/lib/nginx/proxy;
proxy_cache_min_uses 2;
proxy_ignore_client_abort on;
proxy_intercept_errors on;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_redirect off;
proxy_connect_timeout 60;
proxy_send_timeout 180;
proxy_cache_lock on;
proxy_read_timeout 10s;
# setting up trusted IP subnets to respect X-Forwarded-For header (for multi-level proxy setup)
set_real_ip_from 127.0.0.1/32;
set_real_ip_from 10.1.2.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
############################################################################
## Example configuration for: ##
## https://serverip:80/myorigin.com/* -> https://www.myorigin.com/* ##
############################################################################
upstream up_proxy_node {
server localhost:8000 max_conns=50;
keepalive 20;
keepalive_requests 50;
keepalive_timeout 5s;
}
proxy_cache_path /var/lib/nginx/tmp/proxy/global_cache levels=1:2 keys_zone=cache_up_proxy_node:20m inactive=720h max_size=10g;
server {
listen 80 default_server backlog=32768;
lingering_close on;
lingering_time 10s;
lingering_timeout 5s;
resolver 127.0.0.1; # dnsmasq with logging to get an idea of the DNS traffic that Nginx is doing
location ~* ^/(.+\.(css|js|jpg|jpeg|png|gif|ico))$ {
set $origin_uri "/$1$is_args$args";
root /var/www/html;
access_log /var/log/nginx/ssl.access.log main buffer=4k flush=5m;
error_log /var/log/nginx/ssl.error.log notice;
if ($request_method !~ ^(GET|HEAD|OPTIONS)$ ) {
more_set_headers "Content-Type: application/json";
return 405 '{"code": 405, "message": "Method Not Allowed"}';
}
more_clear_headers "Strict-Transport-Security";
more_set_headers "Strict-Transport-Security: max-age=31536000";
more_set_headers "X-Content-Type-Options: nosniff";
expires 1y; # enforce caching in browsers for 1 year (use only consciously, if you are sure that when you change the content of the file on the original, the URL will also change)
set $headerCorsAllowOrigin "";
if ($request_method = 'OPTIONS') {
more_set_headers "Access-Control-Allow-Origin: $headerCorsAllowOrigin";
more_set_headers "Access-Control-Allow-Methods: GET, HEAD, OPTIONS";
more_set_headers "Access-Control-Max-Age: 3600";
more_set_headers "Content-Length: 0";
return 204;
}
set $webp "";
set $file_for_webp "";
if ($http_accept ~* webp) {
set $webp "A";
}
if ($request_filename ~ (.+\.(png|jpe?g))$) {
set $file_for_webp $1;
}
if (-f $file_for_webp.webp) {
set $webp "${webp}E";
}
if ($webp = AE) {
rewrite ^/(.+)$ /webp/$1 last;
}
proxy_cache cache_up_proxy_node;
proxy_cache_key "$proxy_host$request_uri"; # we don't need a schema because we only support HTTPS
proxy_cache_use_stale error timeout invalid_header updating http_429 http_500 http_502 http_503 http_504;
proxy_read_timeout 20s;
proxy_cache_valid 200 720h;
proxy_cache_valid 301 4h;
proxy_cache_valid 302 1h;
proxy_cache_valid 400 401 403 404 30s;
proxy_cache_valid 500 501 502 503 30s;
proxy_cache_valid 429 10s;
# due to keep-alive on origins
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header "Via" "My-CDN";
proxy_set_header Accept-Encoding ""; # we always want to receive and cache RAW content from the origin, because we have a process for preparing static *.gz and *.br versions
proxy_set_header Host dotin.us;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Proto $scheme;
add_header X-Cache-Status $upstream_cache_status;
if (!-f $request_filename) {
proxy_pass http://up_proxy_node$origin_uri;
}
}
# internal location for webp
location ~* ^/webp(/(.*))$ {
internal;
root /var/www/html;
set $origin_uri "/$1$is_args$args";
access_log /var/log/nginx/ssl.access.webp.log main buffer=4k flush=5m;
expires 366d;
more_clear_headers 'Vary';
more_set_headers "Vary: Accept";
more_set_headers "X-Cache: HIT";
try_files $1.webp $1 =404;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment