initial commit
This commit is contained in:
commit
8681a45f60
6 changed files with 912 additions and 0 deletions
154
main.go
Normal file
154
main.go
Normal file
|
@ -0,0 +1,154 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
minio "github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
S3 S3Config `json:"s3"`
|
||||
Bind string `json:"bind"`
|
||||
}
|
||||
|
||||
type S3Config struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
AccessKeyID string `json:"access_key_id"`
|
||||
SecretAccessKey string `json:"secret_access_key"`
|
||||
Bucket string `json:"bucket"`
|
||||
}
|
||||
|
||||
var (
|
||||
config = Config{
|
||||
Bind: ":8080",
|
||||
}
|
||||
|
||||
minioClient *minio.Client
|
||||
)
|
||||
|
||||
func loadconfig(filename string) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
log.Fatal("error reading", filename, ":", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := json.NewDecoder(f).Decode(&config); err != nil {
|
||||
log.Fatal("error parsing", filename, ":", err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
loadconfig("caching-proxy.json")
|
||||
initializeMinioClient()
|
||||
|
||||
log.Println("starting listener on", config.Bind)
|
||||
if err := http.ListenAndServe(config.Bind, proxyhandler{}); err != nil {
|
||||
log.Fatal("error starting http listener:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func initializeMinioClient() {
|
||||
var err error
|
||||
minioClient, err = minio.New(config.S3.Endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(config.S3.AccessKeyID, config.S3.SecretAccessKey, ""),
|
||||
Secure: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln("error creating minio client:", err)
|
||||
}
|
||||
}
|
||||
|
||||
type proxyhandler struct{}
|
||||
|
||||
func (proxyhandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
requestPath := strings.SplitN(r.URL.Path, "/", 3)
|
||||
if len(requestPath) != 3 || requestPath[2] == "" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte("not found"))
|
||||
log.Println("invalid request, 404", r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
get(requestPath[1]+"/"+requestPath[2], w)
|
||||
}
|
||||
|
||||
func get(path string, w http.ResponseWriter) {
|
||||
source, err := minioClient.GetObject(context.Background(), config.S3.Bucket, path, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
log.Println("error getting cached object for", path, ":", err)
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
stat, err := source.Stat()
|
||||
if err != nil {
|
||||
resp := minio.ToErrorResponse(err)
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
download(path, w)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("error getting cached file", path, ":", resp.Code)
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Length", fmt.Sprint(stat.Size))
|
||||
w.Header().Add("Content-Type", stat.ContentType)
|
||||
|
||||
log.Println("cache hit: ", path)
|
||||
|
||||
if _, err := io.Copy(w, source); err != nil {
|
||||
log.Println("error serving cached file:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func download(path string, w http.ResponseWriter) {
|
||||
resp, err := http.Get("https://" + path)
|
||||
if err != nil {
|
||||
log.Println("error getting", path, ":", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
w.Header().Add("Content-Length", fmt.Sprint(resp.ContentLength))
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
|
||||
if resp.StatusCode > 299 {
|
||||
log.Println("upstream non-ok:", resp.Status, ":", path)
|
||||
io.Copy(w, resp.Body)
|
||||
return
|
||||
}
|
||||
|
||||
reader := io.TeeReader(resp.Body, infallableWriter{Next: w})
|
||||
|
||||
_, err = minioClient.PutObject(resp.Request.Context(), config.S3.Bucket, path, reader, resp.ContentLength, minio.PutObjectOptions{})
|
||||
if err != nil {
|
||||
log.Println("error PUTting", path, ":", err)
|
||||
}
|
||||
|
||||
log.Println("cache miss:", path)
|
||||
}
|
||||
|
||||
type infallableWriter struct {
|
||||
Next io.Writer
|
||||
}
|
||||
|
||||
func (i infallableWriter) Write(data []byte) (int, error) {
|
||||
n, err := i.Next.Write(data)
|
||||
if err != nil {
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue