// Storehouse: HTTP object store server // // Licensed under CC0 1.0 Universal: // https://creativecommons.org/publicdomain/zero/1.0/legalcode package main import ( "errors" "flag" "fmt" "io/ioutil" "log" "net/http" "os" "github.com/labstack/echo/v4" "github.com/pablombg/storehouse/objectstore" ) type storehouse struct { objects objectstore.ObjectStore } func InitStorehouse(memory bool, dataDir string) *storehouse { var objects objectstore.ObjectStore if memory { objects = objectstore.NewMemBackend() } else { objects = objectstore.NewFileBackend(dataDir) } return &storehouse{objects: objects} } func (s *storehouse) putObject(c echo.Context) error { bucketId := c.Param("bucketId") objectId := c.Param("objectId") // Get request body bodyBytes, err := ioutil.ReadAll(c.Request().Body) if err != nil { log.Printf("Error reading body: %v", err) m := &errorJSON{Error: "Error reading body"} return c.JSON(http.StatusInternalServerError, m) } err = s.objects.CreateObject(bucketId, objectId, bodyBytes) // Check errors creating the object if err != nil { log.Printf("Error creating the object: %v", err) switch err.(type) { case *objectstore.DuplicateError: // Return "409 Conflict" if there // is a duplicate of the object m := &errorJSON{Error: err.Error()} return c.JSON(http.StatusConflict, m) default: m := &errorJSON{Error: "Internal error"} return c.JSON(http.StatusInternalServerError, m) } } // Return success m := &objectIdJSON{Id: objectId} return c.JSON(http.StatusCreated, m) } func (s *storehouse) getObject(c echo.Context) error { bucketId := c.Param("bucketId") objectId := c.Param("objectId") object, err := s.objects.GetObject(bucketId, objectId) if errors.Is(err, os.ErrNotExist) { msg := "Object not found" log.Println(msg) m := &errorJSON{Error: msg} return c.JSON(http.StatusNotFound, m) } else if err != nil { log.Println(err) m := &errorJSON{Error: "Internal error"} return c.JSON(http.StatusInternalServerError, m) } return c.String(http.StatusOK, object) } func (s *storehouse) deleteObject(c echo.Context) error { bucketId := c.Param("bucketId") objectId := c.Param("objectId") err := s.objects.DeleteObject(bucketId, objectId) if errors.Is(err, os.ErrNotExist) { msg := "Object not found" log.Println(msg) m := &errorJSON{Error: msg} return c.JSON(http.StatusNotFound, m) } else if err != nil { log.Println(err) m := &errorJSON{Error: "Internal error"} return c.JSON(http.StatusInternalServerError, m) } m := &infoJSON{Info: "Object deleted"} return c.JSON(http.StatusOK, m) } func main() { // Command-line options memHelp := "Memory backend with de-duplication (default: file backend w/o de-dup)" memory := flag.Bool("memory", false, memHelp) dataDirHelp := "The directory where the objects are stored with the file backend" dataDir := flag.String("datadir", "data", dataDirHelp) portHelp := "The port on which the server will listen for connections" port := flag.Int("port", 8080, portHelp) flag.Parse() // Initialization s := InitStorehouse(*memory, *dataDir) e := echo.New() // Routes e.GET("/objects/:bucketId/:objectId", s.getObject) e.PUT("/objects/:bucketId/:objectId", s.putObject) e.DELETE("/objects/:bucketId/:objectId", s.deleteObject) // Start server listenConf := fmt.Sprintf(":%v", *port) e.Start(listenConf) }