diff options
| author | horus_arch | 2015-04-19 22:09:52 +0200 |
|---|---|---|
| committer | horus_arch | 2015-04-19 22:09:52 +0200 |
| commit | 01e9a34952bd6ddd383680b0ca2312e476ad07a6 (patch) | |
| tree | 00902575e5c271cc5d35ea65aa8795b8caeb97bc /server | |
| download | mandible-01e9a34952bd6ddd383680b0ca2312e476ad07a6.tar.gz | |
Initial commit.
Diffstat (limited to 'server')
| -rw-r--r-- | server/error.go | 17 | ||||
| -rw-r--r-- | server/health.go | 14 | ||||
| -rw-r--r-- | server/server.go | 307 | ||||
| -rw-r--r-- | server/templateHandler.go | 1 | ||||
| -rw-r--r-- | server/view.go | 30 |
5 files changed, 369 insertions, 0 deletions
diff --git a/server/error.go b/server/error.go new file mode 100644 index 0000000..f8325f9 --- /dev/null +++ b/server/error.go @@ -0,0 +1,17 @@ +package server + +import ( + "net/http" +) + +func errorHandler(w http.ResponseWriter, r *http.Request, status int) { + w.WriteHeader(status) + if status == http.StatusNotFound { + templ := mainTempl.Lookup("404.html") + err := templ.ExecuteTemplate(w, "404.html", r.URL.Path) + if err != nil { + ErrorResponse(w, err.Error(), http.StatusInternalServerError) + return + } + } +} diff --git a/server/health.go b/server/health.go new file mode 100644 index 0000000..f59d027 --- /dev/null +++ b/server/health.go @@ -0,0 +1,14 @@ +package server + +import ( + "fmt" + "net/http" +) + +func healthHandler(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/health" { + errorHandler(w, r, http.StatusNotFound) + return + } + fmt.Fprint(w, "TODO") +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..4cfafe7 --- /dev/null +++ b/server/server.go @@ -0,0 +1,307 @@ +package server + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "html/template" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "strings" + + "mandible/config" + "mandible/imageprocessor" + "mandible/imagestore" + "mandible/uploadedfile" +) + +var mainTempl = template.Must(template.New("global").ParseGlob("templates/*.html")) + +type Server struct { + Config *config.Configuration + HTTPClient *http.Client + imageStore imagestore.ImageStore + hashGenerator *imagestore.HashGenerator +} + +func CreateServer(c *config.Configuration) *Server { + factory := imagestore.NewFactory(c) + httpclient := &http.Client{} + stores := factory.NewImageStores() + store := stores[0] + + hashGenerator := factory.NewHashGenerator(store) + return &Server{c, httpclient, store, hashGenerator} +} + +func (s *Server) uploadFile(uploadFile io.Reader, w http.ResponseWriter, fileName string, thumbs []*uploadedfile.ThumbFile) { + w.Header().Set("Content-Type", "application/json") + + tmpFile, err := ioutil.TempFile(os.TempDir(), "image") + if err != nil { + fmt.Println(err) + ErrorResponse(w, "Unable to write to /tmp", http.StatusInternalServerError) + return + } + + defer tmpFile.Close() + + _, err = io.Copy(tmpFile, uploadFile) + + if err != nil { + fmt.Println(err) + ErrorResponse(w, "Unable to copy image to disk!", http.StatusInternalServerError) + return + } + + upload, err := uploadedfile.NewUploadedFile(fileName, tmpFile.Name(), thumbs) + defer upload.Clean() + + if err != nil { + ErrorResponse(w, "Error detecting mime type!", http.StatusInternalServerError) + return + } + + processor, err := imageprocessor.Factory(s.Config.MaxFileSize, upload) + if err != nil { + ErrorResponse(w, "Unable to process image!", http.StatusInternalServerError) + return + } + + err = processor.Run(upload) + if err != nil { + ErrorResponse(w, "Unable to process image!", http.StatusInternalServerError) + return + } + + upload.SetHash(s.hashGenerator.Get()) + + factory := imagestore.NewFactory(s.Config) + obj := factory.NewStoreObject(upload.GetHash(), upload.GetMime(), "original") + obj, err = s.imageStore.Save(upload.GetPath(), obj) + if err != nil { + ErrorResponse(w, "Unable to save image!", http.StatusInternalServerError) + return + } + + thumbsResp := map[string]interface{}{} + for _, t := range upload.GetThumbs() { + thumbName := fmt.Sprintf("%s/%s", upload.GetHash(), t.GetName()) + tObj := factory.NewStoreObject(thumbName, upload.GetMime(), "t") + tObj, err = s.imageStore.Save(t.GetPath(), tObj) + if err != nil { + ErrorResponse(w, "Unable to save thumbnail!", http.StatusInternalServerError) + return + } + + thumbsResp[t.GetName()] = tObj.Url + } + + size, err := upload.FileSize() + if err != nil { + ErrorResponse(w, "Unable to fetch image metadata!", http.StatusInternalServerError) + return + } + + width, height, err := upload.Dimensions() + + if err != nil { + ErrorResponse(w, err.Error(), http.StatusInternalServerError) + return + } + + resp := map[string]interface{}{ + "link": obj.Url, + "mime": obj.MimeType, + "name": fileName, + "size": size, + "width": width, + "height": height, + "thumbs": thumbsResp, + } + + Response(w, resp) +} + +func (s *Server) Start() { + fileHandler := func(w http.ResponseWriter, r *http.Request) { + uploadFile, header, err := r.FormFile("image") + + if err != nil { + fmt.Println(err) + ErrorResponse(w, "Error processing file!", http.StatusInternalServerError) + return + } + + thumbs, err := parseThumbs(r) + if err != nil { + ErrorResponse(w, err.Error(), http.StatusBadRequest) + return + } + + s.uploadFile(uploadFile, w, header.Filename, thumbs) + uploadFile.Close() + } + + urlHandler := func(w http.ResponseWriter, r *http.Request) { + uploadFile, err := s.download(r.FormValue("image")) + + if err != nil { + ErrorResponse(w, "Error dowloading URL!", http.StatusInternalServerError) + return + } + + thumbs, err := parseThumbs(r) + if err != nil { + ErrorResponse(w, err.Error(), http.StatusBadRequest) + return + } + + s.uploadFile(uploadFile, w, "", thumbs) + uploadFile.Close() + } + + base64Handler := func(w http.ResponseWriter, r *http.Request) { + b64data := r.FormValue("image") + + uploadFile := base64.NewDecoder(base64.StdEncoding, strings.NewReader(b64data)) + + thumbs, err := parseThumbs(r) + if err != nil { + ErrorResponse(w, err.Error(), http.StatusBadRequest) + return + } + + s.uploadFile(uploadFile, w, "", thumbs) + } + + rootHandler := func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + errorHandler(w, r, http.StatusNotFound) + return + } + templ := mainTempl.Lookup("index.html") + err := templ.ExecuteTemplate(w, "index.html", nil) + if err != nil { + ErrorResponse(w, err.Error(), http.StatusInternalServerError) + return + } + } + + apiHandler := func(w http.ResponseWriter, r *http.Request) { + templ := mainTempl.Lookup("api.html") + err := templ.ExecuteTemplate(w, "api.html", nil) + if err != nil { + ErrorResponse(w, err.Error(), http.StatusInternalServerError) + return + } + } + + cliHandler := func(w http.ResponseWriter, r *http.Request) { + templ := mainTempl.Lookup("cli.html") + err := templ.ExecuteTemplate(w, "cli.html", nil) + if err != nil { + ErrorResponse(w, err.Error(), http.StatusInternalServerError) + return + } + } + + http.HandleFunc("/cli", cliHandler) + http.HandleFunc("/api", apiHandler) + + http.HandleFunc("/api/v1/file", fileHandler) + http.HandleFunc("/api/v1/url", urlHandler) + http.HandleFunc("/api/v1/base64", base64Handler) + + staticDir := os.Getenv("STATIC_DIR") + if staticDir == "" { + log.Panic("No directory given for static assets.") + } + http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))) + http.Handle("/i/", http.StripPrefix("/i/", http.FileServer(http.Dir(staticDir+"/../files/i/")))) + http.HandleFunc("/", rootHandler) + http.HandleFunc("/health", healthHandler) + + port := ":" + os.Getenv("PORT") + if port == ":" { + port = fmt.Sprintf(":%d", s.Config.Port) + } + + http.ListenAndServe(port, nil) +} + +func (s *Server) download(url string) (io.ReadCloser, error) { + req, err := http.NewRequest("GET", url, nil) + + if err != nil { + return nil, err + } + + req.Header.Add("User-Agent", s.Config.UserAgent) + + resp, err := s.HTTPClient.Do(req) + + if err != nil { + // "HTTP protocol error" - maybe the server sent an invalid response or timed out + return nil, err + } + + if 200 != resp.StatusCode { + return nil, errors.New("Non-200 status code received") + } + + contentLength := resp.ContentLength + + if contentLength == 0 { + return nil, errors.New("Empty file received") + } + + return resp.Body, nil +} + +func parseThumbs(r *http.Request) ([]*uploadedfile.ThumbFile, error) { + thumbString := r.FormValue("thumbs") + if thumbString == "" { + return []*uploadedfile.ThumbFile{}, nil + } + + var t map[string]map[string]interface{} + err := json.Unmarshal([]byte(thumbString), &t) + if err != nil { + return nil, errors.New("Error parsing thumbnail JSON!") + } + + var thumbs []*uploadedfile.ThumbFile + for name, thumb := range t { + width, wOk := thumb["width"].(float64) + if !wOk { + return nil, errors.New("Invalid thumbnail width!") + } + + height, hOk := thumb["height"].(float64) + if !hOk { + return nil, errors.New("Invalid thumbnail height!") + } + + shape, sOk := thumb["shape"].(string) + if !sOk { + return nil, errors.New("Invalid thumbnail shape!") + } + + switch shape { + case "thumb": + case "square": + case "circle": + default: + return nil, errors.New("Invalid thumbnail shape!") + } + + thumbs = append(thumbs, uploadedfile.NewThumbFile(int(width), int(height), name, shape, "")) + } + + return thumbs, nil +} diff --git a/server/templateHandler.go b/server/templateHandler.go new file mode 100644 index 0000000..abb4e43 --- /dev/null +++ b/server/templateHandler.go @@ -0,0 +1 @@ +package server diff --git a/server/view.go b/server/view.go new file mode 100644 index 0000000..63d5314 --- /dev/null +++ b/server/view.go @@ -0,0 +1,30 @@ +package server + +import ( + "encoding/json" + "net/http" +) + +func Response(w http.ResponseWriter, data map[string]interface{}, status ...int) { + var responseStatus int + if len(status) > 0 { + responseStatus = status[0] + } else { + responseStatus = http.StatusOK + } + + w.WriteHeader(responseStatus) + + resp, _ := json.Marshal( + map[string]interface{}{ + "success": responseStatus == http.StatusOK, + "status": responseStatus, + "data": data, + }) + + w.Write(resp) +} + +func ErrorResponse(w http.ResponseWriter, message string, status int) { + Response(w, map[string]interface{}{"error": message}, status) +} |
