LIVE DEMO

在開發 GAE 應用程式時,難免會遇到應用程式需要處理圖片的問題。GAE 的應用程用可以直接存取靜態的資源檔案,這塊直接在 app.yaml 檔案中設定就可以了,不過也因為 GAE 應用程式的特性,需要將所有的資源上傳一份到 GAE 正式環境中,所以會發現上傳專案的容量大小會爆增(如果你將所有需要的圖檔階直接放在程式內)。

專案檔案太大會影響 GAE 在自動擴展的效能,所以盡可能的將不必要的東西移多專案外(圖檔等)

app.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
- url: /favicon.jpg
mime_type: image/x-icon
static_files: public/images/favicon.jpg
upload: public/images/favicon.jpg
- url: /js/*
mime_type: text/javascript
static_dir: public/js
secure: always
- url: /css/*
mime_type: text/css
static_dir: public/css
secure: always
http_headers:
Access-Control-Allow-Origin: "*"
- url: /images/*
static_dir: public/images
secure: always

每一個 GCP 的專案可以有 5G 免費的 Google Cloud Storage 空間,所以將 GAE 上大檔案的靜態資源放到 GCS 是一個很好的決定,在 GAE 程式中可以使用 google-api-go-client 來存取 GCS 上相關的資源。如果是圖片檔的話,更可以直接用 Image API 的方式來直接讀取圖片資源

Images Go API 讓我們使用 Blobstore 的方式來讀取/裁切圖檔,當我們透過 Blobstore 拿到靜態圖檔時可以直接透過 url 的方式對目標圖片進行調整圖檔的大小或進行裁切

1
2
3
4
5
# 將目標圖片重新縮放至 32 像素(等比例 - 長邊)
http://lhx.ggpht.com/randomStringImageId=s32
# 將照片裁切至 32 像素
http://lhx.ggpht.com/randomStringImageId=s32-c

imgServe

因些我們就可以設計一個動態的路由來對應至 GCS 上特定路徑的圖片

1
2
3
4
/img?f=dress/banner.png --> GCS:Bucket/dress/banner.png
/img?f=dress/color/banner.png --> GCS:Bucket/dress/color/banner.png
/img?f=dress/color/banner.png&s=200 --> GCS:Bucket/dress/color/banner.png 等比縮放至 200 像素
/img?f=dress/color/banner.png&s=200-c --> GCS:Bucket/dress/color/banner.png 等比縮放至 200 像素且進行 1:1 的裁切

如此一來前端面頁如果需要引用照片,可以直接使用 <img src="/img?f=dress/banner.png" alt="">,如果目標圖片不存在則直接輸入一開始就準備好的 image 404 圖片進行代替

main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package main
import (
"fmt"
"golang.org/x/net/context"
"net/http"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"google.golang.org/appengine"
"google.golang.org/appengine/blobstore"
"google.golang.org/appengine/image"
)
const (
Bucket = "<your-gae-default-bucket>"
NotFoundImageURL = "img-api-example/img404.jpg"
)
/**
* index page
*/
func indexGET(c echo.Context) error {
c.Render(200, "index.tpl", map[string]interface{}{
"title": "go gae image API example",
"images": []string{
"img?f=img-api-example/1.png",
"img?f=img-api-example/1.png&s=200",
"img?f=img-api-example/1.png&s=200-c",
"img?f=img-api-example/2.jpg",
},
})
return nil
}
/**
* get not foound image default image, img404.jpg
*/
func getNotFoundImage(ctx context.Context) (urlString string, err error) {
blobPath := fmt.Sprintf("/gs/%s/%s", Bucket, NotFoundImageURL)
blobKey, err := blobstore.BlobKeyForFile(ctx, blobPath)
if err != nil {
return
}
opts := image.ServingURLOptions{Secure: false, Crop: true}
url, err := image.ServingURL(ctx, blobKey, &opts)
if err != nil {
return
}
urlString = url.String()
return
}
/**
* image serve handler
*/
func imgServe(c echo.Context) error {
ctx := appengine.NewContext(c.Request().(*standard.Request).Request)
filePath := c.QueryParam("f")
size := c.QueryParam("s")
blobPath := fmt.Sprintf("/gs/%s/%s", Bucket, filePath)
blobKey, err := blobstore.BlobKeyForFile(ctx, blobPath)
if err != nil {
c.String(http.StatusExpectationFailed, err.Error())
}
opts := image.ServingURLOptions{Secure: false, Crop: true}
if url, err := image.ServingURL(ctx, blobKey, &opts); err != nil {
if n, err := getNotFoundImage(ctx); err != nil {
return err
} else {
c.Redirect(http.StatusTemporaryRedirect, n)
}
} else {
if size != "" {
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s=s%s", url.String(), size))
} else {
c.Redirect(http.StatusTemporaryRedirect, url.String())
}
}
return nil
}
func init() {
e.GET("/", indexGET)
e.GET("/img", imgServe)
}

GAE Go Image serve GCS image

demo code

cage1016/gae-go-image-example: GAE go image API example