Beispiel #1
0
// ServeHTTP はリクエストに応じて処理を行いレスポンスする。
func (h *Handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
	t := log.Start()
	defer log.End(t)

	if req.URL.Path != "/" {
		resp.WriteHeader(http.StatusNotFound)
		fmt.Fprint(resp, "Not Found")
		log.Infof("not found")
		return
	}

	if err := h.operate(resp, req); err != nil {
		log.Infof("fail to operate: error=%v", err)
		resp.WriteHeader(http.StatusBadRequest)
		enc := json.NewEncoder(resp)
		obj := map[string]string{
			"message": err.Error(),
		}
		if err := enc.Encode(obj); err != nil {
			log.Errorf("fail to encode error message to JSON: error=%v", err)
		}
		return
	}

	log.Infof("ok")
}
Beispiel #2
0
func orient(r io.Reader, i image.Image) (image.Image, error) {
	t := log.Start()
	defer log.End(t)

	e, err := exif.Decode(r)
	if err != nil {
		log.Printf("fail to decode exif: %v", err)
		return nil, err
	}
	tag, err := e.Get(exif.Orientation)
	// Orientationタグが存在しない場合、処理を完了する
	if err != nil {
		log.Println("oritentation tag doesn't exist")
		return nil, err
	}
	o, err := tag.Int(0)
	if err != nil {
		log.Println("oritentation tag is't int")
		return nil, err
	}

	rect := i.Bounds()
	// orientation=5~8 なら画像サイズの縦横を入れ替える
	if o >= 5 && o <= 8 {
		rect = RotateRect(rect)
	}
	d := image.NewRGBA64(rect)
	a := affines[o]
	a.TransformCenter(d, i, interp.Bilinear)

	return d, nil
}
Beispiel #3
0
func Start() error {
	t := log.Start()
	defer log.End(t)

	o, err := option.New(os.Args[1:])
	if err != nil {
		return err
	}
	handler, err := NewHandler(o)
	if err != nil {
		return err
	}
	s := http.Server{
		Handler:        &handler,
		ReadTimeout:    10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	l, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	if err := s.Serve(netutil.LimitListener(l, o.MaxConn)); err != nil {
		log.Fatalf("fail: err=%v", err)
	}
	log.Println("listening: addr=%s", addr)
	return nil
}
Beispiel #4
0
func Fetch(url string) (string, error) {
	t := log.Start()
	defer log.End(t)

	sum := md5.Sum([]byte(fmt.Sprintf("%s-%d", url, time.Now().UnixNano())))
	f := fmt.Sprintf("%x", sum)
	filename := path.Join(tempDir, f)
	log.Debugf("file is temporary saved as %s", filename)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Errorf("fail to new request: error=%v", err)
		return "", err
	}
	req.Header.Set("User-Agent", UserAgent)
	resp, err := client.Do(req)
	if err != nil {
		log.Errorf("fail to GET %s: error=%v", url, err)
		return "", err
	}

	dump, _ := httputil.DumpRequest(req, true)
	log.Debugf("%s", dump)

	if resp.StatusCode != http.StatusOK {
		log.Printf("not ok: StatusCode=%d", resp.StatusCode)
		return "", fmt.Errorf("can't fetch image %s", url)
	}
	defer func() {
		if err := resp.Body.Close(); err != nil {
			log.Error(err)
		}
	}()
	log.Printf("ok: StatusCode=%d", resp.StatusCode)

	file, err := os.Create(filename)
	defer func() {
		if err := file.Close(); err != nil {
			log.Error(err)
		}
	}()
	if err != nil {
		return "", err
	}
	if _, err := io.Copy(file, resp.Body); err != nil {
		return "", err
	}

	return filename, nil
}
Beispiel #5
0
// save はファイルやデータを保存します。
func (h *Handler) save(b []byte, f storage.Image) {
	t := log.Start()
	defer log.End(t)

	// 13. アップロードする
	// 14. キャッシュをDBに格納する
	if _, err := h.Uploader.Upload(bytes.NewBuffer(b), f); err != nil {
		log.Errorf("fail to upload: error=%v", err)
		return
	}
	h.Storage.NewRecord(f)
	h.Storage.Create(&f)
	h.Storage.Save(&f)

	log.Debugln("complete save")
}
Beispiel #6
0
func (self *Uploader) Upload(buf *bytes.Buffer, f storage.Image) (string, error) {
	t := log.Start()
	defer log.End(t)

	object := &gcs.Object{Name: f.Filename, CacheControl: fmt.Sprintf("max-age=%d", sixMonths)}
	if res, err := self.service.Objects.Insert(self.bucket, object).Media(buf).Do(); err == nil {
		fmt.Printf("Created object %v at location %v\n\n", res.Name, res.SelfLink)
	} else {
		log.Fatalf("Objects.Insert failed: %v", err)
	}

	url := self.CreateURL(f.Filename)
	log.Printf("ok: url=%s", url)

	return url, nil
}
Beispiel #7
0
func New(o option.Options) (*Storage, error) {
	t := log.Start()
	defer log.End(t)

	dsn := fmt.Sprintf("%s:%s@%s(%s)/%s?charset=utf8&parseTime=True", o.DBUser, o.DBPassword, o.DBProtocol, o.DBAddress, o.DBName)

	db, err := gorm.Open("mysql", dsn)
	if err != nil {
		return nil, err
	}
	db.LogMode(true)
	db.SetLogger(&log.Gorm{})
	if os.Getenv("ENVIRONMENT") == "develop" {
		db.DropTable(&Image{})
	}
	db.CreateTable(&Image{})
	db.AutoMigrate(&Image{})

	return &Storage{db}, nil
}
Beispiel #8
0
// Preprocess はリサイズ処理の前処理を行います。
// 画像をデコードし、jpegのEXIFの回転情報をピクセルに反映して返します。
func (self *Processor) Preprocess(path string) (image.Image, error) {
	t := log.Start()
	defer log.End(t)

	// ファイルをデコードする
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	i, format, err := image.Decode(bufio.NewReader(f))
	if err != nil {
		log.Println("fail to decode")
		return nil, err
	}

	// jpeg以外ならピクセルをそのまま返す
	if format != storage.FormatJpeg {
		return i, nil
	}

	// jpegならEXIFの回転情報をピクセルに反映して返す
	f, err = os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	io, err := orient(bufio.NewReader(f), i)
	// 回転に失敗した場合、元のピクセルを返す
	if err != nil {
		log.Println("cancel to apply orientation")
		return i, nil
	}
	log.Debugf("%s -> %s", reflect.TypeOf(i), reflect.TypeOf(io))
	return io, nil
}
Beispiel #9
0
func TestStartAndEnd(t *testing.T) {
	os.Setenv(log.EnvLogFilename, file)
	filename, err := log.Init()
	if err != nil {
		t.Fatalf("fail to init: error=%v", err)
	}
	if err := os.Unsetenv(log.EnvLogFilename); err != nil {
		t.Fatalf("fail to unset env: error=%v", err)
	}

	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()

	timer := log.Start()

	log.Debug("debug")
	log.Print("print")
	log.Info("info")
	log.Warn("warn")
	log.Warning("warning")
	log.Error("error")

	log.Debugf("debug%s", "f")
	log.Printf("print%s", "f")
	log.Infof("info%s", "f")
	log.Warnf("warn%s", "f")
	log.Warningf("warning%s", "f")
	log.Errorf("error%s", "f")

	log.Debugln("debugln")
	log.Println("println")
	log.Infoln("infoln")
	log.Warnln("warnln")
	log.Warningln("warningln")
	log.Errorln("errorln")

	log.End(timer)

	expected := []map[string]string{
		map[string]string{"level": "info", "msg": "start"},

		map[string]string{"level": "info", "msg": "print"},
		map[string]string{"level": "info", "msg": "info"},
		map[string]string{"level": "warning", "msg": "warn"},
		map[string]string{"level": "warning", "msg": "warning"},
		map[string]string{"level": "error", "msg": "error"},

		map[string]string{"level": "info", "msg": "printf"},
		map[string]string{"level": "info", "msg": "infof"},
		map[string]string{"level": "warning", "msg": "warnf"},
		map[string]string{"level": "warning", "msg": "warningf"},
		map[string]string{"level": "error", "msg": "errorf"},

		map[string]string{"level": "info", "msg": "println"},
		map[string]string{"level": "info", "msg": "infoln"},
		map[string]string{"level": "warning", "msg": "warnln"},
		map[string]string{"level": "warning", "msg": "warningln"},
		map[string]string{"level": "error", "msg": "errorln"},

		map[string]string{"level": "info", "msg": "end"},
	}

	bufs, err := ioutil.ReadFile(filename)
	if err != nil {
		t.Fatalf("fail to read file: error=%v", err)
	}
	lines := strings.Split(string(bufs), "\n")

	l := len(lines)
	for i, line := range lines {
		// fmt.Println(i, line)

		if i == l-1 {
			break
		}
		var obj map[string]interface{}
		if err := json.Unmarshal([]byte(line), &obj); err != nil {
			t.Fatalf("fail to decode json")
		}

		e := expected[i]
		// fmt.Println(i, e)
		if obj["level"] != e["level"] {
			t.Fatalf("wrong level: expected %s, but actual %s", e["level"], obj["level"])
		}
		if obj["msg"] != e["msg"] {
			t.Fatalf("wrong msg: expected %s, but actual %s", e["msg"], obj["msg"])
		}
	}

	if err := log.Close(); err != nil {
		t.Fatalf("fail to close log: error=%v", err)
	}
}
Beispiel #10
0
// 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
}
Beispiel #11
0
func (self *Storage) Close() error {
	t := log.Start()
	defer log.End(t)

	return self.DB.DB().Close()
}