func TestValidateFormat(t *testing.T) { o, err := option.New(os.Args[1:]) if err != nil { t.Fatalf("fail to create options: error=%v", err) } if f, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"0"}, }, o.Hosts); err != nil { t.Fatalf("fail to validate: error=%v", err) } else if f.ValidatedFormat != "jpeg" { t.Errorf("default format should be jpeg") } if f, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"0"}, "format": []string{"jpeg"}, }, o.Hosts); err != nil { t.Errorf("jpeg format is allowed") } else if f.ValidatedFormat != "jpeg" { t.Errorf("format should be jpeg") } if f, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"0"}, "format": []string{"png"}, }, o.Hosts); err != nil { t.Errorf("png format is allowed") } else if f.ValidatedFormat != "png" { t.Errorf("format should be png") } if f, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"0"}, "format": []string{"gif"}, }, o.Hosts); err != nil { t.Errorf("gif format is allowed") } else if f.ValidatedFormat != "gif" { t.Errorf("format should be gif") } if _, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"0"}, "format": []string{"foo"}, }, o.Hosts); err == nil { t.Errorf("format is allowed only jpeg or png or gif") } }
func TestSerialization(t *testing.T) { o, err := option.New(os.Args[1:]) if err != nil { t.Fatalf("fail to create options: error=%v", err) } f1, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"300"}, "method": []string{"thumbnail"}, "format": []string{"png"}, "quality": []string{"30"}, }, o.Hosts) if err != nil { t.Fatalf("should be valid image: error=%v", err) } f2, err := storage.NewImage(map[string][]string{ "quality": []string{"80"}, "format": []string{"png"}, "method": []string{"thumbnail"}, "height": []string{"300"}, "width": []string{"400"}, "url": []string{"http://0.0.0.0"}, "filename": []string{"dummy"}, }, o.Hosts) if err != nil { t.Fatalf("should be valid image: error=%v", err) } f3, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"200"}, "height": []string{"500"}, "method": []string{"thumbnail"}, "format": []string{"png"}, "quality": []string{"80"}, }, o.Hosts) if err != nil { t.Fatalf("should be valid image: error=%v", err) } if f1.ValidatedHash != f2.ValidatedHash { t.Errorf("RequestedHash with equal request should be equal: %+v %+v", f1, f2) } if f1.ValidatedHash == f3.ValidatedHash { t.Errorf("RequestedHash with different file shouldn't be equal: %+v %+v", f1, f3) } if f2.ValidatedHash == f3.ValidatedHash { t.Errorf("RequestedHash with different file shouldn't be equal: %+v %+v", f2, f3) } }
func TestGuessSizeError(t *testing.T) { o, err := option.New(os.Args[1:]) if err != nil { t.Fatalf("fail to create options: error=%v", err) } func() { f, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"200"}, "height": []string{"100"}, }, o.Hosts) if err != nil { t.Fatalf("should be valid image: error=%v", err) } if _, err := f.Normalize(image.Point{0, 300}); err == nil { t.Errorf("with zero source width should return error") } }() func() { f, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"200"}, "height": []string{"100"}, }, o.Hosts) if err != nil { t.Fatalf("should be valid image: error=%v", err) } if _, err := f.Normalize(image.Point{400, 0}); err == nil { t.Errorf("with zero source height should return error") } }() func() { f, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"200"}, "height": []string{"100"}, }, o.Hosts) if err != nil { t.Fatalf("should be valid image: error=%v", err) } if _, err := f.Normalize(image.ZP); err == nil { t.Errorf("source size with zero point should return error") } }() }
func TestValidateMethod(t *testing.T) { o, err := option.New(os.Args[1:]) if err != nil { t.Fatalf("fail to create options: error=%v", err) } if f, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"0"}, }, o.Hosts); err != nil { t.Fatalf("fail to validate: error=%v", err) } else if f.ValidatedMethod != "normal" { t.Errorf("default method should be normal") } if f, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"0"}, "method": []string{"normal"}, }, o.Hosts); err != nil { t.Fatalf("fail to validate: error=%v", err) } else if f.ValidatedMethod != "normal" { t.Errorf("format should be normal") } if f, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"0"}, "method": []string{"thumbnail"}, }, o.Hosts); err != nil { t.Errorf("thumbnail format is allowed") } else if f.ValidatedMethod != "thumbnail" { t.Errorf("format should be thumbnail") } if _, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"0"}, "method": []string{"foo"}, }, o.Hosts); err == nil { t.Errorf("format other than normal or thumbnail isn't allowed") } }
func TestNewImage(t *testing.T) { o, err := option.New(os.Args[1:]) if err != nil { t.Fatalf("fail to create options: error=%v", err) } if _, err := storage.NewImage(map[string][]string{}, o.Hosts); err == nil { t.Fatalf("fail to NewFile: error=%v", err) } q := map[string][]string{ "url": []string{"http://0.0.0.0"}, "method": []string{storage.MethodThumbnail}, "width": []string{"400"}, "height": []string{"300"}, "format": []string{storage.FormatPng}, "quality": []string{"80"}, } f, err := storage.NewImage(q, o.Hosts) if err != nil { t.Fatalf("fail to New: error=%v", err) } if f.ValidatedURL != "http://0.0.0.0" { t.Errorf("URL is expected '%s', but actual '%s'", "http://0.0.0.0", f.ValidatedURL) } if f.ValidatedMethod != storage.MethodThumbnail { t.Errorf("Method is expected '%s', but actual '%s'", storage.MethodThumbnail, f.ValidatedMethod) } if f.ValidatedWidth != 400 { t.Errorf("RequestedWidth is expected %d, but actual %d", 400, f.ValidatedWidth) } if f.ValidatedHeight != 300 { t.Errorf("RequestedHeight is expected %d, but actual %d", 300, f.ValidatedHeight) } if f.ValidatedFormat != storage.FormatPng { t.Errorf("Format is expected '%s', but actual '%s'", storage.FormatPng, f.ValidatedFormat) } if f.ValidatedQuality != 0 { t.Errorf("Quality is expected %d, but actual %d", 0, f.ValidatedQuality) } }
func BenchmarkProcess(b *testing.B) { if err := fetcher.Init(); err != nil { b.Fatal(err) } o, err := option.New(os.Args[1:]) if err != nil { b.Fatal(err) } for i := 0; i < b.N; i++ { o, err := storage.NewImage(map[string][]string{ "url": []string{""}, "width": []string{"800"}, "height": []string{"0"}, }, o.Hosts) if err != nil { b.Fatalf("%s", err) } path, err := fetcher.Fetch(o.ValidatedURL) if err != nil { b.Fatalf("%s", err.Error()) } var bs []byte w := bytes.NewBuffer(bs) p := processor.New() image, err := p.Preprocess(path) if err != nil { b.Fatalf("%s", err.Error()) } if _, err := p.Process(image, w, o); err != nil { b.Fatalf("%s", err.Error()) } } }
// operate は手続き的に一連のリサイズ処理を行う。 // エラーを画一的に扱うためにメソッドとして切り分けを行っている func (h *Handler) operate(resp http.ResponseWriter, req *http.Request) error { t := log.Start() defer log.End(t) // 1. URLクエリからリクエストされているオプションを抽出する i, err := storage.NewImage(req.URL.Query(), h.Hosts) if err != nil { return err } // 3. バリデート済みオプションでリサイズをしたキャッシュがあるか調べる // 4. キャッシュがあればリサイズ画像のURLにリダイレクトする cache := storage.Image{} h.Storage.Where(&storage.Image{ ValidatedHash: i.ValidatedHash, ValidatedWidth: i.ValidatedWidth, ValidatedHeight: i.ValidatedHeight, ValidatedMethod: i.ValidatedMethod, ValidatedFormat: i.ValidatedFormat, ValidatedQuality: i.ValidatedQuality, }).First(&cache) log.Debugln(cache.ID) if cache.ID != 0 { log.Infof("validated cache %+v exists, requested with %+v", cache, i) url := h.Uploader.CreateURL(cache.Filename) http.Redirect(resp, req, url, http.StatusSeeOther) return nil } log.Infof("validated cache doesn't exist, requested with %+v", i) // 5. 元画像を取得する // 6. リサイズの前処理をする filename, err := fetcher.Fetch(i.ValidatedURL) defer func() { if err := fetcher.Clean(filename); err != nil { log.Errorf("fail to clean fetched file: %s", filename) } }() if err != nil { return err } var b []byte buf := bytes.NewBuffer(b) p := processor.New() pixels, err := p.Preprocess(filename) if err != nil { return err } // 7. 正規化する // 8. 正規化済みのオプションでリサイズをしたことがあるか調べる // 9. あればリサイズ画像のURLにリダイレクトする i, err = i.Normalize(pixels.Bounds().Size()) if err != nil { return err } cache = storage.Image{} h.Storage.Where(&storage.Image{ NormalizedHash: i.NormalizedHash, DestWidth: i.DestWidth, DestHeight: i.DestHeight, ValidatedMethod: i.ValidatedMethod, ValidatedFormat: i.ValidatedFormat, ValidatedQuality: i.ValidatedQuality, }).First(&cache) if cache.ID != 0 { log.Infof("normalized cache %+v exists, requested with %+v", cache, i) url := h.Uploader.CreateURL(cache.Filename) http.Redirect(resp, req, url, http.StatusSeeOther) return nil } log.Infof("normalized cache doesn't exist, requested with %+v", i) // 10. リサイズする // 11. ファイルオブジェクトの処理結果フィールドを埋める // 12. レスポンスする size, err := p.Process(pixels, buf, i) if err != nil { return err } b = buf.Bytes() i.ETag = fmt.Sprintf("%x", md5.Sum(b)) i.Filename = i.CreateFilename() i.ContentType = contentTypes[i.ValidatedFormat] i.CanvasWidth = size.X i.CanvasHeight = size.Y resp.Header().Add("Content-Type", i.ContentType) io.Copy(resp, bufio.NewReader(buf)) // レスポンスを完了させるために非同期に処理する go h.save(b, i) return nil }
func TestNormalizeWithMethodThumbnail(t *testing.T) { o, err := option.New(os.Args[1:]) if err != nil { t.Fatalf("fail to create options: error=%v", err) } for i, points := range []map[string]image.Point{ map[string]image.Point{ "source": image.Point{400, 300}, "target": image.Point{200, 0}, "dest": image.Point{200, 150}, "canvas": image.Point{200, 150}, }, map[string]image.Point{ "source": image.Point{400, 300}, "target": image.Point{0, 150}, "dest": image.Point{200, 150}, "canvas": image.Point{200, 150}, }, map[string]image.Point{ "source": image.Point{400, 300}, "target": image.Point{200, 150}, "dest": image.Point{200, 150}, "canvas": image.Point{200, 150}, }, map[string]image.Point{ "source": image.Point{400, 300}, "target": image.Point{300, 150}, "dest": image.Point{300, 225}, "canvas": image.Point{300, 150}, }, map[string]image.Point{ "source": image.Point{400, 300}, "target": image.Point{500, 150}, "dest": image.Point{400, 300}, "canvas": image.Point{400, 150}, }, map[string]image.Point{ "source": image.Point{400, 300}, "target": image.Point{200, 200}, "dest": image.Point{266, 200}, "canvas": image.Point{200, 200}, }, map[string]image.Point{ "source": image.Point{400, 300}, "target": image.Point{200, 400}, "dest": image.Point{400, 300}, "canvas": image.Point{200, 300}, }, map[string]image.Point{ "source": image.Point{400, 300}, "target": image.Point{500, 450}, "dest": image.Point{400, 300}, "canvas": image.Point{400, 300}, }, map[string]image.Point{ "source": image.Point{400, 300}, "target": image.Point{600, 450}, "dest": image.Point{400, 300}, "canvas": image.Point{400, 300}, }, map[string]image.Point{ "source": image.Point{400, 300}, "target": image.Point{700, 450}, "dest": image.Point{400, 300}, "canvas": image.Point{400, 300}, }, } { source := points["source"] target := points["target"] dest := points["dest"] canvas := points["canvas"] f, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "method": []string{"thumbnail"}, "width": []string{strconv.Itoa(target.X)}, "height": []string{strconv.Itoa(target.Y)}, }, o.Hosts) if err != nil { t.Fatalf("should be valid image: error=%v", err) } if f, err := f.Normalize(source); err != nil { t.Errorf("Normalize with thumbnail method should not return error: %v", err) } else if f.DestWidth != dest.X { t.Errorf("DestWidth(%d) expected %d, but actual %d", i, dest.X, f.DestWidth) } else if f.DestHeight != dest.Y { t.Errorf("DestHeight(%d) expected %d, but actual %d", i, dest.Y, f.DestHeight) } else if f.CanvasWidth != canvas.X { t.Errorf("CanvasWidth(%d) expected %d, but actual %d", i, canvas.X, f.CanvasWidth) } else if f.CanvasHeight != canvas.Y { t.Errorf("CanvasHeight(%d) expected %d, but actual %d", i, canvas.Y, f.CanvasHeight) } } }
func TestValidateURL(t *testing.T) { o, err := option.New(os.Args[1:]) if err != nil { t.Fatalf("fail to create options: error=%v", err) } // ローカルホストと特定のホストとそのサブドメインを許可する for _, host := range []string{ "0.0.0.0", "255.255.255.255", "localhost", } { // http, https プロトコルを許可する for _, protocol := range []string{ "http", "https", } { url := fmt.Sprintf("%s://%s", protocol, host) if _, err := storage.NewImage(map[string][]string{ "url": []string{url}, "width": []string{"400"}, }, o.Hosts); err != nil { t.Errorf("should allow %s error=%v", url, err) } url = fmt.Sprintf("%s:%d", url, 99999) if _, err := storage.NewImage(map[string][]string{ "url": []string{url}, "width": []string{"400"}, }, o.Hosts); err != nil { t.Errorf("should allow %s error=%v", url, err) } } // その他のプロトコルを許可しない for _, protocol := range []string{ "ftp", "ssh", "git", } { url := fmt.Sprintf("%s://%s", protocol, host) if _, err := storage.NewImage(map[string][]string{ "url": []string{url}, "width": []string{"400"}, }, o.Hosts); err == nil { t.Errorf("shouldn't allow %s", url) } url = fmt.Sprintf("%s:%d", url, 99999) if _, err := storage.NewImage(map[string][]string{ "url": []string{}, "width": []string{"400"}, }, o.Hosts); err == nil { t.Errorf("shouldn't allow %s", url) } } } // その他のホストとそのサブドメインを許可しない for _, host := range []string{ "example.com", "foo.bar.baz.example.com", } { // どのようなプロトコルも許可しない for _, protocol := range []string{ "http", "https", "ftp", "ssh", "git", } { url := fmt.Sprintf("%s://%s", protocol, host) if _, err := storage.NewImage(map[string][]string{ "url": []string{url}, "width": []string{"400"}, }, o.Hosts); err == nil { t.Errorf("shouldn't allow %s", url) } url = fmt.Sprintf("%s:%d", url, 99999) if _, err := storage.NewImage(map[string][]string{ "url": []string{url}, "width": []string{"400"}, }, o.Hosts); err == nil { t.Errorf("shouldn't allow %s", url) } } } }
func TestValidateQuality(t *testing.T) { o, err := option.New(os.Args[1:]) if err != nil { t.Fatalf("fail to create options: error=%v", err) } if f, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"0"}, }, o.Hosts); err != nil { t.Errorf("fail to validate: error=%v", err) } else if f.ValidatedQuality != 100 { t.Errorf("default quality should be 100") } if _, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"0"}, "quality": []string{"-1"}, }, o.Hosts); err == nil { t.Errorf("negative quality shouldn't be allowed") } if _, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"0"}, "quality": []string{"101"}, }, o.Hosts); err == nil { t.Errorf("over 100 quality shouldn't be allowed") } if f, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"0"}, "quality": []string{"67"}, "format": []string{storage.FormatJpeg}, }, o.Hosts); err != nil { t.Errorf("fail to validate: error=%v", err) } else if f.ValidatedQuality != 67 { t.Errorf("Quality with png format should be 67, but actual %d", f.ValidatedQuality) } if f, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"0"}, "quality": []string{"67"}, "format": []string{storage.FormatPng}, }, o.Hosts); err != nil { t.Errorf("fail to validate: error=%v", err) } else if f.ValidatedQuality != 0 { t.Errorf("Quality with png format should be 0, but actual %d", f.ValidatedQuality) } if f, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"400"}, "height": []string{"0"}, "quality": []string{"67"}, "format": []string{storage.FormatGif}, }, o.Hosts); err != nil { t.Errorf("fail to validate: error=%v", err) } else if f.ValidatedQuality != 0 { t.Errorf("Quality with gif format should be 0, but actual %d", f.ValidatedQuality) } }
func TestValidateSize(t *testing.T) { o, err := option.New(os.Args[1:]) if err != nil { t.Fatalf("fail to create options: error=%v", err) } if _, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"-100"}, "height": []string{"100"}, }, o.Hosts); err == nil { t.Errorf("negative width isn't allowed") } if _, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"100"}, "height": []string{"-100"}, }, o.Hosts); err == nil { t.Errorf("negative height isn't allowed") } if _, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"-100"}, "height": []string{"-100"}, }, o.Hosts); err == nil { t.Errorf("negative width and height isn't allowed") } func() { if _, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"0"}, "height": []string{"0"}, }, o.Hosts); err == nil { t.Errorf("zero size isn't allowed") } }() if _, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"0"}, "height": []string{"100"}, }, o.Hosts); err != nil { t.Errorf("zero width is allowed err=", err) } if _, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"100"}, "height": []string{"0"}, }, o.Hosts); err != nil { t.Errorf("zero height is allowed err=", err) } if _, err := storage.NewImage(map[string][]string{ "url": []string{"http://0.0.0.0"}, "width": []string{"100"}, "height": []string{"100"}, }, o.Hosts); err != nil { t.Errorf("non-zero size is allowed err=", err) } }