Пишу конвертер и перебираюсь на linux

master
Andrey Ivanov 2020-10-27 11:30:14 +03:00 committed by Andrey Ivanov
parent 03c5af6536
commit ce36dd5a07
5 changed files with 178 additions and 93 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}