首先來看看是怎樣的應用吧!
使用者上傳名字與圖片,然後在/show頁面可以看到所有上傳的東西,大概就是這樣,那在這個範例裡我們用Google Cloud Storage來存取圖片,另外這個圖片是有經過處理,會將長邊縮放成150px另一邊則等比例縮放。
第一張圖是來自我滿喜歡的動畫 第二張圖是我隨便畫的XD |
範例專案結構:
/app- app.yaml
- appengine_config.py
- router.py
- main.py
- /templates
- upload.html
- show.html
- /models
- users.py
- init.py
- /lib
- /cloudstorage
要使用google cloud storage需下載其client library,方法請看這篇[GAE] Google Cloud Storage Client Library入門,照著安裝步驟做,專案裡就會有 appengine_config.py和/lib/cloudstorage,也就可以正常使用google cloud storage api了。
Code:
1. /templates/upload.html:上傳用的表單頁面<html>
<head></head>
<meta charset="UTF-8">
<title>Upload</title>
<body>
<form action="" enctype="multipart/form-data" method="post">
<div class="form-group">
<label for="user_name">Name:</label>
<input type="text" id="user_name" name="user_name">
</div>
<div class="form-group">
<label for="uploaded_file">Photo:</label>
<input type="file" id="uploaded_file" name="uploaded_file">
</div>
<div class="form-group">
<button type="submit">Send</button>
</div>
</form>
</body>
</html>
* 要注意的是在表單中要加入 enctype="multipart/form-data" 。在傳送POST request時我們需要將body的資料進行encode編碼,HTML提供三種encoding方法,分別是:
1. application/x-www-form-urlencoded (default)
2. multipart/form-data
3. text/plain
一般若有 <input type="file"> 需要上傳資料時都會使用multipart/form-data,其他則使用application/x-www-form-urlencoded,至於text/plain是完全不介意使用。
(詳細可以參考這:https://stackoverflow.com/questions/4526273/what-does-enctype-multipart-form-data-mean)
* form action="" 是指送出表單後將連到原網頁。
2. /models/users.py:建立Users Model,相當於建立一個Users Table,內有name、photo_key與photo_url三個欄位,其中最重要的是photo_url,這個URL是要給Client端讀取圖片用的。
from google.appengine.ext import ndb
class Users(ndb.Model):
name = ndb.StringProperty(required=True)
photo_key = ndb.BlobKeyProperty()
photo_url = ndb.StringProperty()
@classmethod
def add_new_user(cls, user_name, photo_key, photo_url):
user_key = cls(
name=user_name,
photo_key=photo_key,
photo_url=photo_url
).put()
return user_key
* 我們的Users Model有name、photo_key和photo_url三個儲存屬性,photo_url是用來存圖片的公開的URL用,這個URL是透過blobkey來取得,取得的過程請看下面main.py裡的save_image()函式。
(後來想一想其實可以不用存blobkey,因為我們只要知道圖片的公開URL就可以讀取了,有了blobkey是在之後我們可以使用Blobstore API對這個檔案進行各種修改)
3. router.py:設定url映射,/upload對應到main.py中的UploadHandler,/show則對應到main.py中的ShowPhotoHandler。
import webapp2
from webapp2 import Route
app = webapp2.WSGIApplication([
Route('/upload', handler='main.UploadHandler'),
Route('/show', handler='main.ShowPhotoHandler'),
], debug=True)
4. main.py:handler處理。
Client送出POST表單,我們會執行UploadHandler 裡的 def post(self)的內容,利用self.request.POST['id'] 取出HTTP POST來的資料,在 save_image() 這個函式裡,我們設定好cloud_storage_path決定上傳的檔案要送到Google Cloud Storage的哪裡,之後就像平常的寫入檔案一樣,首先open()再write()寫入。
那我們要如何取得這個圖片的公開URL呢?
其實我們在open()檔案時是可以設定檔案的權限,在open()內加入options={'x-goog-acl': 'public-read'}就可以公開檔案,而Public URL會是這樣:https://storage.googleapis.com/[BucketName]/[FileName],但在這裡我們不這麼做。
而是要使用Google App Engine提供的 Image Service!
Image Service是種REST API,若不懂 Rest API 就先暫時想成是一種可以讓使用者透過URL連結得到服務這樣的設計。 (建議還是去Google一下就是了)
總之想使用這個 Image Service 我們就要得到它 serving URL,那我們可以透過BlobKey來取得,具體做法是這樣:
blobstore_key = blobstore.create_gs_key(cloud_storage_path)
blobstore_key = blobstore.BlobKey(blobstore_key)
serving_url = images.get_serving_url(blobstore_key)
main.py完整code:# -*- coding: utf-8 -*-
import webapp2
import os
import logging
import jinja2
import cloudstorage as gcs
from google.appengine.api import app_identity
from google.appengine.api import blobstore
from google.appengine.api import images
from google.appengine.api import users
from models.users import Users
template_dir = os.path.join(os.path.dirname(__file__), 'template')
jinja_enviroment = jinja2.Environment(
loader=jinja2.FileSystemLoader(template_dir)
)
class UploadHandler(webapp2.RequestHandler):
def get(self):
template = jinja_enviroment.get_template('upload.html')
self.response.write(template.render())
def post(self):
user_photo = self.request.POST['uploaded_file'] # 得到POST上傳的file資料
user_name = self.request.POST['user_name']
saved_photo = self.save_image(user_photo)
Users.add_new_user(
user_name = user_name,
photo_key = saved_photo['blobstore_key'],
photo_url = saved_photo['serving_url']
)
self.response.write('Upload Success')
@classmethod
def save_image(cls, photo):
img_title = photo.filename
img_content = photo.file.read()
img_type = photo.type
bucket_name = os.environ.get('BUCKET_NAME',
app_identity.get_default_gcs_bucket_name())
cloud_storage_path = '/gs/'+bucket_name+'/%s' %(img_title)
blobstore_key = blobstore.create_gs_key(cloud_storage_path) # 使用Blobstore API提供的create_gs_key()得到blobkey字串
with gcs.open(cloud_storage_path[3:], 'w', content_type=img_type) as f: # 要注意寫入cloud storage的路徑沒有含 '/gs'
f.write(img_content)
blobstore_key = blobstore.BlobKey(blobstore_key) #將字串轉成BlobKey object
serving_url = images.get_serving_url(blobstore_key) # 使用images.get_serving_url()從blobkey得到圖片的image serving URL
return {
'serving_url': serving_url,
'blobstore_key': blobstore_key
}
class ShowPhotoHandler(webapp2.RequestHandler):
def get(self):
users = Users.query() # 讀取在Google Cloud Datastore中的所有Users
template_context = {
"users": users,
}
template = jinja_enviroment.get_template('show.html')
self.response.write(template.render(template_context))
* App Engine免費提供的 Image Service 功能可以讓我們不需要存複數張圖片,就可以動態調整圖片大小與旋轉方向等,而且要做的只有修改一點 url 就可以,像下面show.html中我們只是在image serving url後加上 '=s150' 就可以讓圖片的最長邊縮成150px,另一邊則等比例縮小,真的很方便。
(詳細image serving url用法請參考: https://cloud.google.com/appengine/docs/standard/python/images/ )
5. /templates/show.html:圖片顯示頁面
<html>
<head></head>
<meta charset="UTF-8">
<title>Show</title>
<body>
<table border=1>
<tr>
<td bgcolor=pink align="center">Name</td>
<td align="center" width="150px">Photo</td>
</tr>
{% for user in users%}
<tr>
<td bgcolor=pink align="center">{{user.name}}</td>
<td width="150px" height="150px" align="center"><img src={{user.photo_url + '=s150'}}></td>
</tr>
{% endfor %}
</table>
</body>
</html>
Local測試:
輸入 dev_appserver.py app.yaml --clear_datastore=yes
(clear_datastore=yes 幫助我們清除local的資料庫裡之前的資料)
在瀏覽器輸入 http://localhost:8080/upload 可以看到上傳表單,送出後會看到Upload Success的字串。
在到 http://localhost:8080/show 看結果。
最後不妨到 http://localhost:8000/datastore 看一下資料庫,因為目前local端沒有cloud storage,但若你存在default bucket的話應該是能在blobstore viewer那看到資料。
參考資料:
1. 雲端網頁程式設計:Google App Engine使用Python
2. Learn to build scalable web applications (特別是Section07)
2. Learn to build scalable web applications (特別是Section07)
沒有留言:
張貼留言