Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
7ee6480b2f |
11 changed files with 160 additions and 607 deletions
|
@ -3,6 +3,7 @@
|
||||||
.dockerignore
|
.dockerignore
|
||||||
Dockerfile
|
Dockerfile
|
||||||
LICENSE
|
LICENSE
|
||||||
|
.git
|
||||||
.github
|
.github
|
||||||
.gitignore
|
.gitignore
|
||||||
cache
|
cache
|
||||||
|
|
4
.env
4
.env
|
@ -1,4 +0,0 @@
|
||||||
VPN_PROVIDER=""
|
|
||||||
WIREGUARD_KEY=""
|
|
||||||
WIREGUARD_ADDRESSES=""
|
|
||||||
SERVER_HOSTNAMES=""
|
|
|
@ -14,20 +14,10 @@ jobs:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# - name: Set up QEMU for ARM64 builds
|
- name: Set up QEMU for ARM64 builds
|
||||||
# uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
# with:
|
|
||||||
# platforms: arm64
|
|
||||||
|
|
||||||
- name: Cache Go packages
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
with:
|
||||||
path: |
|
platforms: arm64
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-golang-
|
|
||||||
|
|
||||||
- name: Setup Docker BuildX system
|
- name: Setup Docker BuildX system
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
@ -39,7 +29,7 @@ jobs:
|
||||||
username: ${{ secrets.USERNAME }}
|
username: ${{ secrets.USERNAME }}
|
||||||
password: ${{ secrets.TOKEN }}
|
password: ${{ secrets.TOKEN }}
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta AMD64
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
|
@ -48,11 +38,39 @@ jobs:
|
||||||
type=sha,format=short,prefix={{date 'YYYY.MM.DD'}}-,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
|
type=sha,format=short,prefix={{date 'YYYY.MM.DD'}}-,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
|
||||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
|
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
|
||||||
|
|
||||||
- name: Build and push Docker AMD64/ARM64
|
- name: Build and push Docker AMD64 image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
# platforms: linux/amd64,linux/arm64/v8
|
# labels: ${{ steps.meta.outputs.labels }}
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
|
||||||
|
- name: Docker meta ARM64
|
||||||
|
id: meta-arm64
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: git.nadeko.net/fijxu/http3-ytproxy
|
||||||
|
flavor: |
|
||||||
|
suffix=-arm64
|
||||||
|
tags: |
|
||||||
|
type=sha,format=short,prefix={{date 'YYYY.MM.DD'}}-,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
|
||||||
|
type=raw,value=master,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
|
||||||
|
|
||||||
|
- name: Build and push Docker ARM64
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/arm64/v8
|
||||||
|
# labels: ${{ steps.meta-arm64.outputs.labels }}
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta-arm64.outputs.tags }}
|
||||||
|
|
||||||
|
# - name: Build images
|
||||||
|
# uses: https://code.forgejo.org/docker/build-push-action@v5
|
||||||
|
# with:
|
||||||
|
# context: .
|
||||||
|
# tags: git.nadeko.net/fijxu/http3-ytproxy:latest
|
||||||
|
# platforms: linux/amd64,linux/arm64/v8
|
||||||
|
# push: true
|
||||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -16,8 +16,3 @@
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
# Dependency directories (remove the comment below to include it)
|
||||||
# vendor/
|
# vendor/
|
||||||
|
|
||||||
# Certificates!
|
|
||||||
*.pem
|
|
||||||
*.cer
|
|
||||||
*.key
|
|
16
Dockerfile
16
Dockerfile
|
@ -1,18 +1,15 @@
|
||||||
FROM golang:alpine3.21 AS build
|
FROM golang:alpine AS build
|
||||||
|
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
|
||||||
RUN apk add --no-cache build-base libwebp-dev git
|
RUN apk add --no-cache build-base libwebp-dev
|
||||||
|
|
||||||
COPY .git .git
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||||
go build -ldflags "-s -w -X 'main.version=$(date '+%Y-%m-%d')-$(git rev-list --abbrev-commit -1 HEAD)'"
|
go build -ldflags "-s -w"
|
||||||
|
|
||||||
FROM alpine:3.21
|
FROM alpine:edge
|
||||||
|
|
||||||
RUN adduser -u 10001 -S appuser
|
|
||||||
|
|
||||||
RUN apk add --no-cache libwebp
|
RUN apk add --no-cache libwebp
|
||||||
|
|
||||||
|
@ -20,7 +17,4 @@ WORKDIR /app/
|
||||||
|
|
||||||
COPY --from=build /app/http3-ytproxy /app/http3-ytproxy
|
COPY --from=build /app/http3-ytproxy /app/http3-ytproxy
|
||||||
|
|
||||||
# Switch to non-privileged user
|
CMD ./http3-ytproxy -l 0.0.0.0
|
||||||
USER appuser
|
|
||||||
|
|
||||||
ENTRYPOINT ["/app/http3-ytproxy"]
|
|
||||||
|
|
|
@ -1,44 +1,22 @@
|
||||||
# Docker compose file for http3-ytproxy used in inv.nadeko.net
|
# Docker compose file for http3-proxy used in Invidious
|
||||||
|
|
||||||
services:
|
services:
|
||||||
http3-proxy:
|
|
||||||
build: .
|
|
||||||
image: git.nadeko.net/fijxu/http3-ytproxy:latest
|
|
||||||
restart: always
|
|
||||||
# Uncomment this IF YOU ARE using gluetun!
|
|
||||||
# network_mode: "service:gluetun"
|
|
||||||
# Uncomment this IF YOU ARE NOT using gluetun!
|
|
||||||
# ports:
|
|
||||||
# - "0.0.0.0:8443:8443/tcp" # HTTP/2
|
|
||||||
# - "0.0.0.0:8443:8443/udp" # HTTP/3 (QUIC)
|
|
||||||
# Make sure that the key and the certificate files exist!
|
|
||||||
volumes:
|
|
||||||
- ./key.key:/data/key.key:ro
|
|
||||||
- ./fullchain.pem:/data/cert.pem:ro
|
|
||||||
depends_on:
|
|
||||||
gluetun:
|
|
||||||
condition: service_healthy
|
|
||||||
# Needed for HTTP/3, otherwise, quic-go will output this depending of the machine:
|
|
||||||
# "failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 7168 kiB, got: 416 kiB).
|
|
||||||
# See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details."
|
|
||||||
cap_add:
|
|
||||||
- NET_ADMIN
|
|
||||||
|
|
||||||
# You can comment this whole service if you are not going to use Gluetun at all
|
http3-proxy:
|
||||||
gluetun:
|
image: git.nadeko.net/fijxu/http3-proxy:latest
|
||||||
image: qmcgaw/gluetun:latest
|
restart: unless-stopped
|
||||||
restart: always
|
deploy:
|
||||||
ports:
|
replicas: 6
|
||||||
# THIS IS ACTUALLY THE PORT OF HTTP3-PROXY
|
environment:
|
||||||
# SINCE THE HTTP3-PTOXY SERVICE IS RUNNING
|
DISABLE_WEBP: 1
|
||||||
# UNDER GLUETUN NETWORK.
|
|
||||||
- "0.0.0.0:8443:8443/tcp" # HTTP/2
|
http3-proxy-nginx:
|
||||||
- "0.0.0.0:8443:8443/udp" # HTTP/3 (QUIC)
|
image: nginx:latest
|
||||||
env_file:
|
restart: unless-stopped
|
||||||
- .env
|
volumes:
|
||||||
cap_add:
|
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
- NET_ADMIN
|
depends_on:
|
||||||
devices:
|
- http3-proxy
|
||||||
- /dev/net/tun:/dev/net/tun
|
ports:
|
||||||
volumes:
|
- "127.0.0.1:10012:3000"
|
||||||
- ./gluetun:/gluetun
|
|
||||||
|
|
11
go.mod
11
go.mod
|
@ -7,18 +7,9 @@ toolchain go1.23.0
|
||||||
require github.com/quic-go/quic-go v0.48.1
|
require github.com/quic-go/quic-go v0.48.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
|
||||||
github.com/conduitio/bwlimit v0.1.0 // indirect
|
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20241029010322-833c56d90c8e // indirect
|
github.com/google/pprof v0.0.0-20241029010322-833c56d90c8e // indirect
|
||||||
github.com/klauspost/compress v1.17.9 // indirect
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
|
||||||
github.com/onsi/ginkgo/v2 v2.20.2 // indirect
|
github.com/onsi/ginkgo/v2 v2.20.2 // indirect
|
||||||
github.com/prometheus/client_golang v1.20.5 // indirect
|
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
|
||||||
github.com/prometheus/common v0.55.0 // indirect
|
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
go.uber.org/mock v0.5.0 // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
golang.org/x/crypto v0.28.0 // indirect
|
golang.org/x/crypto v0.28.0 // indirect
|
||||||
|
@ -28,7 +19,5 @@ require (
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
golang.org/x/text v0.19.0 // indirect
|
golang.org/x/text v0.19.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
|
||||||
golang.org/x/tools v0.26.0 // indirect
|
golang.org/x/tools v0.26.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.2 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
18
go.sum
18
go.sum
|
@ -1,7 +1,3 @@
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||||
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
|
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
|
||||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||||
|
@ -24,26 +20,14 @@ github.com/google/pprof v0.0.0-20241029010322-833c56d90c8e h1:v7R0PZoC2p1KWQmv1+
|
||||||
github.com/google/pprof v0.0.0-20241029010322-833c56d90c8e/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
github.com/google/pprof v0.0.0-20241029010322-833c56d90c8e/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465 h1:KwWnWVWCNtNq/ewIX7HIKnELmEx2nDP42yskD/pi7QE=
|
github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465 h1:KwWnWVWCNtNq/ewIX7HIKnELmEx2nDP42yskD/pi7QE=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
|
||||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
|
||||||
github.com/kolesa-team/go-webp v1.0.4 h1:wQvU4PLG/X7RS0vAeyhiivhLRoxfLVRlDq4I3frdxIQ=
|
github.com/kolesa-team/go-webp v1.0.4 h1:wQvU4PLG/X7RS0vAeyhiivhLRoxfLVRlDq4I3frdxIQ=
|
||||||
github.com/kolesa-team/go-webp v1.0.4/go.mod h1:oMvdivD6K+Q5qIIkVC2w4k2ZUnI1H+MyP7inwgWq9aA=
|
github.com/kolesa-team/go-webp v1.0.4/go.mod h1:oMvdivD6K+Q5qIIkVC2w4k2ZUnI1H+MyP7inwgWq9aA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
|
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
|
||||||
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
|
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
|
||||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
|
||||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
|
||||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
|
||||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
|
||||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
|
||||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7Y=
|
github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7Y=
|
||||||
|
@ -94,8 +78,6 @@ golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|
144
httppaths.go
144
httppaths.go
|
@ -1,82 +1,30 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func forbiddenChecker(resp *http.Response, w http.ResponseWriter) error {
|
|
||||||
if resp.StatusCode == 403 {
|
|
||||||
w.WriteHeader(403)
|
|
||||||
io.WriteString(w, "Forbidden 403\n")
|
|
||||||
io.WriteString(w, "Maybe Youtube blocked the IP of this proxy?\n")
|
|
||||||
return fmt.Errorf("%s returned %d", resp.Request.Host, resp.StatusCode)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func connectionChecker(ctx context.Context) bool {
|
|
||||||
// To check if the connection has been closed. To prevent
|
|
||||||
// doing a useless request to google servers
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func videoplayback(w http.ResponseWriter, req *http.Request) {
|
func videoplayback(w http.ResponseWriter, req *http.Request) {
|
||||||
q := req.URL.Query()
|
q := req.URL.Query()
|
||||||
|
|
||||||
expire, err := strconv.ParseInt(q.Get("expire"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(500)
|
|
||||||
io.WriteString(w, "Expire query string undefined")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent the process of already expired playbacks
|
|
||||||
// since they will return 403 from googlevideo server
|
|
||||||
if (expire - time.Now().Unix()) <= 0 {
|
|
||||||
w.WriteHeader(403)
|
|
||||||
io.WriteString(w, "Videoplayback URL has expired.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c := q.Get("c")
|
|
||||||
// if c == "" {
|
|
||||||
// w.WriteHeader(400)
|
|
||||||
// io.WriteString(w, "'c' query string undefined.")
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
host := q.Get("host")
|
host := q.Get("host")
|
||||||
q.Del("host")
|
|
||||||
|
|
||||||
if len(host) <= 0 {
|
if len(host) <= 0 {
|
||||||
// Fallback to use mvi and mn to build a host
|
|
||||||
mvi := q.Get("mvi")
|
mvi := q.Get("mvi")
|
||||||
mn := strings.Split(q.Get("mn"), ",")
|
mn := strings.Split(q.Get("mn"), ",")
|
||||||
|
|
||||||
if len(mvi) <= 0 {
|
if len(mvi) <= 0 {
|
||||||
w.WriteHeader(400)
|
io.WriteString(w, "No `mvi` in query parameters")
|
||||||
io.WriteString(w, "'mvi' query string undefined")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(mn) <= 0 {
|
if len(mn) <= 0 {
|
||||||
w.WriteHeader(400)
|
io.WriteString(w, "No `mn` in query parameters")
|
||||||
io.WriteString(w, "'mn' query string undefined")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,8 +32,8 @@ func videoplayback(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.Split(strings.ToLower(host), ".")
|
parts := strings.Split(strings.ToLower(host), ".")
|
||||||
|
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
w.WriteHeader(400)
|
|
||||||
io.WriteString(w, "Invalid hostname.")
|
io.WriteString(w, "Invalid hostname.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -100,18 +48,10 @@ func videoplayback(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if disallowed {
|
if disallowed {
|
||||||
w.WriteHeader(401)
|
|
||||||
io.WriteString(w, "Non YouTube domains are not supported.")
|
io.WriteString(w, "Non YouTube domains are not supported.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if c == "WEB" {
|
|
||||||
// q.Set("alr", "yes")
|
|
||||||
// }
|
|
||||||
// if req.Header.Get("Range") != "" {
|
|
||||||
// q.Set("range", req.Header.Get("Range"))
|
|
||||||
// }
|
|
||||||
|
|
||||||
path := req.URL.EscapedPath()
|
path := req.URL.EscapedPath()
|
||||||
|
|
||||||
proxyURL, err := url.Parse("https://" + host + path)
|
proxyURL, err := url.Parse("https://" + host + path)
|
||||||
|
@ -121,53 +61,24 @@ func videoplayback(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
proxyURL.RawQuery = q.Encode()
|
proxyURL.RawQuery = q.Encode()
|
||||||
|
|
||||||
// https://github.com/FreeTubeApp/FreeTube/blob/5a4cd981cdf2c2a20ab68b001746658fd0c6484e/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js#L1097
|
request, err := http.NewRequest(req.Method, proxyURL.String(), nil)
|
||||||
body := []byte{0x78, 0} // protobuf body
|
|
||||||
|
|
||||||
request, err := http.NewRequest("POST", proxyURL.String(), bytes.NewReader(body))
|
copyHeaders(req.Header, request.Header, false)
|
||||||
|
request.Header.Set("User-Agent", ua)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
copyHeaders(req.Header, request.Header, false)
|
|
||||||
|
|
||||||
switch c {
|
|
||||||
case "ANDROID":
|
|
||||||
request.Header.Set("User-Agent", "com.google.android.youtube/1537338816 (Linux; U; Android 13; en_US; ; Build/TQ2A.230505.002; Cronet/113.0.5672.24)")
|
|
||||||
case "IOS":
|
|
||||||
request.Header.Set("User-Agent", "com.google.ios.youtube/19.32.8 (iPhone14,5; U; CPU iOS 17_6 like Mac OS X;)")
|
|
||||||
case "WEB":
|
|
||||||
request.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36")
|
|
||||||
default:
|
|
||||||
request.Header.Set("User-Agent", default_ua)
|
|
||||||
}
|
|
||||||
|
|
||||||
request.Header.Add("Origin", "https://www.youtube.com")
|
|
||||||
request.Header.Add("Referer", "https://www.youtube.com/")
|
|
||||||
|
|
||||||
if connectionChecker(req.Context()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(request)
|
resp, err := client.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.Header.Get("location") != "" {
|
w.WriteHeader(resp.StatusCode)
|
||||||
new_url, err := url.Parse(resp.Header.Get("location"))
|
if resp.StatusCode == 403 {
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
request.URL = new_url
|
|
||||||
resp, err = client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := forbiddenChecker(resp, w); err != nil {
|
|
||||||
atomic.AddInt64(&stats_.RequestsForbidden.Videoplayback, 1)
|
atomic.AddInt64(&stats_.RequestsForbidden.Videoplayback, 1)
|
||||||
metrics.RequestForbidden.Videoplayback.Inc()
|
io.WriteString(w, "Forbidden 403\n")
|
||||||
|
io.WriteString(w, "Maybe Youtube blocked the IP of this proxy?\n")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,8 +87,6 @@ func videoplayback(w http.ResponseWriter, req *http.Request) {
|
||||||
NoRewrite := strings.HasPrefix(resp.Header.Get("Content-Type"), "audio") || strings.HasPrefix(resp.Header.Get("Content-Type"), "video")
|
NoRewrite := strings.HasPrefix(resp.Header.Get("Content-Type"), "audio") || strings.HasPrefix(resp.Header.Get("Content-Type"), "video")
|
||||||
copyHeaders(resp.Header, w.Header(), NoRewrite)
|
copyHeaders(resp.Header, w.Header(), NoRewrite)
|
||||||
|
|
||||||
w.WriteHeader(resp.StatusCode)
|
|
||||||
|
|
||||||
if req.Method == "GET" && (resp.Header.Get("Content-Type") == "application/x-mpegurl" || resp.Header.Get("Content-Type") == "application/vnd.apple.mpegurl") {
|
if req.Method == "GET" && (resp.Header.Get("Content-Type") == "application/x-mpegurl" || resp.Header.Get("Content-Type") == "application/vnd.apple.mpegurl") {
|
||||||
bytes, err := io.ReadAll(resp.Body)
|
bytes, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -232,32 +141,29 @@ func vi(w http.ResponseWriter, req *http.Request) {
|
||||||
proxyURL.RawQuery = q.Encode()
|
proxyURL.RawQuery = q.Encode()
|
||||||
|
|
||||||
request, err := http.NewRequest(req.Method, proxyURL.String(), nil)
|
request, err := http.NewRequest(req.Method, proxyURL.String(), nil)
|
||||||
|
|
||||||
|
copyHeaders(req.Header, request.Header, false)
|
||||||
|
request.Header.Set("User-Agent", ua)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
request.Header.Set("User-Agent", default_ua)
|
|
||||||
|
|
||||||
if connectionChecker(req.Context()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(request)
|
resp, err := client.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := forbiddenChecker(resp, w); err != nil {
|
w.WriteHeader(resp.StatusCode)
|
||||||
|
if resp.StatusCode == 403 {
|
||||||
atomic.AddInt64(&stats_.RequestsForbidden.Vi, 1)
|
atomic.AddInt64(&stats_.RequestsForbidden.Vi, 1)
|
||||||
metrics.RequestForbidden.Vi.Inc()
|
io.WriteString(w, "Forbidden 403")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// NoRewrite := strings.HasPrefix(resp.Header.Get("Content-Type"), "audio") || strings.HasPrefix(resp.Header.Get("Content-Type"), "video")
|
NoRewrite := strings.HasPrefix(resp.Header.Get("Content-Type"), "audio") || strings.HasPrefix(resp.Header.Get("Content-Type"), "video")
|
||||||
// copyHeaders(resp.Header, w.Header(), NoRewrite)
|
copyHeaders(resp.Header, w.Header(), NoRewrite)
|
||||||
w.WriteHeader(resp.StatusCode)
|
|
||||||
|
|
||||||
io.Copy(w, resp.Body)
|
io.Copy(w, resp.Body)
|
||||||
}
|
}
|
||||||
|
@ -274,25 +180,24 @@ func ggpht(w http.ResponseWriter, req *http.Request) {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println(proxyURL)
|
||||||
|
|
||||||
request, err := http.NewRequest(req.Method, proxyURL.String(), nil)
|
request, err := http.NewRequest(req.Method, proxyURL.String(), nil)
|
||||||
copyHeaders(req.Header, request.Header, false)
|
copyHeaders(req.Header, request.Header, false)
|
||||||
request.Header.Set("User-Agent", default_ua)
|
request.Header.Set("User-Agent", ua)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if connectionChecker(req.Context()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(request)
|
resp, err := client.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := forbiddenChecker(resp, w); err != nil {
|
w.WriteHeader(resp.StatusCode)
|
||||||
|
if resp.StatusCode == 403 {
|
||||||
atomic.AddInt64(&stats_.RequestsForbidden.Ggpht, 1)
|
atomic.AddInt64(&stats_.RequestsForbidden.Ggpht, 1)
|
||||||
metrics.RequestForbidden.Ggpht.Inc()
|
io.WriteString(w, "Forbidden 403")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,7 +205,6 @@ func ggpht(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
NoRewrite := strings.HasPrefix(resp.Header.Get("Content-Type"), "audio") || strings.HasPrefix(resp.Header.Get("Content-Type"), "video")
|
NoRewrite := strings.HasPrefix(resp.Header.Get("Content-Type"), "audio") || strings.HasPrefix(resp.Header.Get("Content-Type"), "video")
|
||||||
copyHeaders(resp.Header, w.Header(), NoRewrite)
|
copyHeaders(resp.Header, w.Header(), NoRewrite)
|
||||||
w.WriteHeader(resp.StatusCode)
|
|
||||||
|
|
||||||
io.Copy(w, resp.Body)
|
io.Copy(w, resp.Body)
|
||||||
}
|
}
|
||||||
|
|
443
main.go
443
main.go
|
@ -1,37 +1,22 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/conduitio/bwlimit"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
||||||
"github.com/quic-go/quic-go"
|
|
||||||
"github.com/quic-go/quic-go/http3"
|
"github.com/quic-go/quic-go/http3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
wl = flag.Int("w", 8000, "Write limit in Kbps")
|
|
||||||
rl = flag.Int("r", 8000, "Read limit in Kbps")
|
|
||||||
)
|
|
||||||
|
|
||||||
// QUIC doesn't seem to support HTTP nor SOCKS5 proxies due to how it's made.
|
|
||||||
// (Since it's UDP)
|
|
||||||
var h3client = &http.Client{
|
var h3client = &http.Client{
|
||||||
Transport: &http3.Transport{},
|
Transport: &http3.Transport{},
|
||||||
Timeout: 10 * time.Second,
|
Timeout: 10 * time.Second,
|
||||||
|
@ -42,8 +27,6 @@ var dialer = &net.Dialer{
|
||||||
KeepAlive: 30 * time.Second,
|
KeepAlive: 30 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy string
|
|
||||||
|
|
||||||
// http/2 client
|
// http/2 client
|
||||||
var h2client = &http.Client{
|
var h2client = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
|
@ -65,18 +48,14 @@ var h2client = &http.Client{
|
||||||
MaxConnsPerHost: 0,
|
MaxConnsPerHost: 0,
|
||||||
MaxIdleConnsPerHost: 10,
|
MaxIdleConnsPerHost: 10,
|
||||||
MaxIdleConns: 0,
|
MaxIdleConns: 0,
|
||||||
Proxy: func(r *http.Request) (*url.URL, error) {
|
|
||||||
if proxy != "" {
|
|
||||||
return url.Parse(proxy)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var client *http.Client
|
// https://github.com/lucas-clemente/quic-go/issues/2836
|
||||||
|
var client = h2client
|
||||||
|
|
||||||
var default_ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
|
// Same user agent as Invidious
|
||||||
|
var ua = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
|
||||||
|
|
||||||
var allowed_hosts = []string{
|
var allowed_hosts = []string{
|
||||||
"youtube.com",
|
"youtube.com",
|
||||||
|
@ -97,8 +76,6 @@ var strip_headers = []string{
|
||||||
"Alt-Svc",
|
"Alt-Svc",
|
||||||
"Server",
|
"Server",
|
||||||
"Cache-Control",
|
"Cache-Control",
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to
|
|
||||||
"report-to",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var path_prefix = ""
|
var path_prefix = ""
|
||||||
|
@ -107,65 +84,11 @@ var manifest_re = regexp.MustCompile(`(?m)URI="([^"]+)"`)
|
||||||
|
|
||||||
var ipv6_only = false
|
var ipv6_only = false
|
||||||
|
|
||||||
var version string
|
|
||||||
|
|
||||||
var h3s bool
|
|
||||||
|
|
||||||
var domain_only_access bool = false
|
|
||||||
|
|
||||||
var programInit = time.Now()
|
|
||||||
|
|
||||||
type ConnectionWatcher struct {
|
|
||||||
totalEstablished int64
|
|
||||||
established int64
|
|
||||||
active int64
|
|
||||||
idle int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/51317122/how-to-get-number-of-idle-and-active-connections-in-go
|
|
||||||
|
|
||||||
// OnStateChange records open connections in response to connection
|
|
||||||
// state changes. Set net/http Server.ConnState to this method
|
|
||||||
// as value.
|
|
||||||
func (cw *ConnectionWatcher) OnStateChange(conn net.Conn, state http.ConnState) {
|
|
||||||
switch state {
|
|
||||||
case http.StateNew:
|
|
||||||
atomic.AddInt64(&stats_.EstablishedConnections, 1)
|
|
||||||
metrics.EstablishedConnections.Inc()
|
|
||||||
atomic.AddInt64(&stats_.TotalConnEstablished, 1)
|
|
||||||
metrics.TotalConnEstablished.Inc()
|
|
||||||
// case http.StateActive:
|
|
||||||
// atomic.AddInt64(&cw.active, 1)
|
|
||||||
case http.StateClosed, http.StateHijacked:
|
|
||||||
atomic.AddInt64(&stats_.EstablishedConnections, -1)
|
|
||||||
metrics.EstablishedConnections.Dec()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // Count returns the number of connections at the time
|
|
||||||
// // the call.
|
|
||||||
// func (cw *ConnectionWatcher) Count() int {
|
|
||||||
// return int(atomic.LoadInt64(&cw.n))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Add adds c to the number of active connections.
|
|
||||||
// func (cw *ConnectionWatcher) Add(c int64) {
|
|
||||||
// atomic.AddInt64(&cw.n, c)
|
|
||||||
// }
|
|
||||||
|
|
||||||
var cw ConnectionWatcher
|
|
||||||
|
|
||||||
type statusJson struct {
|
type statusJson struct {
|
||||||
Version string `json:"version"`
|
RequestCount int64 `json:"requestCount"`
|
||||||
Uptime time.Duration `json:"uptime"`
|
RequestPerSecond int64 `json:"requestPerSecond"`
|
||||||
RequestCount int64 `json:"requestCount"`
|
RequestPerMinute int64 `json:"requestPerMinute"`
|
||||||
RequestPerSecond int64 `json:"requestPerSecond"`
|
RequestsForbidden struct {
|
||||||
RequestPerMinute int64 `json:"requestPerMinute"`
|
|
||||||
TotalConnEstablished int64 `json:"totalEstablished"`
|
|
||||||
EstablishedConnections int64 `json:"establishedConnections"`
|
|
||||||
ActiveConnections int64 `json:"activeConnections"`
|
|
||||||
IdleConnections int64 `json:"idleConnections"`
|
|
||||||
RequestsForbidden struct {
|
|
||||||
Videoplayback int64 `json:"videoplayback"`
|
Videoplayback int64 `json:"videoplayback"`
|
||||||
Vi int64 `json:"vi"`
|
Vi int64 `json:"vi"`
|
||||||
Ggpht int64 `json:"ggpht"`
|
Ggpht int64 `json:"ggpht"`
|
||||||
|
@ -173,15 +96,9 @@ type statusJson struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var stats_ = statusJson{
|
var stats_ = statusJson{
|
||||||
Version: version + "-" + runtime.GOARCH,
|
RequestCount: 0,
|
||||||
Uptime: 0,
|
RequestPerSecond: 0,
|
||||||
RequestCount: 0,
|
RequestPerMinute: 0,
|
||||||
RequestPerSecond: 0,
|
|
||||||
RequestPerMinute: 0,
|
|
||||||
TotalConnEstablished: 0,
|
|
||||||
EstablishedConnections: 0,
|
|
||||||
ActiveConnections: 0,
|
|
||||||
IdleConnections: 0,
|
|
||||||
RequestsForbidden: struct {
|
RequestsForbidden: struct {
|
||||||
Videoplayback int64 `json:"videoplayback"`
|
Videoplayback int64 `json:"videoplayback"`
|
||||||
Vi int64 `json:"vi"`
|
Vi int64 `json:"vi"`
|
||||||
|
@ -193,65 +110,6 @@ var stats_ = statusJson{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type Metrics struct {
|
|
||||||
Uptime prometheus.Gauge
|
|
||||||
RequestCount prometheus.Counter
|
|
||||||
RequestPerSecond prometheus.Gauge
|
|
||||||
RequestPerMinute prometheus.Gauge
|
|
||||||
TotalConnEstablished prometheus.Counter
|
|
||||||
EstablishedConnections prometheus.Gauge
|
|
||||||
ActiveConnections prometheus.Gauge
|
|
||||||
IdleConnections prometheus.Gauge
|
|
||||||
RequestForbidden struct {
|
|
||||||
Videoplayback prometheus.Counter
|
|
||||||
Vi prometheus.Counter
|
|
||||||
Ggpht prometheus.Counter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var metrics = Metrics{
|
|
||||||
Uptime: prometheus.NewGauge(prometheus.GaugeOpts{
|
|
||||||
Name: "http3_ytproxy_uptime",
|
|
||||||
}),
|
|
||||||
RequestCount: prometheus.NewGauge(prometheus.GaugeOpts{
|
|
||||||
Name: "http3_ytproxy_request_count",
|
|
||||||
}),
|
|
||||||
RequestPerSecond: prometheus.NewGauge(prometheus.GaugeOpts{
|
|
||||||
Name: "http3_ytproxy_request_per_second",
|
|
||||||
}),
|
|
||||||
RequestPerMinute: prometheus.NewGauge(prometheus.GaugeOpts{
|
|
||||||
Name: "http3_ytproxy_request_per_minute",
|
|
||||||
}),
|
|
||||||
TotalConnEstablished: prometheus.NewGauge(prometheus.GaugeOpts{
|
|
||||||
Name: "http3_ytproxy_total_conn_established",
|
|
||||||
}),
|
|
||||||
EstablishedConnections: prometheus.NewGauge(prometheus.GaugeOpts{
|
|
||||||
Name: "http3_ytproxy_established_conns",
|
|
||||||
}),
|
|
||||||
ActiveConnections: prometheus.NewGauge(prometheus.GaugeOpts{
|
|
||||||
Name: "http3_ytproxy_active_conns",
|
|
||||||
}),
|
|
||||||
IdleConnections: prometheus.NewGauge(prometheus.GaugeOpts{
|
|
||||||
Name: "http3_ytproxy_idle_conns",
|
|
||||||
}),
|
|
||||||
|
|
||||||
RequestForbidden: struct {
|
|
||||||
Videoplayback prometheus.Counter
|
|
||||||
Vi prometheus.Counter
|
|
||||||
Ggpht prometheus.Counter
|
|
||||||
}{
|
|
||||||
Videoplayback: prometheus.NewCounter(prometheus.CounterOpts{
|
|
||||||
Name: "http3_ytproxy_request_forbidden_videoplayback",
|
|
||||||
}),
|
|
||||||
Vi: prometheus.NewCounter(prometheus.CounterOpts{
|
|
||||||
Name: "http3_ytproxy_request_forbidden_vi",
|
|
||||||
}),
|
|
||||||
Ggpht: prometheus.NewCounter(prometheus.CounterOpts{
|
|
||||||
Name: "http3_ytproxy_request_forbidden_ggpht",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func root(w http.ResponseWriter, req *http.Request) {
|
func root(w http.ResponseWriter, req *http.Request) {
|
||||||
const msg = `
|
const msg = `
|
||||||
HTTP youtube proxy for https://inv.nadeko.net
|
HTTP youtube proxy for https://inv.nadeko.net
|
||||||
|
@ -263,27 +121,8 @@ func root(w http.ResponseWriter, req *http.Request) {
|
||||||
io.WriteString(w, msg)
|
io.WriteString(w, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomHandler wraps the default promhttp.Handler with custom logic
|
|
||||||
func metricsHandler() http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
||||||
// To prevent accessing from the bare IP address
|
|
||||||
if req.Host == "" || net.ParseIP(strings.Split(req.Host, ":")[0]) != nil {
|
|
||||||
w.WriteHeader(444)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
metrics.Uptime.Set(float64(time.Duration(time.Since(programInit).Seconds())))
|
|
||||||
promhttp.Handler().ServeHTTP(w, req)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func stats(w http.ResponseWriter, req *http.Request) {
|
func stats(w http.ResponseWriter, req *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
stats_.Uptime = time.Duration(time.Since(programInit).Seconds())
|
|
||||||
// stats_.TotalEstablished = int64(cw.totalEstablished)
|
|
||||||
// stats_.EstablishedConnections = int64(cw.established)
|
|
||||||
// stats_.ActiveConnections = int64(cw.active)
|
|
||||||
// stats_.IdleConnections = int64(cw.idle)
|
|
||||||
|
|
||||||
if err := json.NewEncoder(w).Encode(stats_); err != nil {
|
if err := json.NewEncoder(w).Encode(stats_); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
@ -301,7 +140,6 @@ func requestPerSecond() {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
current := stats_.RequestCount
|
current := stats_.RequestCount
|
||||||
stats_.RequestPerSecond = current - last
|
stats_.RequestPerSecond = current - last
|
||||||
metrics.RequestPerSecond.Set(float64(stats_.RequestPerSecond))
|
|
||||||
last = current
|
last = current
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -311,257 +149,122 @@ func requestPerMinute() {
|
||||||
for {
|
for {
|
||||||
time.Sleep(60 * time.Second)
|
time.Sleep(60 * time.Second)
|
||||||
current := stats_.RequestCount
|
current := stats_.RequestCount
|
||||||
stats_.RequestPerMinute = current - last
|
stats_.RequestPerSecond = current - last
|
||||||
metrics.RequestPerMinute.Set(float64(stats_.RequestPerMinute))
|
|
||||||
last = current
|
last = current
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func beforeMisc(next http.HandlerFunc) http.HandlerFunc {
|
func beforeAll(next http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, req *http.Request) {
|
return func(w http.ResponseWriter, req *http.Request) {
|
||||||
defer panicHandler(w)
|
if req.Method != "GET" && req.Method != "HEAD" {
|
||||||
|
io.WriteString(w, "Only GET and HEAD requests are allowed.")
|
||||||
// To prevent accessing from the bare IP address
|
|
||||||
if domain_only_access && (req.Host == "" || net.ParseIP(strings.Split(req.Host, ":")[0]) != nil) {
|
|
||||||
w.WriteHeader(444)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
next(w, req)
|
atomic.AddInt64(&stats_.RequestCount, 1)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func beforeProxy(next http.HandlerFunc) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, req *http.Request) {
|
|
||||||
defer panicHandler(w)
|
|
||||||
|
|
||||||
// To prevent accessing from the bare IP address
|
|
||||||
if domain_only_access && (req.Host == "" || net.ParseIP(strings.Split(req.Host, ":")[0]) != nil) {
|
|
||||||
w.WriteHeader(444)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "*")
|
w.Header().Set("Access-Control-Allow-Headers", "*")
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS")
|
|
||||||
w.Header().Set("Access-Control-Max-Age", "1728000")
|
w.Header().Set("Access-Control-Max-Age", "1728000")
|
||||||
w.Header().Set("Strict-Transport-Security", "max-age=86400")
|
|
||||||
w.Header().Set("X-Powered-By", "http3-ytproxy "+version+"-"+runtime.GOARCH)
|
|
||||||
|
|
||||||
if h3s {
|
|
||||||
w.Header().Set("Alt-Svc", "h3=\":8443\"; ma=86400")
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Method == "OPTIONS" {
|
if req.Method == "OPTIONS" {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Method != "GET" && req.Method != "HEAD" {
|
|
||||||
w.WriteHeader(405)
|
|
||||||
io.WriteString(w, "Only GET and HEAD requests are allowed.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.AddInt64(&stats_.RequestCount, 1)
|
|
||||||
metrics.RequestCount.Inc()
|
|
||||||
next(w, req)
|
next(w, req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
defaultHost := "0.0.0.0"
|
var sock string
|
||||||
defaultPort := "8080"
|
var host string
|
||||||
defaultSock := "/tmp/http-ytproxy.sock"
|
var port string
|
||||||
defaultTLSCert := "/data/cert.pem"
|
var tls_cert string
|
||||||
defaultTLSKey := "/data/key.key"
|
var tls_key string
|
||||||
|
|
||||||
var https bool = false
|
path_prefix = os.Getenv("PREFIX_PATH")
|
||||||
var h3c bool = false
|
ipv6_only = os.Getenv("IPV6_ONLY") == "1"
|
||||||
var ipv6 bool = false
|
|
||||||
|
|
||||||
if strings.ToLower(os.Getenv("HTTPS")) == "true" {
|
var https = flag.Bool("https", false, "Use built-in https server")
|
||||||
https = true
|
var ipv6 = flag.Bool("ipv6_only", false, "Only use ipv6 for requests")
|
||||||
}
|
flag.StringVar(&tls_cert, "tls-cert", "", "TLS Certificate path")
|
||||||
if strings.ToLower(os.Getenv("H3C")) == "true" {
|
flag.StringVar(&tls_key, "tls-key", "", "TLS Certificate Key path")
|
||||||
h3c = true
|
flag.StringVar(&sock, "s", "/tmp/http-ytproxy.sock", "Specify a socket name")
|
||||||
}
|
flag.StringVar(&port, "p", "8080", "Specify a port number")
|
||||||
if strings.ToLower(os.Getenv("H3S")) == "true" {
|
flag.StringVar(&host, "l", "0.0.0.0", "Specify a listen address")
|
||||||
h3s = true
|
|
||||||
}
|
|
||||||
if strings.ToLower(os.Getenv("IPV6_ONLY")) == "true" {
|
|
||||||
ipv6 = true
|
|
||||||
}
|
|
||||||
if strings.ToLower(os.Getenv("DOMAIN_ONLY_ACCESS")) == "true" {
|
|
||||||
domain_only_access = true
|
|
||||||
}
|
|
||||||
|
|
||||||
tls_cert := os.Getenv("TLS_CERT")
|
|
||||||
if tls_cert == "" {
|
|
||||||
tls_cert = defaultTLSCert
|
|
||||||
}
|
|
||||||
tls_key := os.Getenv("TLS_KEY")
|
|
||||||
if tls_key == "" {
|
|
||||||
tls_key = defaultTLSKey
|
|
||||||
}
|
|
||||||
sock := os.Getenv("SOCK_PATH")
|
|
||||||
if sock == "" {
|
|
||||||
sock = defaultSock
|
|
||||||
}
|
|
||||||
port := os.Getenv("PORT")
|
|
||||||
if port == "" {
|
|
||||||
port = defaultPort
|
|
||||||
}
|
|
||||||
host := os.Getenv("HOST")
|
|
||||||
if host == "" {
|
|
||||||
host = defaultHost
|
|
||||||
}
|
|
||||||
proxy = os.Getenv("PROXY")
|
|
||||||
|
|
||||||
flag.BoolVar(&https, "https", https, "Use built-in https server (recommended)")
|
|
||||||
flag.BoolVar(&h3c, "h3c", h3c, "Use HTTP/3 for client requests (high CPU usage)")
|
|
||||||
flag.BoolVar(&h3s, "h3s", h3s, "Use HTTP/3 for server requests, (requires HTTPS)")
|
|
||||||
flag.BoolVar(&ipv6_only, "ipv6_only", ipv6_only, "Only use ipv6 for requests")
|
|
||||||
flag.StringVar(&tls_cert, "tls-cert", tls_cert, "TLS Certificate path")
|
|
||||||
flag.StringVar(&tls_key, "tls-key", tls_key, "TLS Certificate Key path")
|
|
||||||
flag.StringVar(&sock, "s", sock, "Specify a socket name")
|
|
||||||
flag.StringVar(&port, "p", port, "Specify a port number")
|
|
||||||
flag.StringVar(&host, "l", host, "Specify a listen address")
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if h3c {
|
if *https {
|
||||||
client = h3client
|
|
||||||
} else {
|
|
||||||
client = h2client
|
|
||||||
}
|
|
||||||
|
|
||||||
if https {
|
|
||||||
if len(tls_cert) <= 0 {
|
if len(tls_cert) <= 0 {
|
||||||
log.Fatal("tls-cert argument is missing, you need a TLS certificate for HTTPS")
|
fmt.Println("tls-cert argument is missing")
|
||||||
|
fmt.Println("You need a TLS certificate for HTTPS")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tls_key) <= 0 {
|
if len(tls_key) <= 0 {
|
||||||
log.Fatal("tls-key argument is missing, you need a TLS key for HTTPS")
|
fmt.Println("tls-key argument is missing")
|
||||||
|
fmt.Println("You need a TLS key for HTTPS")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ipv6_only = ipv6
|
ipv6_only = *ipv6
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
// MISC ROUTES
|
mux.HandleFunc("/", root)
|
||||||
mux.HandleFunc("/", beforeMisc(root))
|
mux.HandleFunc("/health", health)
|
||||||
mux.HandleFunc("/health", beforeMisc(health))
|
mux.HandleFunc("/stats", stats)
|
||||||
mux.HandleFunc("/stats", beforeMisc(stats))
|
|
||||||
|
|
||||||
prometheus.MustRegister(metrics.Uptime)
|
mux.HandleFunc("/videoplayback", beforeAll(videoplayback))
|
||||||
prometheus.MustRegister(metrics.ActiveConnections)
|
mux.HandleFunc("/vi/", beforeAll(vi))
|
||||||
prometheus.MustRegister(metrics.IdleConnections)
|
mux.HandleFunc("/vi_webp/", beforeAll(vi))
|
||||||
prometheus.MustRegister(metrics.EstablishedConnections)
|
mux.HandleFunc("/sb/", beforeAll(vi))
|
||||||
prometheus.MustRegister(metrics.TotalConnEstablished)
|
mux.HandleFunc("/ggpht/", beforeAll(ggpht))
|
||||||
prometheus.MustRegister(metrics.RequestCount)
|
mux.HandleFunc("/a/", beforeAll(ggpht))
|
||||||
prometheus.MustRegister(metrics.RequestPerSecond)
|
mux.HandleFunc("/ytc/", beforeAll(ggpht))
|
||||||
prometheus.MustRegister(metrics.RequestPerMinute)
|
|
||||||
prometheus.MustRegister(metrics.RequestForbidden.Videoplayback)
|
|
||||||
prometheus.MustRegister(metrics.RequestForbidden.Vi)
|
|
||||||
prometheus.MustRegister(metrics.RequestForbidden.Ggpht)
|
|
||||||
|
|
||||||
mux.Handle("/metrics", metricsHandler())
|
|
||||||
|
|
||||||
// PROXY ROUTES
|
|
||||||
mux.HandleFunc("/videoplayback", beforeProxy(videoplayback))
|
|
||||||
mux.HandleFunc("/vi/", beforeProxy(vi))
|
|
||||||
mux.HandleFunc("/vi_webp/", beforeProxy(vi))
|
|
||||||
mux.HandleFunc("/sb/", beforeProxy(vi))
|
|
||||||
mux.HandleFunc("/ggpht/", beforeProxy(ggpht))
|
|
||||||
mux.HandleFunc("/a/", beforeProxy(ggpht))
|
|
||||||
mux.HandleFunc("/ytc/", beforeProxy(ggpht))
|
|
||||||
|
|
||||||
go requestPerSecond()
|
go requestPerSecond()
|
||||||
go requestPerMinute()
|
go requestPerMinute()
|
||||||
|
|
||||||
ln, err := net.Listen("tcp", host+":"+port)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to listen: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1Kbit = 125Bytes
|
|
||||||
var (
|
|
||||||
writeLimit = bwlimit.Byte(*wl) * bwlimit.Byte(125)
|
|
||||||
readLimit = bwlimit.Byte(*rl) * bwlimit.Byte(125)
|
|
||||||
)
|
|
||||||
|
|
||||||
ln = bwlimit.NewListener(ln, writeLimit, readLimit)
|
|
||||||
// srvDialer := bwlimit.NewDialer(&net.Dialer{}, writeLimit, readLimit)
|
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Handler: mux,
|
|
||||||
ReadTimeout: 5 * time.Second,
|
ReadTimeout: 5 * time.Second,
|
||||||
WriteTimeout: 1 * time.Hour,
|
WriteTimeout: 1 * time.Hour,
|
||||||
ConnState: cw.OnStateChange,
|
Addr: string(host) + ":" + string(port),
|
||||||
|
Handler: mux,
|
||||||
}
|
}
|
||||||
|
|
||||||
srvh3 := &http3.Server{
|
socket := string(sock)
|
||||||
Handler: mux,
|
syscall.Unlink(socket)
|
||||||
EnableDatagrams: false, // https://quic.video/blog/never-use-datagrams/ (Read it)
|
listener, err := net.Listen("unix", socket)
|
||||||
IdleTimeout: 120 * time.Second,
|
fmt.Println("Unix socket listening at:", string(sock))
|
||||||
TLSConfig: http3.ConfigureTLSConfig(&tls.Config{}),
|
|
||||||
QUICConfig: &quic.Config{
|
|
||||||
// KeepAlivePeriod: 10 * time.Second,
|
|
||||||
MaxIncomingStreams: 256, // I'm not sure if this is correct.
|
|
||||||
MaxIncomingUniStreams: 256, // Same as above
|
|
||||||
},
|
|
||||||
Addr: host + ":" + port,
|
|
||||||
}
|
|
||||||
|
|
||||||
syscall.Unlink(sock)
|
|
||||||
socket_listener, err := net.Listen("unix", sock)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to bind to UDS, please check the socket name", err.Error())
|
fmt.Println("Failed to bind to UDS, please check the socket name, falling back to TCP/IP")
|
||||||
} else {
|
fmt.Println(err.Error())
|
||||||
defer socket_listener.Close()
|
err := srv.ListenAndServe()
|
||||||
// To allow everyone to access the socket
|
|
||||||
err = os.Chmod(sock, 0777)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to set socket permissions to 777:", err.Error())
|
fmt.Println("Cannot bind to port", string(port), "Error:", err)
|
||||||
|
fmt.Println("Please try changing the port number")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defer listener.Close()
|
||||||
|
// To allow everyone to access the socket
|
||||||
|
err = os.Chmod(socket, 0777)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error setting permissions:", err)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
log.Println("Setting socket permissions to 777")
|
fmt.Println("Setting socket permissions to 777")
|
||||||
}
|
}
|
||||||
|
go srv.Serve(listener)
|
||||||
go srv.Serve(socket_listener)
|
if *https {
|
||||||
log.Println("Unix socket listening at:", string(sock))
|
fmt.Println("Serving HTTPS at port", string(port))
|
||||||
|
if err := srv.ListenAndServeTLS(tls_cert, tls_key); err != nil {
|
||||||
if https {
|
|
||||||
if _, err := os.Open(tls_cert); errors.Is(err, os.ErrNotExist) {
|
|
||||||
log.Panicf("Certificate file does not exist at path '%s'", tls_cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Open(tls_key); errors.Is(err, os.ErrNotExist) {
|
|
||||||
log.Panicf("Key file does not exist at path '%s'", tls_key)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Serving HTTPS at port", string(port)+"/tcp")
|
|
||||||
go func() {
|
|
||||||
if err := srv.ServeTLS(ln, tls_cert, tls_key); err != nil {
|
|
||||||
log.Fatal("Failed to server HTTP/2", err.Error())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if h3s {
|
|
||||||
log.Println("Serving HTTP/3 (HTTPS) via QUIC at port", string(port)+"/udp")
|
|
||||||
go func() {
|
|
||||||
if err := srvh3.ListenAndServeTLS(tls_cert, tls_key); err != nil {
|
|
||||||
log.Fatal("Failed to serve HTTP/3:", err.Error())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
select {}
|
|
||||||
} else {
|
|
||||||
log.Println("Serving HTTP at port", string(port))
|
|
||||||
if err := srv.Serve(ln); err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("Serving HTTP at port", string(port))
|
||||||
|
srv.ListenAndServe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
7
utils.go
7
utils.go
|
@ -55,10 +55,3 @@ func RelativeUrl(in string) (newurl string) {
|
||||||
segment_url.Path = path_prefix + segment_url.Path
|
segment_url.Path = path_prefix + segment_url.Path
|
||||||
return segment_url.RequestURI()
|
return segment_url.RequestURI()
|
||||||
}
|
}
|
||||||
|
|
||||||
func panicHandler(w http.ResponseWriter) {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
log.Printf("Panic: %v", r)
|
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue