2024-12-16 21:11:32 +08:00
package main
import (
"bytes"
"context"
2024-12-18 10:47:18 +08:00
"flag"
2024-12-16 21:11:32 +08:00
"fmt"
"io"
2024-12-25 11:00:02 +08:00
"log/slog"
2024-12-16 21:11:32 +08:00
"net/http"
"os"
"path/filepath"
"regexp"
2024-12-19 00:03:22 +08:00
"slices"
2024-12-18 17:16:55 +08:00
"strings"
2024-12-16 21:11:32 +08:00
"sync"
"time"
2024-12-18 10:47:18 +08:00
"github.com/getsentry/sentry-go"
2024-12-16 21:11:32 +08:00
"gopkg.in/yaml.v3"
)
var zeroTime time . Time
type UpstreamMatch struct {
Match string ` yaml:"match" `
Replace string ` yaml:"replace" `
}
type Upstream struct {
Server string ` yaml:"server" `
Match UpstreamMatch ` yaml:"match" `
}
func ( upstream Upstream ) GetPath ( orig string ) ( string , bool , error ) {
if upstream . Match . Match == "" || upstream . Match . Replace == "" {
return orig , true , nil
}
matcher , err := regexp . Compile ( upstream . Match . Match )
if err != nil {
return "" , false , err
}
return matcher . ReplaceAllString ( orig , upstream . Match . Replace ) , matcher . MatchString ( orig ) , nil
}
type LocalStorage struct {
2024-12-25 11:00:02 +08:00
Path string ` yaml:"path" `
TemporaryFilePattern string ` yaml:"temporary-file-pattern" `
2024-12-16 21:11:32 +08:00
}
2024-12-18 17:16:55 +08:00
type Accel struct {
EnableByHeader string ` yaml:"enable-by-header" `
ResponseWithHeaders [ ] string ` yaml:"response-with-headers" `
}
2024-12-16 21:11:32 +08:00
type Storage struct {
Type string ` yaml:"type" `
Local * LocalStorage ` yaml:"local" `
2024-12-18 17:16:55 +08:00
Accel Accel ` yaml:"accel" `
2024-12-16 21:11:32 +08:00
}
2024-12-19 00:03:22 +08:00
type CachePolicyOnPath struct {
Match string ` yaml:"match" `
RefreshAfter string ` yaml:"refresh-after" `
}
2024-12-16 21:11:32 +08:00
type Cache struct {
2024-12-19 00:03:22 +08:00
RefreshAfter time . Duration ` yaml:"refresh-after" `
Policies [ ] CachePolicyOnPath ` yaml:"policies" `
2024-12-16 21:11:32 +08:00
}
type MiscConfig struct {
FirstChunkBytes uint64 ` yaml:"first-chunk-bytes" `
ChunkBytes uint64 ` yaml:"chunk-bytes" `
}
type Config struct {
Upstreams [ ] Upstream ` yaml:"upstream" `
Storage Storage ` yaml:"storage" `
Cache Cache ` yaml:"cache" `
Misc MiscConfig ` yaml:"misc" `
}
type StreamObject struct {
Headers http . Header
Buffer * bytes . Buffer
Offset int
ctx context . Context
wg * sync . WaitGroup
}
func ( memoryObject * StreamObject ) StreamTo ( w io . Writer , wg * sync . WaitGroup ) error {
defer wg . Done ( )
offset := 0
if w == nil {
w = io . Discard
}
OUTER :
for {
select {
case <- memoryObject . ctx . Done ( ) :
break OUTER
default :
}
newOffset := memoryObject . Offset
if newOffset == offset {
time . Sleep ( time . Millisecond )
continue
}
bytes := memoryObject . Buffer . Bytes ( ) [ offset : newOffset ]
written , err := w . Write ( bytes )
if err != nil {
return err
}
offset += written
}
time . Sleep ( time . Millisecond )
2024-12-25 11:00:02 +08:00
slog . With (
"start" , offset ,
"end" , memoryObject . Buffer . Len ( ) ,
"n" , memoryObject . Buffer . Len ( ) - offset ,
) . Debug ( "remain bytes" )
2024-12-16 21:11:32 +08:00
_ , err := w . Write ( memoryObject . Buffer . Bytes ( ) [ offset : ] )
return err
}
type Server struct {
Config
lu * sync . Mutex
o map [ string ] * StreamObject
}
type Chunk struct {
buffer [ ] byte
error error
}
func configFromFile ( path string ) ( * Config , error ) {
file , err := os . Open ( path )
if err != nil {
return nil , err
}
defer file . Close ( )
config := & Config {
Upstreams : [ ] Upstream {
{
Server : "https://mirrors.ustc.edu.cn" ,
} ,
} ,
Storage : Storage {
Type : "local" ,
Local : & LocalStorage {
2024-12-25 11:00:02 +08:00
Path : "./data" ,
TemporaryFilePattern : "temp.*" ,
2024-12-16 21:11:32 +08:00
} ,
2024-12-18 17:16:55 +08:00
Accel : Accel {
ResponseWithHeaders : [ ] string { "X-Sendfile" , "X-Accel-Redirect" } ,
} ,
2024-12-16 21:11:32 +08:00
} ,
Misc : MiscConfig {
FirstChunkBytes : 1024 * 1024 * 50 ,
ChunkBytes : 1024 * 1024 ,
} ,
Cache : Cache {
2024-12-19 00:03:22 +08:00
RefreshAfter : time . Hour ,
2024-12-16 21:11:32 +08:00
} ,
}
if err := yaml . NewDecoder ( file ) . Decode ( & config ) ; err != nil {
return nil , err
}
2024-12-18 17:08:47 +08:00
if config . Storage . Local != nil {
localPath , err := filepath . Abs ( config . Storage . Local . Path )
if err != nil {
return nil , err
}
config . Storage . Local . Path = localPath
}
2024-12-16 21:11:32 +08:00
return config , nil
}
2024-12-18 17:16:55 +08:00
func ( server * Server ) serveFile ( w http . ResponseWriter , r * http . Request , path string ) {
if location := r . Header . Get ( server . Storage . Accel . EnableByHeader ) ; server . Storage . Accel . EnableByHeader != "" && location != "" {
relPath , err := filepath . Rel ( server . Storage . Local . Path , path )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
2024-12-19 01:27:14 +08:00
return
2024-12-18 17:16:55 +08:00
}
accelPath := filepath . Join ( location , relPath )
for _ , headerKey := range server . Storage . Accel . ResponseWithHeaders {
w . Header ( ) . Set ( headerKey , accelPath )
}
return
}
http . ServeFile ( w , r , path )
}
2024-12-16 21:11:32 +08:00
func ( server * Server ) handleRequest ( w http . ResponseWriter , r * http . Request ) {
fullpath := filepath . Join ( server . Storage . Local . Path , r . URL . Path )
fullpath , err := filepath . Abs ( fullpath )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
return
}
2024-12-18 17:08:47 +08:00
if ! strings . HasPrefix ( fullpath , server . Storage . Local . Path ) {
http . Error ( w , "crossing local directory boundary" , http . StatusBadRequest )
return
}
2024-12-16 21:11:32 +08:00
ranged := r . Header . Get ( "Range" ) != ""
localStatus , mtime , err := server . checkLocal ( w , r , fullpath )
2025-01-09 23:14:49 +08:00
slog . With ( "status" , localStatus , "mtime" , mtime , "error" , err ) . Debug ( "local status checked" )
2024-12-16 21:11:32 +08:00
if os . IsPermission ( err ) {
http . Error ( w , err . Error ( ) , http . StatusForbidden )
} else if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
} else if localStatus != localNotExists {
if localStatus == localExistsButNeedHead {
if ranged {
server . streamOnline ( nil , r , mtime , fullpath )
2024-12-18 17:16:55 +08:00
server . serveFile ( w , r , fullpath )
2024-12-16 21:11:32 +08:00
} else {
server . streamOnline ( w , r , mtime , fullpath )
}
} else {
2024-12-18 17:16:55 +08:00
server . serveFile ( w , r , fullpath )
2024-12-16 21:11:32 +08:00
}
} else {
if ranged {
server . streamOnline ( nil , r , mtime , fullpath )
2024-12-18 17:16:55 +08:00
server . serveFile ( w , r , fullpath )
2024-12-16 21:11:32 +08:00
} else {
server . streamOnline ( w , r , mtime , fullpath )
}
}
}
type localStatus int
const (
localNotExists localStatus = iota
localExists
localExistsButNeedHead
)
func ( server * Server ) checkLocal ( w http . ResponseWriter , _ * http . Request , key string ) ( exists localStatus , mtime time . Time , err error ) {
if stat , err := os . Stat ( key ) ; err == nil {
2024-12-19 00:03:22 +08:00
refreshAfter := server . Cache . RefreshAfter
refresh := ""
for _ , policy := range server . Cache . Policies {
if match , err := regexp . MatchString ( policy . Match , key ) ; err != nil {
return 0 , zeroTime , err
} else if match {
if dur , err := time . ParseDuration ( policy . RefreshAfter ) ; err != nil {
if slices . Contains ( [ ] string { "always" , "never" } , policy . RefreshAfter ) {
refresh = policy . RefreshAfter
} else {
return 0 , zeroTime , err
}
} else {
refreshAfter = dur
}
break
}
}
2025-01-09 23:14:49 +08:00
mtime := stat . ModTime ( )
slog . With ( "policy" , refresh , "after" , refreshAfter , "mtime" , mtime , "key" , key ) . Debug ( "refresh policy checked" )
if ( mtime . Add ( refreshAfter ) . Before ( time . Now ( ) ) || refresh == "always" ) && refresh != "never" {
2024-12-16 21:11:32 +08:00
return localExistsButNeedHead , mtime . In ( time . UTC ) , nil
}
2024-12-18 14:17:59 +08:00
return localExists , mtime . In ( time . UTC ) , nil
2024-12-16 21:11:32 +08:00
} else if os . IsPermission ( err ) {
http . Error ( w , err . Error ( ) , http . StatusForbidden )
} else if ! os . IsNotExist ( err ) {
return localNotExists , zeroTime , err
}
return localNotExists , zeroTime , nil
}
func ( server * Server ) streamOnline ( w http . ResponseWriter , r * http . Request , mtime time . Time , key string ) {
memoryObject , exists := server . o [ r . URL . Path ]
locked := false
defer func ( ) {
if locked {
server . lu . Unlock ( )
locked = false
}
} ( )
if ! exists {
server . lu . Lock ( )
locked = true
2024-12-25 11:03:18 +08:00
memoryObject , exists = server . o [ r . URL . Path ]
2024-12-16 21:11:32 +08:00
}
if exists {
if locked {
server . lu . Unlock ( )
locked = false
}
if w != nil {
memoryObject . wg . Add ( 1 )
for k := range memoryObject . Headers {
v := memoryObject . Headers . Get ( k )
w . Header ( ) . Set ( k , v )
}
if err := memoryObject . StreamTo ( w , memoryObject . wg ) ; err != nil {
2024-12-25 11:00:02 +08:00
slog . With ( "error" , err ) . Warn ( "failed to stream response with existing memory object" )
2024-12-16 21:11:32 +08:00
}
}
} else {
2024-12-25 11:00:02 +08:00
slog . With ( "mtime" , mtime ) . Debug ( "checking fastest upstream" )
2024-12-16 21:11:32 +08:00
selectedIdx , response , chunks , err := server . fastesUpstream ( r , mtime )
if chunks == nil && mtime != zeroTime {
2024-12-25 11:00:02 +08:00
slog . With ( "upstreamIdx" , selectedIdx , "key" , key ) . Debug ( "not modified. using local version" )
2024-12-16 21:11:32 +08:00
if w != nil {
2024-12-18 17:16:55 +08:00
server . serveFile ( w , r , key )
2024-12-16 21:11:32 +08:00
}
return
}
if err != nil {
2024-12-25 11:00:02 +08:00
slog . With ( "error" , err ) . Warn ( "failed to select fastest upstream" )
2024-12-16 21:11:32 +08:00
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
if selectedIdx == - 1 || response == nil || chunks == nil {
2024-12-25 11:00:02 +08:00
slog . Debug ( "no upstream is selected" )
2024-12-16 21:11:32 +08:00
http . NotFound ( w , r )
return
}
if response . StatusCode == http . StatusNotModified {
2024-12-25 11:00:02 +08:00
slog . With ( "upstreamIdx" , selectedIdx ) . Debug ( "not modified. using local version" )
2024-12-18 10:47:18 +08:00
os . Chtimes ( key , zeroTime , time . Now ( ) )
2024-12-18 17:16:55 +08:00
server . serveFile ( w , r , key )
2024-12-16 21:11:32 +08:00
return
}
2024-12-25 11:00:02 +08:00
slog . With (
"upstreamIdx" , selectedIdx ,
) . Debug ( "found fastest upstream" )
2024-12-16 21:11:32 +08:00
buffer := & bytes . Buffer { }
ctx , cancel := context . WithCancel ( r . Context ( ) )
defer cancel ( )
memoryObject = & StreamObject {
Headers : response . Header ,
Buffer : buffer ,
ctx : ctx ,
wg : & sync . WaitGroup { } ,
}
server . o [ r . URL . Path ] = memoryObject
server . lu . Unlock ( )
locked = false
2024-12-19 10:58:46 +08:00
err = nil
2024-12-16 21:11:32 +08:00
if w != nil {
memoryObject . wg . Add ( 1 )
for k := range memoryObject . Headers {
v := memoryObject . Headers . Get ( k )
w . Header ( ) . Set ( k , v )
}
go memoryObject . StreamTo ( w , memoryObject . wg )
}
for chunk := range chunks {
if chunk . error != nil {
2024-12-18 14:17:59 +08:00
err = chunk . error
2024-12-25 11:00:02 +08:00
slog . With ( "error" , err ) . Warn ( "failed to read from upstream" )
2024-12-16 21:11:32 +08:00
}
if chunk . buffer == nil {
break
}
n , _ := buffer . Write ( chunk . buffer )
memoryObject . Offset += n
}
cancel ( )
memoryObject . wg . Wait ( )
2025-01-09 21:33:43 +08:00
if response . ContentLength > 0 {
if memoryObject . Offset == int ( response . ContentLength ) && err != nil {
slog . With ( "length" , memoryObject . Offset , "error" , err , "upstreamIdx" , selectedIdx ) . Debug ( "something happened during download. but response body is read as whole. so error is reset to nil" )
err = nil
}
} else if err == io . EOF {
slog . With ( "length" , memoryObject . Offset , "error" , err , "upstreamIdx" , selectedIdx ) . Debug ( "something happened during download. content length is not specified and error is a eof. so error is reset to nil" )
err = nil
}
2024-12-18 14:17:59 +08:00
if err != nil {
2024-12-25 11:00:02 +08:00
slog . With ( "error" , err , "upstreamIdx" , selectedIdx ) . Error ( "something happened during download. will not cache this response" )
2024-12-18 14:17:59 +08:00
}
2024-12-16 21:11:32 +08:00
go func ( ) {
2024-12-25 11:00:02 +08:00
defer func ( ) {
server . lu . Lock ( )
defer server . lu . Unlock ( )
delete ( server . o , r . URL . Path )
slog . Debug ( "memory object released" )
} ( )
2024-12-19 10:58:46 +08:00
if err == nil {
2024-12-25 11:00:02 +08:00
slog . Debug ( "preparing to release memory object" )
2024-12-19 10:58:46 +08:00
mtime := zeroTime
lastModifiedHeader := response . Header . Get ( "Last-Modified" )
if lastModified , err := time . Parse ( time . RFC1123 , lastModifiedHeader ) ; err != nil {
2024-12-25 11:00:02 +08:00
slog . With (
"error" , err ,
"value" , lastModifiedHeader ,
"url" , response . Request . URL ,
) . Debug ( "failed to parse last modified header value. set modified time to now" )
2024-12-19 10:58:46 +08:00
} else {
2024-12-25 11:00:02 +08:00
slog . With (
"header" , lastModifiedHeader ,
"value" , lastModified ,
"url" , response . Request . URL ,
) . Debug ( "found modified time" )
2024-12-19 10:58:46 +08:00
mtime = lastModified
}
if err := os . MkdirAll ( server . Storage . Local . Path , 0755 ) ; err != nil {
2024-12-25 11:00:02 +08:00
slog . With ( "error" , err ) . Warn ( "failed to create local storage path" )
2024-12-19 10:58:46 +08:00
}
2024-12-25 11:00:02 +08:00
if server . Config . Storage . Local . TemporaryFilePattern == "" {
if err := os . WriteFile ( key , buffer . Bytes ( ) , 0644 ) ; err != nil {
slog . With ( "error" , err ) . Warn ( "failed to write file" )
os . Remove ( key )
}
return
}
fp , err := os . CreateTemp ( server . Storage . Local . Path , server . Storage . Local . TemporaryFilePattern )
2024-12-19 10:58:46 +08:00
if err != nil {
2024-12-25 11:00:02 +08:00
slog . With (
"key" , key ,
"path" , server . Storage . Local . Path ,
"pattern" , server . Storage . Local . TemporaryFilePattern ,
"error" , err ,
) . Warn ( "failed to create template file" )
return
}
name := fp . Name ( )
if _ , err := fp . Write ( buffer . Bytes ( ) ) ; err != nil {
2024-12-19 10:58:46 +08:00
fp . Close ( )
os . Remove ( name )
2024-12-25 11:00:02 +08:00
slog . With ( "error" , err ) . Warn ( "failed to write into template file" )
2024-12-19 10:58:46 +08:00
} else if err := fp . Close ( ) ; err != nil {
os . Remove ( name )
2024-12-25 11:00:02 +08:00
slog . With ( "error" , err ) . Warn ( "failed to close template file" )
2024-12-19 10:58:46 +08:00
} else {
os . Chtimes ( name , zeroTime , mtime )
dirname := filepath . Dir ( key )
os . MkdirAll ( dirname , 0755 )
os . Remove ( key )
os . Rename ( name , key )
}
2024-12-16 21:11:32 +08:00
}
} ( )
2024-12-19 10:58:46 +08:00
2024-12-16 21:11:32 +08:00
}
}
func ( server * Server ) fastesUpstream ( r * http . Request , lastModified time . Time ) ( resultIdx int , resultResponse * http . Response , resultCh chan Chunk , resultErr error ) {
returnLock := & sync . Mutex { }
upstreams := len ( server . Upstreams )
cancelFuncs := make ( [ ] func ( ) , upstreams )
selectedCh := make ( chan int , 1 )
selectedOnce := & sync . Once { }
wg := & sync . WaitGroup { }
wg . Add ( len ( server . Upstreams ) )
defer close ( selectedCh )
for idx := range server . Upstreams {
idx := idx
ctx , cancel := context . WithCancel ( context . Background ( ) )
cancelFuncs [ idx ] = cancel
2024-12-25 11:00:02 +08:00
logger := slog . With ( "upstreamIdx" , idx )
2024-12-16 21:11:32 +08:00
go func ( ) {
defer wg . Done ( )
response , ch , err := server . tryUpstream ( ctx , idx , r , lastModified )
if err == context . Canceled { // others returned
2024-12-25 11:00:02 +08:00
logger . Debug ( "context canceled" )
2024-12-16 21:11:32 +08:00
return
}
if err != nil {
2024-12-18 14:17:59 +08:00
if err != context . Canceled && err != context . DeadlineExceeded {
2024-12-25 11:00:02 +08:00
logger . With ( "error" , err ) . Warn ( "upstream has error" )
2024-12-18 14:17:59 +08:00
}
2024-12-16 21:11:32 +08:00
return
}
if response == nil {
return
}
locked := returnLock . TryLock ( )
if ! locked {
return
}
defer returnLock . Unlock ( )
selectedOnce . Do ( func ( ) {
resultResponse , resultCh , resultErr = response , ch , err
selectedCh <- idx
2024-12-18 14:17:59 +08:00
for cancelIdx , cancel := range cancelFuncs {
if cancelIdx == idx {
2024-12-25 11:00:02 +08:00
slog . With ( "upstreamIdx" , cancelIdx ) . Debug ( "selected thus not canceled" )
2024-12-16 21:11:32 +08:00
continue
}
2024-12-25 11:00:02 +08:00
slog . With ( "upstreamIdx" , cancelIdx ) . Debug ( "not selected and thus canceled" )
2024-12-16 21:11:32 +08:00
cancel ( )
}
2024-12-25 11:00:02 +08:00
logger . Debug ( "upstream is selected" )
2024-12-16 21:11:32 +08:00
} )
2024-12-25 11:00:02 +08:00
logger . Debug ( "voted" )
2024-12-16 21:11:32 +08:00
} ( )
}
wg . Wait ( )
2024-12-25 11:00:02 +08:00
slog . Debug ( "all upstream returned" )
2024-12-16 21:11:32 +08:00
resultIdx = - 1
select {
case idx := <- selectedCh :
resultIdx = idx
default :
}
return
}
func ( server * Server ) tryUpstream ( ctx context . Context , upstreamIdx int , r * http . Request , lastModified time . Time ) ( response * http . Response , chunks chan Chunk , err error ) {
upstream := server . Upstreams [ upstreamIdx ]
2024-12-25 11:00:02 +08:00
logger := slog . With ( "upstreamIdx" , upstreamIdx )
2024-12-16 21:11:32 +08:00
newpath , matched , err := upstream . GetPath ( r . URL . Path )
2024-12-25 11:00:02 +08:00
logger . With (
"path" , newpath ,
"matched" , matched ,
) . Debug ( "trying upstream" )
2024-12-16 21:11:32 +08:00
if err != nil {
return nil , nil , err
}
if ! matched {
return nil , nil , nil
}
newurl := upstream . Server + newpath
method := r . Method
if lastModified != zeroTime {
method = http . MethodGet
}
request , err := http . NewRequestWithContext ( ctx , method , newurl , nil )
if err != nil {
return nil , nil , err
}
if lastModified != zeroTime {
2024-12-25 11:00:02 +08:00
logger . With (
"mtime" , lastModified . Format ( time . RFC1123 ) ,
) . Debug ( "check modified since" )
2024-12-16 21:11:32 +08:00
request . Header . Set ( "If-Modified-Since" , lastModified . Format ( time . RFC1123 ) )
}
for _ , k := range [ ] string { "User-Agent" } {
if _ , exists := request . Header [ k ] ; exists {
request . Header . Set ( k , r . Header . Get ( k ) )
}
}
response , err = http . DefaultClient . Do ( request )
if err != nil {
return nil , nil , err
}
2024-12-25 11:00:02 +08:00
slog . With ( "status" , response . StatusCode ) . Debug ( "responded" )
2024-12-16 21:11:32 +08:00
if response . StatusCode == http . StatusNotModified {
return response , nil , nil
}
if response . StatusCode >= 400 && response . StatusCode < 500 {
return nil , nil , nil
}
if response . StatusCode < 200 || response . StatusCode >= 500 {
2024-12-25 11:00:02 +08:00
slog . With (
"url" , newurl ,
"status" , response . StatusCode ,
) . Warn ( "unexpected status" )
2024-12-16 21:11:32 +08:00
return response , nil , fmt . Errorf ( "unexpected status(url=%v): %v: %v" , newurl , response . StatusCode , response )
}
2024-12-19 10:58:46 +08:00
var currentOffset int64
2024-12-16 21:11:32 +08:00
ch := make ( chan Chunk , 1024 )
buffer := make ( [ ] byte , server . Misc . FirstChunkBytes )
n , err := io . ReadAtLeast ( response . Body , buffer , len ( buffer ) )
if err != nil {
if n == 0 {
return response , nil , err
}
}
ch <- Chunk { buffer : buffer [ : n ] }
go func ( ) {
defer close ( ch )
for {
buffer := make ( [ ] byte , server . Misc . ChunkBytes )
n , err := io . ReadAtLeast ( response . Body , buffer , len ( buffer ) )
if n > 0 {
ch <- Chunk { buffer : buffer [ : n ] }
2024-12-19 10:58:46 +08:00
currentOffset += int64 ( n )
2024-12-16 21:11:32 +08:00
}
2024-12-19 10:58:46 +08:00
if response . ContentLength > 0 && currentOffset == response . ContentLength && err == io . EOF || err == io . ErrUnexpectedEOF {
2024-12-25 11:00:02 +08:00
logger . Debug ( "done" )
2024-12-16 21:11:32 +08:00
return
}
if err != nil {
ch <- Chunk { error : err }
return
}
}
} ( )
return response , ch , nil
}
2024-12-18 10:47:18 +08:00
var (
configFilePath = "config.yaml"
logLevel = "info"
sentrydsn = ""
)
func init ( ) {
if v , ok := os . LookupEnv ( "CONFIG_PATH" ) ; ok {
configFilePath = v
}
if v , ok := os . LookupEnv ( "LOG_LEVEL" ) ; ok {
logLevel = v
}
if v , ok := os . LookupEnv ( "SENTRY_DSN" ) ; ok {
sentrydsn = v
}
flag . StringVar ( & configFilePath , "config" , configFilePath , "path to config file" )
flag . StringVar ( & logLevel , "log-level" , logLevel , "log level. (trace, debug, info, warn, error)" )
flag . StringVar ( & sentrydsn , "sentry" , sentrydsn , "sentry dsn to report errors" )
}
2024-12-16 21:11:32 +08:00
func main ( ) {
2024-12-18 10:47:18 +08:00
flag . Parse ( )
2024-12-25 11:00:02 +08:00
lvl := slog . LevelInfo
if err := lvl . UnmarshalText ( [ ] byte ( logLevel ) ) ; err != nil {
slog . With ( "error" , err ) . Error ( "failed to parse log level" )
os . Exit ( - 1 )
2024-12-18 10:47:18 +08:00
} else {
2024-12-25 11:00:02 +08:00
slog . SetLogLoggerLevel ( lvl )
2024-12-18 10:47:18 +08:00
}
if sentrydsn != "" {
if err := sentry . Init ( sentry . ClientOptions {
Dsn : sentrydsn ,
} ) ; err != nil {
2024-12-25 11:00:02 +08:00
slog . With ( "dsn" , sentrydsn , "error" , err ) . Error ( "failed to setup sentry" )
os . Exit ( - 1 )
2024-12-18 10:47:18 +08:00
}
defer sentry . Flush ( time . Second * 3 )
}
config , err := configFromFile ( configFilePath )
2024-12-16 21:11:32 +08:00
if err != nil {
panic ( err )
}
ch := make ( chan any , 10 )
for idx := 0 ; idx < 10 ; idx += 1 {
ch <- idx
}
server := Server {
Config : * config ,
lu : & sync . Mutex { } ,
o : make ( map [ string ] * StreamObject ) ,
}
2024-12-18 14:30:14 +08:00
http . HandleFunc ( "GET /{path...}" , server . handleRequest )
2024-12-25 11:00:02 +08:00
slog . With ( "addr" , ":8881" ) . Info ( "serving app" )
2024-12-16 21:11:32 +08:00
http . ListenAndServe ( ":8881" , nil )
}