2020-10-24 12:47:41 -03:00
package main
import (
2024-10-29 01:29:55 -03:00
"encoding/json"
2024-09-17 03:23:49 -03:00
"flag"
2020-10-24 12:47:41 -03:00
"fmt"
2020-10-25 09:41:17 -03:00
"io"
2020-10-24 12:47:41 -03:00
"log"
2020-10-25 09:41:17 -03:00
"net"
2020-10-24 12:47:41 -03:00
"net/http"
2020-10-25 11:01:23 -03:00
"os"
2021-11-07 15:23:39 -03:00
"regexp"
2024-10-29 19:34:26 -03:00
"runtime"
2024-10-29 15:01:35 -03:00
"sync/atomic"
2020-10-25 09:52:02 -03:00
"syscall"
2021-03-12 03:59:53 -03:00
"time"
2020-10-24 12:47:41 -03:00
2023-11-07 11:05:53 -03:00
"github.com/quic-go/quic-go/http3"
2020-10-24 12:47:41 -03:00
)
2020-10-25 09:41:17 -03:00
var h3client = & http . Client {
2024-10-29 15:01:35 -03:00
Transport : & http3 . Transport { } ,
2024-09-17 03:23:49 -03:00
Timeout : 10 * time . Second ,
2020-10-24 12:47:41 -03:00
}
2022-06-27 08:25:31 -04:00
var dialer = & net . Dialer {
Timeout : 30 * time . Second ,
KeepAlive : 30 * time . Second ,
}
2020-10-25 09:41:17 -03:00
// http/2 client
2021-03-12 03:59:53 -03:00
var h2client = & http . Client {
Transport : & http . Transport {
2022-06-27 08:25:31 -04:00
Dial : func ( network , addr string ) ( net . Conn , error ) {
2024-10-28 01:58:29 -03:00
var net string
if ipv6_only {
net = "tcp6"
} else {
net = "tcp4"
2022-06-27 08:25:31 -04:00
}
2024-10-28 01:58:29 -03:00
return dialer . Dial ( net , addr )
2022-06-27 08:25:31 -04:00
} ,
2021-03-12 03:59:53 -03:00
TLSHandshakeTimeout : 10 * time . Second ,
2021-06-20 06:37:39 -04:00
ResponseHeaderTimeout : 20 * time . Second ,
2021-03-12 03:59:53 -03:00
ExpectContinueTimeout : 1 * time . Second ,
2021-04-09 04:50:14 -04:00
IdleConnTimeout : 30 * time . Second ,
2021-04-09 10:37:35 -04:00
ReadBufferSize : 16 * 1024 ,
ForceAttemptHTTP2 : true ,
MaxConnsPerHost : 0 ,
MaxIdleConnsPerHost : 10 ,
MaxIdleConns : 0 ,
2021-03-12 03:59:53 -03:00
} ,
}
2020-10-25 09:41:17 -03:00
2024-10-29 01:29:55 -03:00
// https://github.com/lucas-clemente/quic-go/issues/2836
2024-10-29 19:34:26 -03:00
var client * http . Client
2024-10-29 01:29:55 -03:00
// 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"
2020-10-25 09:41:17 -03:00
2021-07-21 14:23:27 -04:00
var allowed_hosts = [ ] string {
"youtube.com" ,
"googlevideo.com" ,
"ytimg.com" ,
"ggpht.com" ,
2021-08-04 11:00:40 -04:00
"googleusercontent.com" ,
2021-07-21 14:23:27 -04:00
}
2022-05-17 06:19:43 -04:00
var strip_headers = [ ] string {
"Accept-Encoding" ,
"Authorization" ,
"Origin" ,
2022-05-18 08:50:31 -04:00
"Referer" ,
2022-05-17 06:19:43 -04:00
"Cookie" ,
"Set-Cookie" ,
2022-06-02 22:33:51 -04:00
"Etag" ,
2024-10-04 21:14:01 -03:00
"Alt-Svc" ,
"Server" ,
"Cache-Control" ,
2022-05-17 06:19:43 -04:00
}
2021-11-17 10:14:51 -03:00
var path_prefix = ""
2021-11-07 15:23:39 -03:00
var manifest_re = regexp . MustCompile ( ` (?m)URI="([^"]+)" ` )
2024-10-28 01:58:29 -03:00
var ipv6_only = false
2020-10-24 12:47:41 -03:00
2024-10-29 19:34:26 -03:00
var version string
2024-10-29 01:29:55 -03:00
type statusJson struct {
2024-10-29 19:34:26 -03:00
Version string ` json:"version" `
RequestCount int64 ` json:"requestCount" `
RequestPerSecond int64 ` json:"requestPerSecond" `
RequestPerMinute int64 ` json:"requestPerMinute" `
2024-10-29 15:01:35 -03:00
RequestsForbidden struct {
Videoplayback int64 ` json:"videoplayback" `
Vi int64 ` json:"vi" `
Ggpht int64 ` json:"ggpht" `
} ` json:"requestsForbidden" `
2020-10-25 09:41:17 -03:00
}
2024-10-29 15:01:35 -03:00
var stats_ = statusJson {
2024-10-29 19:34:26 -03:00
Version : version + "-" + runtime . GOARCH ,
2024-10-29 15:01:35 -03:00
RequestCount : 0 ,
RequestPerSecond : 0 ,
RequestPerMinute : 0 ,
RequestsForbidden : struct {
Videoplayback int64 ` json:"videoplayback" `
Vi int64 ` json:"vi" `
Ggpht int64 ` json:"ggpht" `
} {
Videoplayback : 0 ,
Vi : 0 ,
Ggpht : 0 ,
} ,
2020-10-25 09:41:17 -03:00
}
2024-10-29 15:01:35 -03:00
func root ( w http . ResponseWriter , req * http . Request ) {
const msg = `
HTTP youtube proxy for https : //inv.nadeko.net
https : //git.nadeko.net/Fijxu/http3-ytproxy
Routes :
/ stats
/ health `
io . WriteString ( w , msg )
}
2020-10-25 09:41:17 -03:00
2024-10-29 15:01:35 -03:00
func stats ( w http . ResponseWriter , req * http . Request ) {
2024-10-29 01:29:55 -03:00
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2020-10-25 09:41:17 -03:00
2024-10-29 15:01:35 -03:00
if err := json . NewEncoder ( w ) . Encode ( stats_ ) ; err != nil {
2024-10-29 01:29:55 -03:00
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
2021-06-20 03:19:07 -04:00
}
2021-11-07 15:23:39 -03:00
}
2024-10-29 15:01:35 -03:00
func health ( w http . ResponseWriter , req * http . Request ) {
w . WriteHeader ( 200 )
io . WriteString ( w , "OK" )
}
func requestPerSecond ( ) {
var last int64
for {
time . Sleep ( 1 * time . Second )
current := stats_ . RequestCount
stats_ . RequestPerSecond = current - last
last = current
}
}
func requestPerMinute ( ) {
var last int64
for {
time . Sleep ( 60 * time . Second )
current := stats_ . RequestCount
2024-10-29 19:38:10 -03:00
stats_ . RequestPerMinute = current - last
2024-10-29 15:01:35 -03:00
last = current
}
}
func beforeAll ( next http . HandlerFunc ) http . HandlerFunc {
return func ( w http . ResponseWriter , req * http . Request ) {
if req . Method != "GET" && req . Method != "HEAD" {
io . WriteString ( w , "Only GET and HEAD requests are allowed." )
return
}
atomic . AddInt64 ( & stats_ . RequestCount , 1 )
w . Header ( ) . Set ( "Access-Control-Allow-Origin" , "*" )
w . Header ( ) . Set ( "Access-Control-Allow-Headers" , "*" )
w . Header ( ) . Set ( "Access-Control-Max-Age" , "1728000" )
if req . Method == "OPTIONS" {
w . WriteHeader ( http . StatusOK )
return
}
next ( w , req )
}
}
2020-10-25 09:41:17 -03:00
func main ( ) {
2024-04-28 14:24:50 -04:00
var sock string
2024-10-04 20:56:01 -03:00
var host string
2023-11-07 12:18:20 -03:00
var port string
2024-10-29 15:01:35 -03:00
var tls_cert string
var tls_key string
2023-11-07 11:33:20 -03:00
2021-11-17 10:14:51 -03:00
path_prefix = os . Getenv ( "PREFIX_PATH" )
2024-10-28 01:58:29 -03:00
ipv6_only = os . Getenv ( "IPV6_ONLY" ) == "1"
2024-10-02 19:04:19 -03:00
2024-10-29 19:34:26 -03:00
var https = flag . Bool ( "https" , false , "Use built-in https server (recommended)" )
var h3 = flag . Bool ( "h3" , false , "Use HTTP/3 for requests (high CPU usage)" )
2024-10-28 01:58:29 -03:00
var ipv6 = flag . Bool ( "ipv6_only" , false , "Only use ipv6 for requests" )
2024-10-29 19:34:26 -03:00
flag . StringVar ( & ua , "u" , "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36" , "Set User-Agent (do not modify)" )
2024-10-29 15:01:35 -03:00
flag . StringVar ( & tls_cert , "tls-cert" , "" , "TLS Certificate path" )
flag . StringVar ( & tls_key , "tls-key" , "" , "TLS Certificate Key path" )
2024-10-04 20:56:01 -03:00
flag . StringVar ( & sock , "s" , "/tmp/http-ytproxy.sock" , "Specify a socket name" )
2023-11-07 12:18:20 -03:00
flag . StringVar ( & port , "p" , "8080" , "Specify a port number" )
2024-10-08 23:14:29 -03:00
flag . StringVar ( & host , "l" , "0.0.0.0" , "Specify a listen address" )
2024-09-17 03:23:49 -03:00
flag . Parse ( )
2024-10-29 19:34:26 -03:00
if * h3 {
client = h3client
} else {
client = h2client
}
2024-10-29 15:01:35 -03:00
if * https {
if len ( tls_cert ) <= 0 {
fmt . Println ( "tls-cert argument is missing" )
fmt . Println ( "You need a TLS certificate for HTTPS" )
}
if len ( tls_key ) <= 0 {
fmt . Println ( "tls-key argument is missing" )
fmt . Println ( "You need a TLS key for HTTPS" )
}
}
2024-10-28 01:58:29 -03:00
ipv6_only = * ipv6
2024-10-29 01:29:55 -03:00
mux := http . NewServeMux ( )
mux . HandleFunc ( "/" , root )
2024-10-29 15:01:35 -03:00
mux . HandleFunc ( "/health" , health )
mux . HandleFunc ( "/stats" , stats )
mux . HandleFunc ( "/videoplayback" , beforeAll ( videoplayback ) )
mux . HandleFunc ( "/vi/" , beforeAll ( vi ) )
mux . HandleFunc ( "/vi_webp/" , beforeAll ( vi ) )
mux . HandleFunc ( "/sb/" , beforeAll ( vi ) )
mux . HandleFunc ( "/ggpht/" , beforeAll ( ggpht ) )
mux . HandleFunc ( "/a/" , beforeAll ( ggpht ) )
mux . HandleFunc ( "/ytc/" , beforeAll ( ggpht ) )
go requestPerSecond ( )
go requestPerMinute ( )
2024-10-29 01:29:55 -03:00
2021-03-12 03:59:53 -03:00
srv := & http . Server {
ReadTimeout : 5 * time . Second ,
2022-04-18 04:33:19 -04:00
WriteTimeout : 1 * time . Hour ,
2024-10-04 20:56:01 -03:00
Addr : string ( host ) + ":" + string ( port ) ,
2024-10-29 01:29:55 -03:00
Handler : mux ,
2021-03-12 03:59:53 -03:00
}
2024-10-02 19:04:19 -03:00
2024-10-04 20:56:01 -03:00
socket := string ( sock )
syscall . Unlink ( socket )
listener , err := net . Listen ( "unix" , socket )
2024-10-29 01:29:55 -03:00
fmt . Println ( "Unix socket listening at:" , string ( sock ) )
2024-10-04 20:56:01 -03:00
2020-10-25 09:41:17 -03:00
if err != nil {
2023-11-07 12:18:20 -03:00
fmt . Println ( "Failed to bind to UDS, please check the socket name, falling back to TCP/IP" )
2020-10-25 09:41:17 -03:00
fmt . Println ( err . Error ( ) )
2023-11-07 12:18:20 -03:00
err := srv . ListenAndServe ( )
if err != nil {
fmt . Println ( "Cannot bind to port" , string ( port ) , "Error:" , err )
fmt . Println ( "Please try changing the port number" )
}
2020-10-25 09:41:17 -03:00
} else {
2021-03-04 05:57:42 -03:00
defer listener . Close ( )
2024-10-02 19:04:19 -03:00
// To allow everyone to access the socket
err = os . Chmod ( socket , 0777 )
if err != nil {
fmt . Println ( "Error setting permissions:" , err )
return
} else {
fmt . Println ( "Setting socket permissions to 777" )
}
go srv . Serve ( listener )
2024-10-04 20:56:01 -03:00
if * https {
2024-10-29 01:29:55 -03:00
fmt . Println ( "Serving HTTPS at port" , string ( port ) )
2024-10-29 15:01:35 -03:00
if err := srv . ListenAndServeTLS ( tls_cert , tls_key ) ; err != nil {
2024-10-04 20:56:01 -03:00
log . Fatal ( err )
}
} else {
2024-10-29 01:29:55 -03:00
fmt . Println ( "Serving HTTP at port" , string ( port ) )
2024-10-04 20:56:01 -03:00
srv . ListenAndServe ( )
}
2020-10-25 09:41:17 -03:00
}
2020-10-24 12:47:41 -03:00
}