Przeglądaj źródła

Extract processing handler and imageType functions from server.go

DarthSim 6 lat temu
rodzic
commit
a9244a7063
4 zmienionych plików z 263 dodań i 230 usunięć
  1. 96 0
      image_type.go
  2. 149 0
      processing_handler.go
  3. 0 39
      processing_options.go
  4. 18 191
      server.go

+ 96 - 0
image_type.go

@@ -0,0 +1,96 @@
+package main
+
+/*
+#cgo LDFLAGS: -s -w
+#include "vips.h"
+*/
+import "C"
+
+import (
+	"path/filepath"
+	"fmt"
+	"net/url"
+	"strings"
+)
+
+type imageType int
+
+const (
+	imageTypeUnknown = imageType(C.UNKNOWN)
+	imageTypeJPEG    = imageType(C.JPEG)
+	imageTypePNG     = imageType(C.PNG)
+	imageTypeWEBP    = imageType(C.WEBP)
+	imageTypeGIF     = imageType(C.GIF)
+	imageTypeICO     = imageType(C.ICO)
+	imageTypeSVG     = imageType(C.SVG)
+	imageTypeHEIC    = imageType(C.HEIC)
+
+	contentDispositionFilenameFallback = "image"
+)
+
+var (
+	imageTypes = map[string]imageType{
+		"jpeg": imageTypeJPEG,
+		"jpg":  imageTypeJPEG,
+		"png":  imageTypePNG,
+		"webp": imageTypeWEBP,
+		"gif":  imageTypeGIF,
+		"ico":  imageTypeICO,
+		"svg":  imageTypeSVG,
+		"heic": imageTypeHEIC,
+	}
+
+	mimes = map[imageType]string{
+		imageTypeJPEG: "image/jpeg",
+		imageTypePNG:  "image/png",
+		imageTypeWEBP: "image/webp",
+		imageTypeGIF:  "image/gif",
+		imageTypeICO:  "image/x-icon",
+		imageTypeHEIC: "image/heif",
+	}
+
+	contentDispositionsFmt = map[imageType]string{
+		imageTypeJPEG: "inline; filename=\"%s.jpg\"",
+		imageTypePNG:  "inline; filename=\"%s.png\"",
+		imageTypeWEBP: "inline; filename=\"%s.webp\"",
+		imageTypeGIF:  "inline; filename=\"%s.gif\"",
+		imageTypeICO:  "inline; filename=\"%s.ico\"",
+		imageTypeHEIC: "inline; filename=\"%s.heic\"",
+	}
+)
+
+func (it imageType) String() string {
+	for k, v := range imageTypes {
+		if v == it {
+			return k
+		}
+	}
+	return ""
+}
+
+func (it imageType) Mime() string {
+	if mime, ok := mimes[it]; ok {
+		return mime
+	} else {
+		return "application/octet-stream"
+	}
+}
+
+func (it imageType) ContentDisposition(imageURL string) string {
+	format, ok := contentDispositionsFmt[it]
+	if !ok {
+		return "inline"
+	}
+
+	url, err := url.Parse(imageURL)
+	if err != nil {
+		return fmt.Sprintf(format, contentDispositionFilenameFallback)
+	}
+
+	_, filename := filepath.Split(url.Path)
+	if len(filename) == 0 {
+		return fmt.Sprintf(format, contentDispositionFilenameFallback)
+	}
+
+	return fmt.Sprintf(format, strings.TrimSuffix(filename, filepath.Ext(filename)))
+}

+ 149 - 0
processing_handler.go

