diff --git a/go.mod b/go.mod index 8ca28d7..b025ea1 100644 --- a/go.mod +++ b/go.mod @@ -7,5 +7,6 @@ require github.com/valyala/fasthttp v1.55.0 require ( github.com/andybalholm/brotli v1.1.0 // indirect github.com/klauspost/compress v1.17.9 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index aecbfe9..31dab69 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1 github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8= diff --git a/server.go b/server.go index 5cb252e..8407696 100644 --- a/server.go +++ b/server.go @@ -1,13 +1,127 @@ package main import ( + "bufio" + "bytes" + "database/sql" + "fmt" + "os" + "strings" + + _ "github.com/mattn/go-sqlite3" "github.com/valyala/fasthttp" ) +const behindProxy = true + +var removeHeaders = map[string]bool{ + "date": true, + "expires": true, + "server": true, +} + +var conn *sql.DB +var stmt *sql.Stmt + func main() { + db, err := sql.Open("sqlite3", "archive.db") + if err != nil { + return + } + conn = db + defer db.Close() + + sel_stmt, err := db.Prepare("select id, code from data where method = ? and url = ? limit 1") + if err != nil { + return + } + stmt = sel_stmt + fasthttp.ListenAndServe("127.0.0.1:4001", handler) } func handler(ctx *fasthttp.RequestCtx) { - ctx.Response.SetStatusCode(200) + // -- find in DB and read id+code + uri := ctx.URI() + + var scheme []byte + if behindProxy { + scheme = ctx.Request.Header.Peek("X-Forwarded-Proto") + } else { + scheme = uri.Scheme() + } + + host, port := parseHost( + uri.Host(), + bytes.Equal(scheme, []byte("https")), + ) + + row := stmt.QueryRow( + ctx.Method(), + fmt.Sprintf( + "%s://%s:%d%s", + scheme, + host, + port, + ctx.RequestURI(), + ), + ) + + var id int + var code int + + err := row.Scan(&id, &code) + if err != nil { + ctx.Response.SetStatusCode(404) + return + } + + // -- set status code + if code != 0 { + ctx.Response.SetStatusCode(code) + } + + // -- find in FS and read headers+body + path := "storage/" + string(id) + + fh, err := os.Open(path + "/headers") + if err != nil { + ctx.Response.SetStatusCode(500) + ctx.Response.SetBodyString("Unable to read headers: " + err.Error()) + return + } + sc := bufio.NewScanner(fh) + for sc.Scan() { + header := strings.SplitN(sc.Text(), ": ", 2) + name, value := strings.ToLower(header[0]), header[1] + if removeHeaders[name] { + continue + } + ctx.Response.Header.Add(name, value) + } + fh.Close() + + fb, err := os.Open(path + "/body") + if err != nil { + ctx.Response.SetStatusCode(500) + ctx.Response.SetBodyString("Unable to read body: " + err.Error()) + return + } + + ctx.Response.SetBodyStream(fb, -1) +} + +func parseHost(host []byte, https bool) ([]byte, []byte) { + idx := bytes.LastIndex(host, []byte(":")) + var port []byte + if idx != -1 { + port = host[idx+1:] + } else { + if https { + port = []byte("443") + } else { + port = []byte("80") + } + } + return host[:idx], port }