102 lines
2.1 KiB
Go
102 lines
2.1 KiB
Go
package hmns3
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"git.handmade.network/hmn/hmn/src/config"
|
|
"git.handmade.network/hmn/hmn/src/jobs"
|
|
"git.handmade.network/hmn/hmn/src/logging"
|
|
"git.handmade.network/hmn/hmn/src/utils"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
const dir = "./tmp/s3"
|
|
|
|
type server struct {
|
|
log zerolog.Logger
|
|
}
|
|
|
|
func StartServer(ctx context.Context) jobs.Job {
|
|
if !config.Config.DigitalOcean.RunFakeServer {
|
|
return jobs.Noop()
|
|
}
|
|
|
|
utils.Must0(os.MkdirAll(dir, fs.ModePerm))
|
|
|
|
s := server{
|
|
log: logging.ExtractLogger(ctx).With().
|
|
Str("module", "S3 server").
|
|
Logger(),
|
|
}
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodGet {
|
|
s.getObject(w, r)
|
|
} else if r.Method == http.MethodPut {
|
|
s.putObject(w, r)
|
|
} else {
|
|
panic("Unimplemented method!")
|
|
}
|
|
})
|
|
|
|
job := jobs.New()
|
|
srv := http.Server{
|
|
Addr: config.Config.DigitalOcean.FakeAddr,
|
|
}
|
|
|
|
s.log.Info().Msg("Starting local S3 server")
|
|
go func() {
|
|
defer job.Done()
|
|
err := srv.ListenAndServe()
|
|
if err != nil {
|
|
if errors.Is(err, http.ErrServerClosed) {
|
|
// This is normal and fine
|
|
} else {
|
|
panic(err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
<-ctx.Done()
|
|
s.log.Info().Msg("Shutting down local S3 server")
|
|
srv.Shutdown(context.Background())
|
|
}()
|
|
|
|
return job
|
|
}
|
|
|
|
func (s *server) getObject(w http.ResponseWriter, r *http.Request) {
|
|
bucket, key := bucketKey(r)
|
|
|
|
file := utils.Must1(os.Open(filepath.Join(dir, bucket, key)))
|
|
io.Copy(w, file)
|
|
}
|
|
|
|
func (s *server) putObject(w http.ResponseWriter, r *http.Request) {
|
|
bucket, key := bucketKey(r)
|
|
|
|
w.Header().Set("Location", fmt.Sprintf("/%s", bucket))
|
|
utils.Must0(os.MkdirAll(filepath.Join(dir, bucket), fs.ModePerm))
|
|
if key != "" {
|
|
file := utils.Must1(os.Create(filepath.Join(dir, bucket, key)))
|
|
io.Copy(file, r.Body)
|
|
}
|
|
}
|
|
|
|
func bucketKey(r *http.Request) (string, string) {
|
|
slashIdx := strings.IndexByte(r.URL.Path[1:], '/')
|
|
if slashIdx == -1 {
|
|
return r.URL.Path[1:], ""
|
|
} else {
|
|
return r.URL.Path[1 : 1+slashIdx], strings.ReplaceAll(r.URL.Path[2+slashIdx:], "/", "~")
|
|
}
|
|
}
|