@@ -0,0 +1,149 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var (
+	responseGzipBufPool *bufPool
+	responseGzipPool    *gzipPool
+
+	processingSem chan struct{}
+
+	headerVaryValue string
+)
+
+func initProcessingHandler() {
+	processingSem = make(chan struct{}, conf.Concurrency)
+
+	if conf.GZipCompression > 0 {
+		responseGzipBufPool = newBufPool("gzip", conf.Concurrency, conf.GZipBufferSize)
+		responseGzipPool = newGzipPool(conf.Concurrency)
+	}
+
+	vary := make([]string, 0)
+
+	if conf.EnableWebpDetection || conf.EnforceWebp {
+		vary = append(vary, "Accept")
+	}
+
+	if conf.GZipCompression > 0 {
+		vary = append(vary, "Accept-Encoding")
+	}
+
+	if conf.EnableClientHints {
+		vary = append(vary, "DPR", "Viewport-Width", "Width")
+	}
+
+	headerVaryValue = strings.Join(vary, ", ")
+}
+
+func respondWithImage(ctx context.Context, reqID string, r *http.Request, rw http.ResponseWriter, data []byte) {
+	po := getProcessingOptions(ctx)
+
+	rw.Header().Set("Expires", time.Now().Add(time.Second*time.Duration(conf.TTL)).Format(http.TimeFormat))
+	rw.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, public", conf.TTL))
+	rw.Header().Set("Content-Type", po.Format.Mime())
+	rw.Header().Set("Content-Disposition", po.Format.ContentDisposition(getImageURL(ctx)))
+
+	if len(headerVaryValue) > 0 {
+		rw.Header().Set("Vary", headerVaryValue)
+	}
+
+	if conf.GZipCompression > 0 && strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
+		buf := responseGzipBufPool.Get(0)
+		defer responseGzipBufPool.Put(buf)
+
+		gz := responseGzipPool.Get(buf)
+		defer responseGzipPool.Put(gz)
+
+		gz.Write(data)
+		gz.Close()
+
+		rw.Header().Set("Content-Encoding", "gzip")
+		rw.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
+
+		rw.WriteHeader(200)
+		rw.Write(buf.Bytes())
+	} else {
+		rw.Header().Set("Content-Length", strconv.Itoa(len(data)))
+		rw.WriteHeader(200)
+		rw.Write(data)
+	}
+
+	logResponse(reqID, 200, fmt.Sprintf("Processed in %s: %s; %+v", getTimerSince(ctx), getImageURL(ctx), po))
+}
+
+func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
+	ctx := context.Background()
+
+	if newRelicEnabled {
+		var newRelicCancel context.CancelFunc
+		ctx, newRelicCancel = startNewRelicTransaction(ctx, rw, r)
+		defer newRelicCancel()
+	}
+
+	if prometheusEnabled {
+		prometheusRequestsTotal.Inc()
+		defer startPrometheusDuration(prometheusRequestDuration)()
+	}
+
+	processingSem <- struct{}{}
+	defer func() { <-processingSem }()
+
+	ctx, timeoutCancel := startTimer(ctx, time.Duration(conf.WriteTimeout)*time.Second)
+	defer timeoutCancel()
+
+	ctx, err := parsePath(ctx, r)
+	if err != nil {
+		panic(err)
+	}
+
+	ctx, downloadcancel, err := downloadImage(ctx)
+	defer downloadcancel()
+	if err != nil {
+		if newRelicEnabled {
+			sendErrorToNewRelic(ctx, err)
+		}
+		if prometheusEnabled {
+			incrementPrometheusErrorsTotal("download")
+		}
+		panic(err)
+	}
+
+	checkTimeout(ctx)
+
+	if conf.ETagEnabled {
+		eTag := calcETag(ctx)
+		rw.Header().Set("ETag", eTag)
+
+		if eTag == r.Header.Get("If-None-Match") {
+			logResponse(reqID, 304, "Not modified")
+			rw.WriteHeader(304)
+			return
+		}
+	}
+
+	checkTimeout(ctx)
+
+	imageData, processcancel, err := processImage(ctx)
+	defer processcancel()
+	if err != nil {
+		if newRelicEnabled {
+			sendErrorToNewRelic(ctx, err)
+		}
+		if prometheusEnabled {
+			incrementPrometheusErrorsTotal("processing")
+		}
+		panic(err)
+	}
+
+	checkTimeout(ctx)
+
+	respondWithImage(ctx, reqID, r, rw, imageData)
+}

+ 0 - 39
processing_options.go

@@ -1,11 +1,5 @@
 package main
 
