Пишу конвертер и перебираюсь на linux
parent
03c5af6536
commit
ce36dd5a07
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue