From ce36dd5a0778787b9455900bcf504f272e856d0e Mon Sep 17 00:00:00 2001 From: Andrey Ivanov Date: Tue, 27 Oct 2020 11:30:14 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B8=D1=88=D1=83=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=82=D0=B5=D1=80=20=D0=B8=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B1=D0=B8=D1=80=D0=B0=D1=8E=D1=81=D1=8C=20=D0=BD?= =?UTF-8?q?=D0=B0=20linux?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- previewer/application/application.go | 3 +- previewer/application/functions.go | 79 -------------------- previewer/application/handlers.go | 24 +++--- previewer/application/query.go | 59 +++++++++++++++ previewer/converter/converter.go | 106 +++++++++++++++++++++++++++ 5 files changed, 178 insertions(+), 93 deletions(-) delete mode 100644 previewer/application/functions.go create mode 100644 previewer/application/query.go create mode 100644 previewer/converter/converter.go diff --git a/previewer/application/application.go b/previewer/application/application.go index 7ba3177..1ae7d46 100644 --- a/previewer/application/application.go +++ b/previewer/application/application.go @@ -11,7 +11,7 @@ import ( type App struct { *http.Server - Log logger.Interface + Log logger.Interface Cache cache.Cache } @@ -38,4 +38,3 @@ func (s *App) Stop() error { } return nil } - diff --git a/previewer/application/functions.go b/previewer/application/functions.go deleted file mode 100644 index 9d9fdd4..0000000 --- a/previewer/application/functions.go +++ /dev/null @@ -1,79 +0,0 @@ -package application - -import ( - "bytes" - "image" - "io/ioutil" - "log" - "net/http" - "net/url" - "strconv" - "strings" - "errors" - "image/jpeg" - "github.com/nfnt/resize" -) - -type Query struct { - Height int - Width int - URL *url.URL -} - -func BuildQuery(u *url.URL) (q Query, err error) { - t := strings.Split(u.Path, "/") - q.Width,err=strconv.Atoi(t[2]) - if err!=nil { - return Query{}, errors.New("width must be an integer") - } - q.Height,err=strconv.Atoi(t[3]) - if err!=nil { - return Query{}, errors.New("height must be an integer") - } - tn := "http://"+strings.Join(t[4:],"/") - q.URL,err=q.URL.Parse(tn) - if err!=nil { - return Query{}, errors.New("not valid url") - } - return q,nil -} - -func (q Query) id() string { - return strconv.Itoa(q.Width)+"/"+strconv.Itoa(q.Height)+"/"+q.URL.Path -} - -func (q Query) fromOrigin() ([]byte, http.Header, error) { - client := &http.Client{} - req, err := http.NewRequest("GET", "https://" + q.URL.Host + "/" + q.URL.Path, nil) - if err != nil { - return nil, nil, err - } - req.Close = true - res, err := client.Do(req) - if err != nil { - return nil, nil, err - } - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, nil, err - } - if err = res.Body.Close(); err != nil { - return nil, nil, err - } - return body, res.Header, nil -} - -func (q Query) resize(b []byte) ([]byte, error) { - i,_,err := image.Decode(bytes.NewReader(b)) - if err != nil { - return nil, err - } - log.Println("ресайзим") - m := resize.Resize(uint(q.Width), uint(q.Height), i, resize.Bicubic) - var g []byte - s := bytes.NewBuffer(g) - if err = jpeg.Encode(s,m,nil); err != nil { - return nil, err - } - return g, nil -} \ No newline at end of file diff --git a/previewer/application/handlers.go b/previewer/application/handlers.go index 5a692ba..189ee82 100644 --- a/previewer/application/handlers.go +++ b/previewer/application/handlers.go @@ -2,6 +2,7 @@ package application import ( "github.com/tiburon-777/OTUS_Project/previewer/cache" + "github.com/tiburon-777/OTUS_Project/previewer/converter" "github.com/tiburon-777/OTUS_Project/previewer/logger" "log" "net/http" @@ -10,30 +11,30 @@ import ( func handler(c cache.Cache) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - q,err := BuildQuery(r.URL) - if err!=nil { + q, err := buildQuery(r.URL) + if err != nil { http.Error(w, "Can't parse query", http.StatusNotFound) return } - b,ok := c.Get(cache.Key(q.id())) - pic,ok := b.([]byte) + b, ok := c.Get(cache.Key(q.id())) + pic, ok := b.([]byte) if ok { log.Println("Взяли из кэша") - writeResponce(w, nil,200,pic) + writeResponse(w, nil, pic) return } - pic,h,err := q.fromOrigin() + pic, h, err := q.fromOrigin() if err != nil { http.Error(w, "Pic not found in origin", http.StatusNotFound) return } - pic, err = q.resize(pic) + pic, err = converter.SelectType(q.Width, q.Height, pic) if err != nil { http.Error(w, "Resizer kirdyk...", http.StatusInternalServerError) return } - c.Set(cache.Key(q.id()),pic) - writeResponce(w, h,200,pic) + c.Set(cache.Key(q.id()), pic) + writeResponse(w, h, pic) }) } @@ -55,12 +56,11 @@ func loggingMiddleware(next http.Handler, l logger.Interface) http.HandlerFunc { }) } -func writeResponce(w http.ResponseWriter, h http.Header, code int, body []byte) { +func writeResponse(w http.ResponseWriter, h http.Header, body []byte) { for name, values := range h { for _, value := range values { - w.Header().Add(name,value) + w.Header().Add(name, value) } } _, _ = w.Write(body) } - diff --git a/previewer/application/query.go b/previewer/application/query.go new file mode 100644 index 0000000..66d9eee --- /dev/null +++ b/previewer/application/query.go @@ -0,0 +1,59 @@ +package application + +import ( + "errors" + "io/ioutil" + "net/http" + "net/url" + "strconv" + "strings" +) + +type Query struct { + Height int + Width int + URL *url.URL +} + +func buildQuery(u *url.URL) (q Query, err error) { + t := strings.Split(u.Path, "/") + q.Width, err = strconv.Atoi(t[2]) + if err != nil { + return Query{}, errors.New("width must be an integer") + } + q.Height, err = strconv.Atoi(t[3]) + if err != nil { + return Query{}, errors.New("height must be an integer") + } + tn := "http://" + strings.Join(t[4:], "/") + q.URL, err = q.URL.Parse(tn) + if err != nil { + return Query{}, errors.New("not valid url") + } + return q, nil +} + +func (q Query) id() string { + return strconv.Itoa(q.Width) + "/" + strconv.Itoa(q.Height) + "/" + q.URL.Path +} + +func (q Query) fromOrigin() ([]byte, http.Header, error) { + client := &http.Client{} + req, err := http.NewRequest("GET", "https://"+q.URL.Host+"/"+q.URL.Path, nil) + if err != nil { + return nil, nil, err + } + req.Close = true + res, err := client.Do(req) + if err != nil { + return nil, nil, err + } + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, nil, err + } + if err = res.Body.Close(); err != nil { + return nil, nil, err + } + return body, res.Header, nil +} diff --git a/previewer/converter/converter.go b/previewer/converter/converter.go new file mode 100644 index 0000000..fcfb65a --- /dev/null +++ b/previewer/converter/converter.go @@ -0,0 +1,106 @@ +package converter + +import ( + "bytes" + "errors" + "github.com/nfnt/resize" + "image" + "image/draw" + "image/gif" + "image/jpeg" + "image/png" + "net/http" +) + +type Image struct { + image.Image +} + +func SelectType(width int, height int, b []byte) ([]byte, error) { + contentType := http.DetectContentType(b) + var decode func(reader *bytes.Reader) (image.Image, error) + var encode func(m image.Image) ([]byte, error) + switch contentType { + case "image/png": + decode = func(reader *bytes.Reader) (image.Image, error) { + return png.Decode(bytes.NewReader(b)) + } + encode = func(m image.Image) (res []byte, err error) { + err = png.Encode(bytes.NewBuffer(res), m) + return res, err + } + case "image/gif": + decode = func(reader *bytes.Reader) (image.Image, error) { + return gif.Decode(bytes.NewReader(b)) + } + encode = func(m image.Image) (res []byte, err error) { + err = gif.Encode(bytes.NewBuffer(res), m, nil) + return res, err + } + default: + decode = func(reader *bytes.Reader) (image.Image, error) { + return jpeg.Decode(bytes.NewReader(b)) + } + encode = func(m image.Image) (res []byte, err error) { + err = jpeg.Encode(bytes.NewBuffer(res), m, nil) + return res, err + } + } + i, err := decode(bytes.NewReader(b)) + m := Image{i} + if err != nil { + return nil, err + } + err = m.convert(width, height) + if err != nil { + return nil, err + } + res, err := encode(m) + return res, err +} + +func (img *Image) convert(width int, height int) error { + widthOrig := img.Bounds().Max.X + heightOrig := img.Bounds().Max.Y + sfOriginal := sizeFactor(widthOrig, heightOrig) + sfNew := sizeFactor(int(width), int(height)) + switch { + case sfOriginal > sfNew: + // Ресайз по одной высоте и кроп по ширине следом + // Определение ширины кропа. + img.resize(int(float64(widthOrig)*sfOriginal), height) + if err := img.crop(image.Point{X: (widthOrig - width) / 2, Y: 0}, image.Point{X: (widthOrig-width)/2 + width, Y: height}); err != nil { + return err + } + case sfOriginal == sfNew: + img.resize(width, height) + case sfOriginal < sfNew: + // Ресайз по одной ширине и кроп по высоте следом + img.resize(width, int(float64(heightOrig)*sfOriginal)) + if err := img.crop(image.Point{X: 0, Y: (heightOrig - height) / 2}, image.Point{X: width, Y: (heightOrig-height)/2 + height}); err != nil { + return err + } + } + return nil +} + +func (img *Image) resize(width, height int) { + img = &Image{resize.Resize(uint(width), uint(height), img, resize.Bicubic)} +} + +func (img *Image) crop(p1 image.Point, p2 image.Point) error { + if img == nil { + return errors.New("corrupted image") + } + if p1.X < 0 || p1.Y < 0 || p2.X < 0 || p2.Y < 0 { + return errors.New("not valid corner points") + } + b := image.Rect(0, 0, p2.X, p2.Y) + resImg := image.NewRGBA(b) + draw.Draw(resImg, b, img, p1, draw.Src) + return nil +} + +func sizeFactor(width int, height int) float64 { + return float64(width) / float64(height) +}