-/*
-#cgo LDFLAGS: -s -w
-#include "vips.h"
-*/
-import "C"
-
 import (
 	"context"
 	"encoding/base64"
@@ -20,19 +14,6 @@ import (
 
 type urlOptions map[string][]string
 
-type imageType int
-
-const (
-	imageTypeUnknown = imageType(C.UNKNOWN)
-	imageTypeJPEG    = imageType(C.JPEG)
-	imageTypePNG     = imageType(C.PNG)
-	imageTypeWEBP    = imageType(C.WEBP)
-	imageTypeGIF     = imageType(C.GIF)
-	imageTypeICO     = imageType(C.ICO)
-	imageTypeSVG     = imageType(C.SVG)
-	imageTypeHEIC    = imageType(C.HEIC)
-)
-
 type processingHeaders struct {
 	Accept        string
 	Width         string
@@ -40,17 +21,6 @@ type processingHeaders struct {
 	DPR           string
 }
 
-var imageTypes = map[string]imageType{
-	"jpeg": imageTypeJPEG,
-	"jpg":  imageTypeJPEG,
-	"png":  imageTypePNG,
-	"webp": imageTypeWEBP,
-	"gif":  imageTypeGIF,
-	"ico":  imageTypeICO,
-	"svg":  imageTypeSVG,
-	"heic": imageTypeHEIC,
-}
-
 type gravityType int
 
 const (
@@ -158,15 +128,6 @@ var (
 	errInvalidPath                        = newError(404, "Invalid path", msgInvalidURL)
 )
 
-func (it imageType) String() string {
-	for k, v := range imageTypes {
-		if v == it {
-			return k
-		}
-	}
-	return ""
-}
-
 func (gt gravityType) String() string {
 	for k, v := range gravityTypes {
 		if v == gt {

+ 18 - 191
server.go

@@ -6,47 +6,15 @@ import (
 	"fmt"
 	"net"
 	"net/http"
-	"net/url"
-	"path/filepath"
-	"strconv"
-	"strings"
 	"time"
 
 	"golang.org/x/net/netutil"
 )
 
-const (
-	contextDispositionFilenameFallback = "image"
-)
-
 var (
-	mimes = map[imageType]string{
-		imageTypeJPEG: "image/jpeg",
-		imageTypePNG:  "image/png",
-		imageTypeWEBP: "image/webp",
-		imageTypeGIF:  "image/gif",
-		imageTypeICO:  "image/x-icon",
-		imageTypeHEIC: "image/heif",
-	}
-
-	contentDispositionsFmt = map[imageType]string{
-		imageTypeJPEG: "inline; filename=\"%s.jpg\"",
-		imageTypePNG:  "inline; filename=\"%s.png\"",
-		imageTypeWEBP: "inline; filename=\"%s.webp\"",
-		imageTypeGIF:  "inline; filename=\"%s.gif\"",
-		imageTypeICO:  "inline; filename=\"%s.ico\"",
-		imageTypeHEIC: "inline; filename=\"%s.heic\"",
-	}
-
 	imgproxyIsRunningMsg = []byte("imgproxy is running")
 
-	errInvalidMethod = newError(422, "Invalid request method", "Method doesn't allowed")
 	errInvalidSecret = newError(403, "Invalid secret", "Forbidden")
-
-	responseGzipBufPool *bufPool
-	responseGzipPool    *gzipPool
-
-	processingSem chan struct{}
 )
 
 func buildRouter() *router {
@@ -62,8 +30,6 @@ func buildRouter() *router {
 }
 
 func startServer() *http.Server {
-	processingSem = make(chan struct{}, conf.Concurrency)
-
 	l, err := net.Listen("tcp", conf.Bind)
 	if err != nil {
 		logFatal(err.Error())
@@ -76,10 +42,7 @@ func startServer() *http.Server {
 		MaxHeaderBytes: 1 << 20,
 	}
 
-	if conf.GZipCompression > 0 {
-		responseGzipBufPool = newBufPool("gzip", conf.Concurrency, conf.GZipBufferSize)
-		responseGzipPool = newGzipPool(conf.Concurrency)
-	}
+	initProcessingHandler()
 
 	go func() {
 		logNotice("Starting server at %s", conf.Bind)
@@ -100,86 +63,6 @@ func shutdownServer(s *http.Server) {
 	s.Shutdown(ctx)
 }
 
-func contentDisposition(imageURL string, imgtype imageType) string {
-	url, err := url.Parse(imageURL)
-	if err != nil {
-		return fmt.Sprintf(contentDispositionsFmt[imgtype], contextDispositionFilenameFallback)
-	}
-
-	_, filename := filepath.Split(url.Path)
-	if len(filename) == 0 {
-		return fmt.Sprintf(contentDispositionsFmt[imgtype], contextDispositionFilenameFallback)
-	}
-
-	return fmt.Sprintf(contentDispositionsFmt[imgtype], strings.TrimSuffix(filename, filepath.Ext(filename)))
-}
-
-func respondWithImage(ctx context.Context, reqID string, r *http.Request, rw http.ResponseWriter, data []byte) {
-	po := getProcessingOptions(ctx)
-
-	rw.Header().Set("Expires", time.Now().Add(time.Second*time.Duration(conf.TTL)).Format(http.TimeFormat))
-	rw.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, public", conf.TTL))
-	rw.Header().Set("Content-Type", mimes[po.Format])
-	rw.Header().Set("Content-Disposition", contentDisposition(getImageURL(ctx), po.Format))
-
-	addVaryHeader(rw)
-
-	if conf.GZipCompression > 0 && strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
-		buf := responseGzipBufPool.Get(0)
-		defer responseGzipBufPool.Put(buf)
-
-		gz := responseGzipPool.Get(buf)
-		defer responseGzipPool.Put(gz)
-
-		gz.Write(data)
-		gz.Close()
-
-		rw.Header().Set("Content-Encoding", "gzip")
-		rw.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
-
-		rw.WriteHeader(200)
-		rw.Write(buf.Bytes())
-	} else {
-		rw.Header().Set("Content-Length", strconv.Itoa(len(data)))
-		rw.WriteHeader(200)
-		rw.Write(data)
-	}
-
-	logResponse(reqID, 200, fmt.Sprintf("Processed in %s: %s; %+v", getTimerSince(ctx), getImageURL(ctx), po))
-}
-
-func addVaryHeader(rw http.ResponseWriter) {
-	vary := make([]string, 0)
-
-	if conf.EnableWebpDetection || conf.EnforceWebp {
-		vary = append(vary, "Accept")
-	}
-
-	if conf.GZipCompression > 0 {
-		vary = append(vary, "Accept-Encoding")
-	}
-
-	if conf.EnableClientHints {
-		vary = append(vary, "DPR", "Viewport-Width", "Width")
-	}
-
-	if len(vary) > 0 {
-		rw.Header().Set("Vary", strings.Join(vary, ", "))
-	}
-}
-
-func respondWithError(reqID string, rw http.ResponseWriter, err *imgproxyError) {
-	logResponse(reqID, err.StatusCode, err.Message)
-
-	rw.WriteHeader(err.StatusCode)
-
-	if conf.DevelopmentErrorsMode {
-		rw.Write([]byte(err.Message))
-	} else {
-		rw.Write([]byte(err.PublicMessage))
-	}
-}
-
 func withCORS(h routeHandler) routeHandler {
 	return func(reqID string, rw http.ResponseWriter, r *http.Request) {
 		if len(conf.AllowOrigin) > 0 {
@@ -202,7 +85,7 @@ func withSecret(h routeHandler) routeHandler {
 		if subtle.ConstantTimeCompare([]byte(r.Header.Get("Authorization")), authHeader) == 1 {
 			h(reqID, rw, r)
 		} else {
-			respondWithError(reqID, rw, errInvalidSecret)
+			panic(errInvalidSecret)
 		}
 	}
 }
@@ -210,10 +93,23 @@ func withSecret(h routeHandler) routeHandler {
 func handlePanic(reqID string, rw http.ResponseWriter, r *http.Request, err error) {
 	reportError(err, r)
 
-	if ierr, ok := err.(*imgproxyError); ok {
-		respondWithError(reqID, rw, ierr)
+	var (
+		ierr *imgproxyError
+		ok   bool
+	)
+
+	if ierr, ok = err.(*imgproxyError); !ok {
+		ierr = newUnexpectedError(err.Error(), 3)
+	}
+
+	logResponse(reqID, ierr.StatusCode, ierr.Message)
+
+	rw.WriteHeader(ierr.StatusCode)
+
+	if conf.DevelopmentErrorsMode {
+		rw.Write([]byte(ierr.Message))
 	} else {
-		respondWithError(reqID, rw, newUnexpectedError(err.Error(), 3))
+		rw.Write([]byte(ierr.PublicMessage))
 	}
 }
 
@@ -227,72 +123,3 @@ func handleOptions(reqID string, rw http.ResponseWriter, r *http.Request) {
 	logResponse(reqID, 200, "Respond with options")
 	rw.WriteHeader(200)
 }
-
-func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
-	ctx := context.Background()
-
-	if newRelicEnabled {
-		var newRelicCancel context.CancelFunc
-		ctx, newRelicCancel = startNewRelicTransaction(ctx, rw, r)
-		defer newRelicCancel()
-	}
-
-	if prometheusEnabled {
-		prometheusRequestsTotal.Inc()
-		defer startPrometheusDuration(prometheusRequestDuration)()
-	}
-
-	processingSem <- struct{}{}
-	defer func() { <-processingSem }()
-
-	ctx, timeoutCancel := startTimer(ctx, time.Duration(conf.WriteTimeout)*time.Second)
-	defer timeoutCancel()
-
-	ctx, err := parsePath(ctx, r)
-	if err != nil {
-		panic(err)
-	}
-
-	ctx, downloadcancel, err := downloadImage(ctx)
-	defer downloadcancel()
-	if err != nil {
-		if newRelicEnabled {
-			sendErrorToNewRelic(ctx, err)
-		}
-		if prometheusEnabled {
-			incrementPrometheusErrorsTotal("download")
-		}
-		panic(err)
-	}
-
-	checkTimeout(ctx)
-
-	if conf.ETagEnabled {
-		eTag := calcETag(ctx)
-		rw.Header().Set("ETag", eTag)
-
-		if eTag == r.Header.Get("If-None-Match") {
-			logResponse(reqID, 304, "Not modified")
-			rw.WriteHeader(304)
-			return
-		}
-	}
-
-	checkTimeout(ctx)
-
-	imageData, processcancel, err := processImage(ctx)
-	defer processcancel()
-	if err != nil {
-		if newRelicEnabled {
-			sendErrorToNewRelic(ctx, err)
-		}
-		if prometheusEnabled {
-			incrementPrometheusErrorsTotal("processing")
-		}
-		panic(err)
-	}
-
-	checkTimeout(ctx)
-
-	respondWithImage(ctx, reqID, r, rw, imageData)
-}