services/cdn/api/rest/handler.go
package rest
import (
"encoding/hex"
"hash/fnv"
"net/http"
"strconv"
"github.com/go-chi/chi"
"github.com/golang/glog"
"github.com/pkg/errors"
"go.kicksware.com/api/shared/api/rest"
"go.kicksware.com/api/shared/core"
"github.com/victorspringer/http-cache"
"go.kicksware.com/api/services/cdn/core/meta"
"go.kicksware.com/api/services/cdn/core/model"
"go.kicksware.com/api/services/cdn/core/service"
)
type Handler struct {
service service.ContentService
auth *rest.AuthMiddleware
cache *cache.Client
}
func NewHandler(service service.ContentService, auth core.AuthService) *Handler {
return &Handler{
service,
rest.NewAuthMiddleware(auth),
newCacheClient(),
}
}
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
query := meta.ContentQuery{
Filename: chi.URLParam(r, "filename"),
Collection: chi.URLParam(r, "collection"),
}
if len(query.Collection) == 0 || len(query.Filename) == 0 {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
img, err := h.service.Original(query); if err != nil {
http.Error(w, errors.Wrap(err, http.StatusText(http.StatusInternalServerError)).Error(),
http.StatusInternalServerError)
glog.Errorln(err)
return
}
h.setupResponse(w, img, http.StatusOK)
}
func (h *Handler) GetCropped(w http.ResponseWriter, r *http.Request) {
query := meta.ContentQuery{
Filename: chi.URLParam(r, "filename"),
Collection: chi.URLParam(r, "collection"),
}
if len(query.Collection) == 0 || len(query.Filename) == 0 {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
options := ParseOptions(r); if options.Height == 0 && options.Width == 0 {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
img, err := h.service.Crop(query, options); if err != nil {
http.Error(w, errors.Wrap(err, http.StatusText(http.StatusInternalServerError)).Error(),
http.StatusInternalServerError)
glog.Errorln(err)
return
}
h.setupResponse(w, img, http.StatusOK)
}
func (h *Handler) GetResized(w http.ResponseWriter, r *http.Request) {
query := meta.ContentQuery{
Filename: chi.URLParam(r, "filename"),
Collection: chi.URLParam(r, "collection"),
}
if len(query.Collection) == 0 || len(query.Filename) == 0 {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
options := ParseOptions(r); if options.Height == 0 && options.Width == 0 {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
img, err := h.service.Resize(query, options); if err != nil {
http.Error(w, errors.Wrap(err, http.StatusText(http.StatusInternalServerError)).Error(),
http.StatusInternalServerError)
glog.Errorln(err)
return
}
h.setupResponse(w, img, http.StatusOK)
}
func (h *Handler) GetThumbnail(w http.ResponseWriter, r *http.Request) {
query := meta.ContentQuery{
Filename: chi.URLParam(r, "filename"),
Collection: chi.URLParam(r, "collection"),
}
if len(query.Collection) == 0 || len(query.Filename) == 0 {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
img, err := h.service.Thumbnail(query); if err != nil {
http.Error(w, errors.Wrap(err, http.StatusText(http.StatusInternalServerError)).Error(),
http.StatusInternalServerError)
glog.Errorln(err)
return
}
h.setupResponse(w, img, http.StatusOK)
}
func (h *Handler) Post(w http.ResponseWriter, r *http.Request) {
panic("not implemented")
}
func (h *Handler) setupResponse(w http.ResponseWriter, content *model.Content, statusCode int) {
if content != nil {
w.Header().Set("Accept-Ranges", "bytes")
w.Header().Set("ETag", generateETagFor(*content))
w.Header().Set("Cache-Control", "public, max-age=31536000")
w.Header().Set("Content-Length", strconv.Itoa(len(content.Data)))
w.Header().Set("Content-Type", string(content.MimeType))
w.WriteHeader(statusCode)
w.Write(content.Data)
}
}
func generateETagFor(content model.Content) string {
h := fnv.New32a()
h.Write(content.Data)
return hex.EncodeToString(h.Sum(nil))
}