package gpaste import ( "encoding/json" "io" "net/http" "strings" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/google/uuid" "go.uber.org/zap" ) type HTTPServer struct { store FileStore config *ServerConfig Logger *zap.SugaredLogger AccessLogger *zap.SugaredLogger http.Server } func NewHTTPServer(cfg *ServerConfig) *HTTPServer { srv := &HTTPServer{ config: cfg, Logger: zap.NewNop().Sugar(), AccessLogger: zap.NewNop().Sugar(), } srv.store = NewMemoryFileStore() r := chi.NewRouter() r.Use(middleware.RealIP) r.Use(middleware.RequestID) r.Use(srv.MiddlewareAccessLogger) r.Get("/", srv.HandlerIndex) r.Post("/api/file", srv.HandlerAPIFilePost) r.Get("/api/file/{id}", srv.HandlerAPIFileGet) srv.Handler = r return srv } func (s *HTTPServer) HandlerIndex(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("index")) } func (s *HTTPServer) HandlerAPIFilePost(w http.ResponseWriter, r *http.Request) { f := &File{ ID: uuid.Must(uuid.NewRandom()).String(), Body: r.Body, } reqID := middleware.GetReqID(r.Context()) // Check if multipart form ct := r.Header.Get("Content-Type") if strings.Contains(ct, "multipart/form-data") { s.processMultiPartFormUpload(w, r) return } err := s.store.Store(f) if err != nil { w.WriteHeader(http.StatusInternalServerError) s.Logger.Warnw("Error storing file.", "req_id", reqID, "error", err, "id", f.ID, "remote_addr", r.RemoteAddr) return } s.Logger.Infow("Stored file.", "req_id", reqID, "id", f.ID, "remote_addr", r.RemoteAddr) var resp = struct { Message string `json:"message"` ID string `json:"id"` URL string `json:"url"` }{ Message: "OK", ID: f.ID, URL: "TODO", } w.WriteHeader(http.StatusAccepted) encoder := json.NewEncoder(w) if err := encoder.Encode(&resp); err != nil { s.Logger.Warnw("Error encoding response to client.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) } } func (s *HTTPServer) HandlerAPIFileGet(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") if id == "" { w.WriteHeader(http.StatusBadRequest) return } f, err := s.store.Get(id) if err != nil { // TODO: LOG w.WriteHeader(http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) if _, err := io.Copy(w, f.Body); err != nil { reqID := middleware.GetReqID(r.Context()) s.Logger.Warnw("Error writing file to client.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) } } func (s *HTTPServer) processMultiPartFormUpload(w http.ResponseWriter, r *http.Request) { reqID := middleware.GetReqID(r.Context()) type resp struct { Message string `json:"message"` ID string `json:"id"` URL string `json:"url"` } var responses []resp if err := r.ParseMultipartForm(1024 * 1024 * 10); err != nil { s.Logger.Warnw("Error parsing multipart form.", "req_id", reqID, "err", err) } for k := range r.MultipartForm.File { ff, fh, err := r.FormFile(k) if err != nil { s.Logger.Warnw("Error reading file from multipart form.", "req_id", reqID, "error", err) return } f := &File{ ID: uuid.Must(uuid.NewRandom()).String(), OriginalFilename: fh.Filename, Body: ff, } if err := s.store.Store(f); err != nil { w.WriteHeader(http.StatusInternalServerError) s.Logger.Warnw("Error storing file.", "req_id", reqID, "error", err, "id", f.ID, "remote_addr", r.RemoteAddr) return } s.Logger.Infow("Stored file.", "req_id", reqID, "id", f.ID, "filename", f.OriginalFilename, "remote_addr", r.RemoteAddr) responses = append(responses, resp{Message: "OK", ID: f.ID, URL: "TODO"}) } w.WriteHeader(http.StatusAccepted) encoder := json.NewEncoder(w) if err := encoder.Encode(&responses); err != nil { s.Logger.Warnw("Error encoding response to client.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) } }