126 lines
3.3 KiB
Go
126 lines
3.3 KiB
Go
package server
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha1"
|
|
"fmt"
|
|
"math/big"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/apex/log"
|
|
|
|
"gitlab.com/unboundsoftware/s3uploader/storage"
|
|
)
|
|
|
|
type Server struct {
|
|
*http.ServeMux
|
|
store storage.Storage
|
|
returnUrl string
|
|
logger log.Interface
|
|
now func() time.Time
|
|
}
|
|
|
|
func (s *Server) HandlePut(w http.ResponseWriter, req *http.Request) {
|
|
switch req.Method {
|
|
case http.MethodOptions:
|
|
s.cors(w)
|
|
case http.MethodPut:
|
|
path := strings.TrimPrefix(req.URL.Path, "/put")
|
|
s.upload(path, w, req)
|
|
default:
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
_, _ = w.Write([]byte("This endpoint requires PUT"))
|
|
}
|
|
}
|
|
|
|
func (s *Server) HandleUpload(w http.ResponseWriter, req *http.Request) {
|
|
switch req.Method {
|
|
case http.MethodOptions:
|
|
s.cors(w)
|
|
case http.MethodPut:
|
|
prefix, err := randomString(64)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = w.Write([]byte("An error occurred"))
|
|
return
|
|
}
|
|
now := s.now()
|
|
day := now.Format("20060102")
|
|
prefixHash := hash(prefix)
|
|
dayHash := hash(now.Format(time.RFC3339Nano))
|
|
s.upload(fmt.Sprintf("/%s/%s%s", day, prefixHash, dayHash), w, req)
|
|
default:
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
_, _ = w.Write([]byte("This endpoint requires PUT"))
|
|
}
|
|
}
|
|
|
|
func (s *Server) HandleHealth(w http.ResponseWriter, _ *http.Request) {
|
|
_, _ = w.Write([]byte("OK"))
|
|
}
|
|
|
|
func (s *Server) upload(path string, w http.ResponseWriter, req *http.Request) {
|
|
s.logger.Infof("uploading to %s", path)
|
|
|
|
err := s.store.Store(strings.TrimPrefix(path, "/"), req.Body)
|
|
if err != nil {
|
|
s.logger.WithError(err).Error("error storing object in bucket")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = w.Write([]byte("error storing object in bucket"))
|
|
return
|
|
}
|
|
w.Header().Add("Access-Control-Expose-Headers", "X-File-URL")
|
|
w.Header().Add("Access-Control-Allow-Origin", "*")
|
|
|
|
fileUrl := fmt.Sprintf("%s%s", s.returnUrl, path)
|
|
w.Header().Add("X-File-URL", fileUrl)
|
|
s.logger.Infof("success - file is available at %s", fileUrl)
|
|
_, _ = w.Write([]byte("success"))
|
|
}
|
|
|
|
func (s *Server) cors(w http.ResponseWriter) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "PUT,OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With")
|
|
w.Header().Set("Access-Control-Max-Age", "1728000")
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func New(store storage.Storage, url string, logger log.Interface) http.Handler {
|
|
trimmedReturnUrl := strings.TrimSuffix(url, "/")
|
|
|
|
mux := &Server{
|
|
ServeMux: http.NewServeMux(),
|
|
store: store,
|
|
returnUrl: trimmedReturnUrl,
|
|
logger: logger,
|
|
now: time.Now,
|
|
}
|
|
mux.HandleFunc("/put/", mux.HandlePut)
|
|
mux.HandleFunc("/upload", mux.HandleUpload)
|
|
mux.HandleFunc("/health", mux.HandleHealth)
|
|
return mux
|
|
}
|
|
|
|
func hash(s string) string {
|
|
hasher := sha1.New()
|
|
hasher.Write([]byte(s))
|
|
return fmt.Sprintf("%x", hasher.Sum(nil))
|
|
}
|
|
|
|
func randomString(n int) (string, error) {
|
|
const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
ret := make([]byte, n)
|
|
for i := 0; i < n; i++ {
|
|
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
ret[i] = letters[num.Int64()]
|
|
}
|
|
|
|
return string(ret), nil
|
|
}
|