18 สิงหาคม 2560

set up flask + python3 on ec2

เนื่องจาก GAE จะใช้ฟรีต้องใช้ appengine standard ซึ่งรองรับแค่ python2.7

โดยโปรเจคนี้ต้องการใช้ firebase ซึ่งมี pyrebase เป็นตัว warpper API ในการ รับส่งข้อมูล
และ pyrebase พัฒนาบน python3 ด้วยเหตุนี้ จึงจำเป็นต้องไป python3

GAE flex รองรับ python3 แต่ ด้วยราคาที่ลองใช้ดูแล้วตก $85 (3,000THB) ต่อเดือน เปรียบเทียบแล้ว ย้ายกลับมาใช้ EC2 freetier ดีกว่า

โดยเราสามารถ ย้าย code ที่เขียนอยู่บน GAE มาได้เลย โดยปรับนิดหน่อย

มี app.yaml ที่ไม่ต้องใช้ นอกนั้น copy มาได้หมดเลย


สิ่งที่เตรียมเพิ่มสำหรับการใช้งาน flask on EC2

sudo yum install mod24_wsgi-python35

เมื่อพร้อมแล้ว ไปสร้าง directory ไว้ที่

mkdir /var/www/flaskapp

สร้าง virtual environment เตรียมไว้ โดยลง package สำหรับ application

virtualenv -p python3 py3
source py3/bin/activate
pip install -r requirements.txt 
* requiements.txt คือ ไฟล์ที่บอกว่า application ต้องลง package อะไรเพิ่มบ้าง หากไม่มีก็ไม่ต้องใช้

นำ source code มาวางไว้ใต้ path นี้ แล้ว สร้างไฟล์ใหม่เพิ่มอีก 1 ไฟล์ เพื่อเรียกใช้ wsgi

vi /var/www/flaskapp/deploy.wsgi

##############################################
import sys,os,site

# Add virtualenv site packages
site.addsitedir(os.path.join(os.path.dirname(__file__),     'py3/local/lib/python3.5/site-packages'))
site.addsitedir(os.path.join(os.path.dirname(__file__),     'py3/local/lib64/python3.5/site-packages'))

# Path of execution
sys.path.append('/var/www/flaskapp')

activate_env = os.path.expanduser(os.path.join(os.path.dirname(__file__), 'py3/bin/activate_this.py'))
exec(compile(open(activate_env,"rb").read(),activate_env,'exec'), dict(__file__=activate_env))

from flaskapp import app as application

สร้างไฟล์สำหรับ port 80 ให้มาเรียกใช้ flask
vi /etc/httpd/conf.d/vhost.conf

##############################################
<VirtualHost *:80>
    ServerName flask.yuunai.com

    WSGIDaemonProcess flaskapp threads=5 user=ec2-user home=/var/www/flaskapp
    WSGIScriptAlias / /var/www/flaskapp/deploy.wsgi

    <Directory /var/www/flaskapp>
       WSGIProcessGroup flaskapp
       WSGIApplicationGroup %{GLOBAL}
       Order deny,allow
       Allow from all
    </Directory>
</VirtualHost>

ServerName ใส่อะไรก็ได้

จากนั้น restart service httpd
sudo service httpd restart

ลองเรียกใช้งานผ่านหน้าเวป (^_^)v






30 กรกฎาคม 2560

รู้จัก firebase realtime datatabase on python

ก่อนจะไปเขียน python ก็ต้องมี firebase ให้เรียกใช้งานก่อน
เริ่มต้นไปที่ firebase google (ต้องใช้ google account)

firebase คือ อะไร ไปอ่านเวปนี้ ref. เวปนี้จะเน้นไปทาง java


สร้าง project ขึ้นมาก่อน (google ให้เราสร้างได้ฟรี 25 projects)

click ที่ Database ก็จะมีหน้าตาประมาณนี้ ส่วนที่จะเอาไปใช้คือ ลิงค์

[ https://sample-app-a1e7f.firebaseio.com/ ] แต่ละคน จะได้ส่วนนี้แตกต่างกันไป

ลิงค์นี้คือ ส่วนที่เอาไว้เรียกใช้งาน


แต่โดย default ตรง Ruls (กฎ) จะไม่ยอมให้คนอื่น acces ได้เลย ดังนั้น เราต้องเข้าไปแก้ไขซะ

เบื้องต้นก็แก้ให้เป็น true ให้หมดไปก่อน



อ่านที่เวปนี้ เขียนอธิบายไว้ละเอียด ref.

ตัวอย่างข้อมูล ก็จะได้หน้าตาประมาณนี้ เป็น JSON


ทดสอบว่าสามารถใช้งานได้ สามารถเรียกดูได้จาก

https://sample-app-a2e7f.firebaseio.com/mydata.json


ต่อมาในส่วนของ python

ขั้นแรก import firebase package ตอนนี้ มีอยู่ 3 แหล่งที่ทาง google แนะนำ ref



1. python-firebase by   Özgür Vatansever


ลง package ที่ต้องจำเป็นก่อนที่จะใช้งานตัวนี้คือ requests

$ sudo pip install requests
$ sudo pip install python-firebase
$ sudo pip install python-firebase-gae 
** ตัวนี้ถ้าไปใช้กับ google appengine standard จะมีปัญหา เนื่องจากไม่ support multi processor ให้ติดตั้ง python-firebase อีกตัวแทน
ดูรายละเอียดได้ที่ https://github.com/b4oshany/python-firebase-gae/



ใน code python ให้เพิ่ม 2 บรรทัดนี้ลงไป

from firebase import firebase
firebase = firebase.FirebaseApplication('https://sample-app-x.firebaseio.com', None)

ตรงส่วนสีแดง ก็ใส่ URL link ของตัวเองลงไป

วิธีเขียนข้อมูลลง DB ref


PUT กับ POST ใช้ต่างกันยังไง

- PUT จะต้องระบุ key
- POST จะสร้าง random key ให้

เช่น

result = firebase.post(id, data['comment'])
firebase จะ random key ให้

/id
   -Kq7rSDsQRXWKAVu0phH: "comment"
หากอยากระบุ key ของตัวเอง และ ให้ firebase สร้าง key ให้

result = firebase.post(id,{'comment': 'mycomment'})
ได้ nested JSON

/id
   -Kq7swawlLJlz07VEBFT
      comment: "mycomment"
กรณี PUT จะต้องระบุ key ให้ด้วย
result = firebase.put(id,'comment',{'msg': 'mycomment'})
จะได้ผลลัพธ์แบบนี้

/id
   -comment
      msg: "mycomment"
ถ้าอ้างอิง id , comment เดิม ข้อมูลที่ put ลงมาจะเขียนทับ ข้อมูลเก่า

PATCH ใช้ update key ที่ระบุไว้ โดยไม่กระทบ กับ ข้อมูลส่วนอื่น

DELETE เอาไว้ลบข้อมูล

firebase.delete(id, 'comment')

วิธีการเรียกอ่านข้อมูล  GET ref

result = firebase.get('/users', '1')
ใส่ option {'print': 'pretty'} เพื่อให้ ผลออกมาอ่านได้ง่ายขึ้น

result = firebase.get('/users/2', None, {'print': 'pretty'}, {'X_FANCY_HEADER': 'VERY FANCY'})
ลองเอา URL นี้วางใน browser  https://sample-app-a1e7f.firebaseio.com/mydata.json

จะได้ผลออกมาแบบนี้ ซึ่งอ่านยากมาก


ลองเพิ่ม option print = pretty

https://sample-app-a1e7f.firebaseio.com/mydata.json?print=pretty

จะได้ผลออกมาแบบนี้ ซึ่งอ่านง่ายกว่าเยอะ


Authentication


from firebase import firebase
firebase = firebase.FirebaseApplication('https://your_storage.firebaseio.com', authentication=None)
result = firebase.get('/users', None, {'print': 'pretty'})
print result
{'error': 'Permission denied.'}

authentication = firebase.Authentication('THIS_IS_MY_SECRET', 'ozgurvt@gmail.com', extra={'id': 123})
firebase.authentication = authentication
print authentication.extra
{'admin': False, 'debug': False, 'email': 'ozgurvt@gmail.com', 'id': 123, 'provider': 'password'}

user = authentication.get_user()
print user.firebase_auth_token
"eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJhZG1pbiI6IGZhbHNlLCAiZGVidWciOiBmYWxzZSwgIml
hdCI6IDEzNjE5NTAxNzQsICJkIjogeyJkZWJ1ZyI6IGZhbHNlLCAiYWRtaW4iOiBmYWxzZSwgInByb3ZpZGVyIjog
InBhc3N3b3JkIiwgImlkIjogNSwgImVtYWlsIjogIm96Z3VydnRAZ21haWwuY29tIn0sICJ2IjogMH0.lq4IRVfvE
GQklslOlS4uIBLSSJj88YNrloWXvisRgfQ"

result = firebase.get('/users', None, {'print': 'pretty'})
print result
{'1': 'John Doe', '2': 'Jane Doe'}
เมื่อ sign-in ด้วย email,password  ใช้ function Authentication เพื่อ ให้ firebase สร้าง token สำหรับใช้ตรวจสอบว่ากับ rule ว่ามีสิทธิ์มั้ย

authentication = firebase.Authentication('password', 'email', extra={'id': 123})
ดูค่าที่ทาง server return กลับมา

print authentication.extra

{'admin': False, 
 'debug': False,
 'email': 'ozgurvt@gmail.com',
 'id': 123,
 'provider': 'password'}

provider คือ attribute ที่บอกว่า เรา authen ด้วยวิธีใด
เนื่องจาก firebase รองรับ multi provider คือ login ด้วย google , facebook .. สามารถมี accout id เดียวกันได้ id คือ ตัวบอกว่า login ด้วยวิธีนี้ แล้วจะได้ id อะไร

2.  Pyrebase by Jame childs-maidment

* ควรใช้กับ python3 หากใช้กับ python2 อาจจะมีข้อผิดพลาด

pip install pyrebase
เรียกใช้

import pyrebase

config = {
  "apiKey": "apiKey",
  "authDomain": "projectId.firebaseapp.com",
  "databaseURL": "https://databaseName.firebaseio.com",
  "storageBucket": "projectId.appspot.com",
  "serviceAccount": "path/to/serviceAccountCredentials.json"
}

firebase = pyrebase.initialize_app(config)

serviceAccount ไว้สำหรับเมื่อ admin login เข้ามาสามารถใช้งานได้โดยไม่สน rule ที่ประกาศใน firebase

วิธีการ create serviceAccountCredentials ref

1. เข้าไปที่หน้า firebase console แล้วเลือก setting (รูปเฟือง) > users and permissions



2.  เลือก Service accounts



3. ที่ฝั่งขวา เลือก firebase-adminsdk  แล้วกด create key



4. เมื่อได้ไฟล์มา เอาไปวางไว้ที่ server เพื่อ เรียกใช้  (ตัวอย่าง ส่วนหนึ่ง)

Pyrebase เรียกใช้ firebase service ได้ถึง 3 (python-firebase ได้แค่ database , authen)

- firebase.auth() - Authentication
- firebase.database() - Database
- firebase.storage() - Storage

Authentication

sign_in_with_email_and_password() จะส่ง user data และ token เพื่อใช้กับ firebase rule

# Get a reference to the auth service
auth = firebase.auth()

# Log the user in
user = auth.sign_in_with_email_and_password(email, password)

# Get a reference to the database service
db = firebase.database()

# data to save
data = {
    "name": "Mortimer 'Morty' Smith"
}

# Pass the user's idToken to the push method
results = db.child("users").push(data, user['idToken'])
ระบุ user['idToken'] ด้วยเพื่อให้มี สิทธิ์ในการ write

ปกติ token จะ expire ภายใน 1 ชม. ดังนั้นหากมีการใช้อยู่ให้เรา refresh ใหม่เรื่อยๆ

user = auth.sign_in_with_email_and_password(email, password)
# before the 1 hour expiry:
user = auth.refresh(user['refreshToken'])
# now we have a fresh token
user['idToken']

Manage User

create user ที่ใช้ email /password

auth.create_user_with_email_and_password(email, password)
* ต้อง enable sign-in method ที่ firebase dashboard ด้วย



Verifying emails
auth.send_email_verification(user['idToken'])

Sending password reset emails
auth.send_password_reset_email("email")

Get account information
auth.get_account_info(user['idToken'])

Database

 - Save data

ใช้ child() method ในการสร้าง path ลงไปในแต่ละ level (google support 32 levels)
db = firebase.database()
db.child("users").child("Morty")
push : ใช้เก็บข้อมูลที่ต้องไม่ซ้ำ สร้าง key อัตโนมัติ, timestamp-based key
data = {"name": "Mortimer 'Morty' Smith"}
db.child("users").push(data)
set : เมื่อต้องการระบุ key ของตัวเอง key ที่ระบุคือ "Morty"
data = {"name": "Mortimer 'Morty' Smith"}
db.child("users").child("Morty").set(data)
update : อัพเดทข้อมูลที่มีอยู่แล้ว
db.child("users").child("Morty").update({"name": "Mortiest Morty"})
remove : ลบข้อมูลที่มีอย่แล้ว
db.child("users").child("Morty").remove()

multi-location updates : อัพเดทพร้อมกับหลายที่
data = {
    "users/Morty/": {
        "name": "Mortimer 'Morty' Smith"
    },
    "users/Rick/": {
        "name": "Rick Sanchez"
    }
}

db.update(data)

ใช้ push อัพเดทพร้อมกันหลายที่ต้องใช้คู่กับ generate_key() method.
data = {
    "users/"+db.generate_key(): {
        "name": "Mortimer 'Morty' Smith"
    },
    "users/"+db.generate_key(): {
        "name": "Rick Sanchez"
    }
}

db.update(data)

 - Retrieve data

val : เมื่อได้ object มา ใช้ val() ในการ

users = db.child("users").get()
print(users.val()
# {"Morty": {"name": "Mortimer 'Morty' Smith"},
        "Rick": {"name": "Rick Sanchez"}}
ดึงเฉพาะ value จาก attribute ที่ต้องการ
print(users.val()["name"])
#{ Mortimer 'Morty' Smith ,
# Rick Sanchez }
key : method key() เรียกค่า key อย่างเดียว

user = db.child("users").get()

print(user.key())

 # users
each : ดึงค่าออกมาเป็น list แล้วใช้ key() , val() แสดงค่าอีกที

all_users = db.child("users").get()

for user in all_users.each():

    print(user.key()) # Morty

    print(user.val()) # {name": "Mortimer 'Morty' Smith"}

    print(user.val()["name"])
get : ดึงค่าจาก path ทั้งหมด
all_users = db.child("users").get()
shallow : ดึงเฉพาะ ค่า key ออกมาเท่านั้น
all_user_ids = db.child("users").shallow().get()

# {"Morty",
        "Rick"}
* shallow() ใช้ร่วมกับ complex querie ไม่ได้

streaming : จับการเปลี่ยนแปลงของข้อมูลทันที ที่มีการเปลี่ยน

def stream_handler(message):

    print(message["event"]) # put

    print(message["path"]) # /-K7yGTTEp7O549EzTYtI

    print(message["data"]) # {'title': 'Pyrebase', "body": "etc..."}



my_stream = db.child("posts").stream(stream_handler)
อย่างน้อยควรจะ put หรือ patch ข้อมูลด้วย Streaminig from the REST API

ถ้ามีการทำ multiple stream ควรระบุ stream_id ด้วย

my_stream = db.child("posts").stream(stream_handler, stream_id="new_posts")
close the stream
my_stream.close()

Complex Queries

Queries สามารถใช้ method ต่อกันได้เรื่อยๆ

users_by_name = db.child("users").order_by_child("name").limit_to_first(3).get()
ผลลัพธ์จะได้ 3 users แรกเรียงตามชื่อ

order_by_child : เรียงตาม level ที่กำหนด ตาม key

users_by_name = db.child("users").order_by_child("name").get()
equal_to : คืนค่าที่มี value ตามที่กำหนด

users_by_score = db.child("users").order_by_child("score").equal_to(10).get()
คืนค่า users ที่มี score เท่ากับ 10

start_at and end_at : ระบุ range ของข้อมูล

users_by_score = db.child("users").order_by_child("score").start_at(3).end_at(10).get()
คืนค่า users เรียงตาม score ที่มี score อยู่ระหว่าง 3-10

limit_to_first and limit_to_last : limit ข้อมูลที่รับ

users_by_score = db.child("users").order_by_child("score").limit_to_first(5).get()
order_by_key : เรียง key ตาม ascending (น้อยไปมาก)

users_by_key = db.child("users").order_by_key().get()
order_by_value : เรียงตาม value

users_by_value = db.child("users").order_by_value().get()

Storage

ใช้เก็บข้อมูลรูปภาพ ไฟล์

child : ระบุ path ที่จะใช้เก็บ

storage.child("images/example.jpg")
put : รับ path จาก local file และ ใส่ token เป็น option ได้

storage = firebase.storage()

# as admin

storage.child("images/example.jpg").put("example2.jpg")

# as user

storage.child("images/example.jpg").put("example2.jpg", user['idToken'])
หากรับ filename มาจาก <input type="file" name="image" />
ตอนรับข้อมูลให้ส่ง request.files.get('image') มาให้ put

storage.child("images/example.jpg").put(request.files.get('image'))

download : ดึงข้อมูลมาเก็บลง local และ สามารถเปลี่ยนชื่อได้

storage.child("images/example.jpg").download("downloaded.jpg")
get_url : คืนค่า url สำหรับ access ผ่าน http

storage.child("images/example.jpg").get_url()
# https://firebasestorage.googleapis.com/v0/b/storage-url.appspot.com/o/images%2Fexample.jpg?alt=media
generate_key : db.generate_key() ใช้ตาม Firebase's key generation algorithm

sort : ปกติ เราจะใช้ orderby แต่ถ้าเราต้องการเรียงอีกที ให้ใช้ sort() method
.
articles = db.child("articles").order_by_child("date").start_at(startDate).end_at(endDate).get()

articles_by_likes = db.sort(articles, "likes")

*Storage ยังไม่มี method delete()





20 กรกฎาคม 2560

Flask : deploy บน Google app engine

สิ่งที่ต้องมีก่อน deploy บน GAE
   - gunicorn
   - app.yaml

โดยโครงสร้าง file จะเป็นดังนี้
/project
  |-app.yaml
  |-config.py
  |-main.py
  /yuunai (application name)
     |-__init__.py
     |-script.py
     /templates
       |-file.html
     /static
       |-file.css

การทำงาน(deploy) จะเรียงลำดับไฟล์ดังนี้

   1. app.yaml ขึ้นมา เพื่อสร้าง environment รองรับการ deploy ตามค่าที่ประกาศไว้ ซึ่งในไฟล์นี้จะมีประกาศ entrypoint คือ gunicorn ซึ่งไปเรียกใช้ main:app
   2. gunicorn ไปอ่านที่ไฟล์ main.py ต่อเพื่อเรียกใช้ ตัวแปร app ที่ประกาศไว้
   3. main.py ประกาศตัวแปร app ให้ไปเรียก application เพื่อ start flask ขึ้นมาใช้งาน
   4. application/__init__.py ที่อยู่ใต้ directory application  ประกาศฟังก์ชั่นโดยใช้ blueprint ไปเรียก script เริ่มต้น
   5. application/script.py เรียกใช้ฟังก์ชั่นต่างๆ และ ส่งค่าไปแสดงผลที่  /application/templates/file.html และ /applicaiton/static/file.css


เนื่องจาก Flask เป็น web application รองรับการเรียกใช้บริการ request by request  (เพราะมันคือ application)  เมื่อมี request มามันจะเกิดการเข้า queue โดยเราจะใช้ gunicorn มาเป็นตัวกลางในการรับส่งข้อมูล โดยใช้คำสั่งตามนี้

pip install gunicorn
gunicorn -w 1 -b 0.0.0.0:5000 hello:app
-w 1 หมายถึง สร้าง web process ขึ้นมา 1 process
-b 0.0.0.0:5000 หมายถึง อนุญาตให้เครื่องอื่นติดต่อเข้ามาผ่าน port เบอร์ 5000
hello:app หมายถึง Application ที่ทำงานคือ app ที่อยู่ใน hello.py

/project/app.yaml
# [START runtime]
runtime: python
env: flex
entrypoint: gunicorn -b :$PORT main:app

runtime_config:
  python_version: 3
เพื่อบอกว่าจะ deploy บน GAE บน Env ไหน (standard/flex) และ ใช้ python version อะไร

 config.py ประกาศค่าคงที่ไว้ที่ไฟล์ สำหรับส่งต่อให้ application

/project/config.py
SECRET_KEY = 'secret'
DATA_BACKEND = 'datastore'
PROJECT_ID = 'yuunai-app'
CLOUD_STORAGE_BUCKET = 'yuunai'
MAX_CONTENT_LENGTH = 8 * 1024 * 1024
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])
GOOGLE_OAUTH2_CLIENT_ID = 'xxx'
GOOGLE_OAUTH2_CLIENT_SECRET = 'xxx'
GOOGLE_OAUTH2_CLIENT_ID = 'xxx'
GOOGLE_OAUTH2_CLIENT_SECRET = 'xxx'

2 ค่านี้ generate จาก credential หรือ ref

1. เลือก OAuth client ID

2. เลือก app type และ ระบุ ชื่อ ใน ตย. ใช้ Python Bookshelf Client
     ต้องใส่ redirect URI ด้วย เนื่องจาก การ authen ใช้ function call back ต้องชี้จุดกลับมาให้ด้วย

    - http://localhost:8080/oauth2callback
    - http://<app-id>.appspot.com/oauth2callback
    - https://<app-id>.appspot.com/oauth2callback
    - http://<app-id>.appspot-preview.com/oauth2callback
    https://<app-id>.appspot-preview.com/oauth2callback


3.  กด create จะได้
  - client id  ==>  GOOGLE_OAUTH2_CLIENT_ID
  - client secret   ==> GOOGLE_OAUTH2_CLIENT_SECRET



/project/main.py
import yuunai
import config

from flask import Flask, render_template
app = yuunai.create_app(config)

if __name__ == '__main__':
   app.run(host='127.0.0.1', port=8080,debug=True)
import application ชื่อ yuunai (directory) โดยที่ไฟล์ __init__.py มีประกาศฟังก์ชั่น create_app เอาไว้แล้วเพื่อรับค่าคงที่จาก config.py

/project/application/__init__.py
from flask import current_app, Flask, redirect, request, session, url_for
def create_app(config, debug=False, testing=False, config_overrides=None):
    app = Flask(__name__)
    app.config.from_object(config)

    app.debug = debug
    app.testing = testing

    if config_overrides:
        app.config.update(config_overrides)
    
    # Register the Bookshelf CRUD blueprint.
    from .crud import crud
    app.register_blueprint(crud, url_prefix='/page')

    # Add a default root route.
    @app.route("/")
    def index():
        return redirect(url_for('crud.list'))


    @app.errorhandler(500)
    def server_error(e):
        return """
        An internal error occurred: <pre>{}</pre>
        See logs for full stacktrace.
        """.format(e), 500

    return app

    # Register the Bookshelf CRUD blueprint.
    from .crud import crud
    app.register_blueprint(crud, url_prefix='/page')
ประกาศ import ไฟล์ crud.py และ ค่า url เริ่มต้นเมื่อมีการใช้งาน
ประกาศ blueprint เมื่อทุกฟังก์ชั่นถูกเรียกใช้จะอยู่ภายใต้ url = /page
การใช้ href ชี้มาใต้ /page จะมาเรียกใช้ crud.py

/project/yuunai/crud.py
from flask import Blueprint, redirect, render_template, request, url_for
crud = Blueprint('crud', __name__)
# [START list]
@crud.route("/")
def list():
    return render_template("list.html")
# [END list]
register crud ให้เป็น script คล้าย index.html

/project/yuunai/templates/base.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Yuunai : where r u</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
  </head>
  <body>
    <div class="navbar navbar-default">
      <div class="container">
        <div class="navbar-header">
          <div class="navbar-brand">Yuunai --></div>
        </div>
        <ul class="nav navbar-nav">
          <li><a href="#">search</a></li>
        </ul>
      </div>
    </div>
    <div class="container">
      {% block content %}{% endblock %}
    </div>
    {{user}}
  </body>
</html>

/project/yuunai/templates/list.html
{% extends "base.html" %}

{% block content %}

<h3>hello world</h3>
<a href="/books/add" class="btn btn-success btn-sm">
  <i class="glyphicon glyphicon-plus"></i>
  Add book
</a>



{% endblock %}
เมื่อใช้ extend และ block content เข้ามาจะช่วยให้สะดวกเรื่องการตบแต่งหน้าเวป ไม่ต้องมี code ซ้ำๆ กันหลายที่

ก่อนที่จะทำการ deploy
1. ทำตามบทความก่อนหน้า มี main.py (เปลี่ยนชื่อจาก app.py) , /templates
2. ทดสอบดูบน preview

หากใช้งานได้ลอง deploy โดย run command
gcloud app deploy

ได้ตัวอย่างดังนี้


















15 กรกฎาคม 2560

Flask : static files

ในเวป บางทีเราจำเป็นต้องมีการใช้งานพวก CSS , javascript ที่ส่วนใหญ่ไม่ค่อยมีการเปลี่ยนแปลงบ่อยๆ ซึ่งเรียกรวมๆว่า static file โดย Flask รองรับไฟล์ประเภทนี้ และ จัดเก็บไว้ภายใต้ directory static

/Application folder
   |-app.py
   |/templates
      |-hello.html
   |/static
      |-hello.js
      |-hello.css

วิธีการเรียกใช้งานใน html
<html>

   <head>
      <script type = "text/javascript" 
         src = "{{ url_for('static', filename = hello.js') }}" ></script>
   </head>
   
   <body>
      <input type = "button" onclick = "sayHello()" value = "Say Hello" />
   </body>
   
</html>












Flask : เริ่มใช้งาน template

จากตัวอย่างที่แล้ว เราสามารถใช้ สร้าง แสดงผล html ออกมาได้ แต่ มันจะยุ่งยากถ้าต้องการแสดงผลแบบซับซ้อน
from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
   return '<html><body><h1>Hello World'</h1></body></html>'

if __name__ == '__main__':
   app.run(debug = True)

วิธีลดความซับซ้อนโดยใช้ Jinja2 template engine เป็นตัวประสานงานกับ Flask ในการแสดงผล รับค่า โดยใช้ function render_template()
from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
   return render_template(‘hello.html’)

if __name__ == '__main__':
   app.run(debug = True)

โดยมีโครงสร้างบังคับต้องมี Directory templates อยุ่ที่ path เดียวกับ script เช่น
/Application folder
   |-app.py
   |/templates
      |-hello.html
โดยการทำงานจะเหมือน PHP คือ สามารถส่ง variable ไปยัง html เพื่อสร้าง dynamic web page ขึ้นมาเช่น

hello.html
<!doctype html>
<html>
   <body>
   
      <h1>Hello {{ name }}!</h1>
      
   </body>
</html>
app.py
from flask import Flask, render_template
app = Flask(__name__)

@app.route('/hello/<user>')
def hello_name(user):
   return render_template('hello.html', name = user)

if __name__ == '__main__':
   app.run(debug = True)

Jinja2 teamplate engine ใช้เครื่องหมายในการประกาศตัวแปร หรือ การทำงานดังนี้
{%...%} สำหรับ ประกาศคำสั่ง
{{...}} สำหรับ ประกาศตัวแปร
{#...#} สำหรับ coment แต่ไม่แสดงที่ output
#...##  สำหรับ comment แสดงที่ output

เช่น
app.py
from flask import Flask, render_template
app = Flask(__name__)

@app.route('/result')
def result():
   dict = {'phy':50,'che':60,'maths':70}
   items = [{'name': 'Ice cream', 'price': 50},
                {'name': 'Cookie', 'price': 35},
                {'name': 'Chocolate', 'price': 40},
                {'name': 'Milk', 'price': 32.5}]
   return render_template('result.html', result = dict, items = items)

if __name__ == '__main__':
   app.run(host='127.0.0.1', port=8080,debug=True)
result.html
<!doctype html>
<html>
   <body>
   
      <table border = 1>
         {% for key, value in result.items() %}
         
            <tr>
               <th> {{ key }} </th>
               <td> {{ value }} </td>
            </tr>
         {% endfor %}
      </table>
        <hr>
         <ul>
           {% for item in items %}
              <li>{{item.name}} Price: {{item.price}}</li>
           {% endfor %}
         </ul>
      
   </body>
</html>
* result.iteritems() ใช้งานบน python3 ไม่ได้ ให้เปลี่ยนเป็น result.items()

จะแสดงผลได้แบบนี้


















Flask : เริ่มต้นกับ hello world

เมื่อติดตั้ง virtualenv และ flask เรียบร้อยแล้ว เริ่ม dev งานแรก
* อย่าลืมเรียกใช้ virtual environment => source venv/bin/activate
** ทดสอบบน Google App Engine จำเป็นต้องใช้งาน port 8080

1. ไปยัง directory ที่เราสร้างไว้ แล้วสร้างไฟล์ app.py  ref.
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
   return 'Hello World’

if __name__ == '__main__':
  app.run(host='127.0.0.1', port=8080,debug=True)

ทดสอบ run
python app.py

ตัวอย่าง output
(py3) yuunai_th@yuunai-app:~/test2$ python app.py
 * Running on http://127.0.0.1:8080/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 130-808-004
127.0.0.1 - - [15/Jul/2017 12:51:28] "GET /?authuser=1 HTTP/1.0" 200 -

กดที่ ไอคอน ซ้ายสุด เลือก preview on port 8080 เพื่อตรวจสอบโปรแกรม


2. ปรับแต่ง app.py

@app.route() 

เป็นตัวกำหนด path ที่ URL ว่าจะให้ไปเรียกใช้ function อะไร โดยวางบรรทัดก่อน function นั้น

เราสามารถ ส่งผ่าน variable มาจาก URL ได้ เช่น
@app.route('/hello/<string:name>')
def Home(name):
    return ("<h1>Hello %s!! </h1>" % name)

<string:name> string คือ conversion variable เพื่อให้อยู่ใน type ที่ต้องการ

url_for('function',parameter = value)

ใช้สำหรับ redirect ไปเรียกใช้งาน function ที่เคยประกาศไว้แล้ว เหมาะสำหรับ dynamic web page

return redirect(url_for('hello_guest',guest = name))

HTTP method จาก app.route()
ใช้เป็นตัวรับ method ว่าจะให้ response แบบไหน
login.html
<html>
   <body>
      
      <form action = "http://localhost:5000/login" method = "post">
         <p>Enter Name:</p>
         <p><input type = "text" name = "nm" /></p>
         <p><input type = "submit" value = "submit" /></p>
      </form>
      
   </body>
</html>
from flask import Flask, redirect, url_for, request
app = Flask(__name__)

@app.route('/success/<name>')
def success(name):
   return 'welcome %s' % name

@app.route('/login',methods = ['POST', 'GET'])
def login():
   if request.method == 'POST':
      user = request.form['nm']
      return redirect(url_for('success',name = user))
   else:
      user = request.args.get('nm')
      return redirect(url_for('success',name = user))

if __name__ == '__main__':
   app.run(debug = True)

เมื่อ login.html sumit form ส่งมายัง /login , app.route จะเช็กว่าเป็น method อะไรจาก
request.method 

จากตัวอย่างจะเห็นว่า ในการอ่านค่า variable

POST ใช้ request.form['nm'] 
GET ใช้ request.args.get('nm') 






เริ่มต้นกับ Flask

What is Web Framework?
Web Application Framework or simply Web Framework represents a collection of libraries and modules that enables a web application developer to write applications without having to bother about low-level details such as protocols, thread management etc.

What is Flask?
Flask is a web application framework written in Python. It is developed by Armin Ronacher, who leads an international group of Python enthusiasts named Pocco. Flask is based on the Werkzeug WSGI toolkit and Jinja2 template engine. Both are Pocco projects.

WSGI
Web Server Gateway Interface (WSGI) has been adopted as a standard for Python web application development. WSGI is a specification for a universal interface between the web server and the web applications.

Werkzeug
It is a WSGI toolkit, which implements requests, response objects, and other utility functions. This enables building a web framework on top of it. The Flask framework uses Werkzeug as one of its bases.

Jinga2
Jinga2 is a popular templating engine for Python. A web templating system combines a template with a certain data source to render dynamic web pages.

Flask is often referred to as a micro framework. It aims to keep the core of an application simple yet extensible. Flask does not have built-in abstraction layer for database handling, nor does it have form a validation support. Instead, Flask supports the extensions to add such functionality to the application. Some of the popular Flask extensions are discussed later in the tutorial.

1. ติดตั้ง virtualenv


pip install virtualenv

virtualenv (virtual environment) ช่วยในการแยก env ในการทำงาน กรณีมีหลายโปรเจคออกจาก เนื่องจากแต่ละโประจค มีการใช้ python version, library ,library version แตกต่างกันไป ref1  ref2

2. สร้าง project  (directory) และเริ่มใช้งาน env

mkdir newproj
cd newproj
virtualenv venv

venv คือ ชื่อเพื่อระบุ env ที่เราสร้าง
-p python3 คือ การระบุ python version ที่ต้องการใช้

เช่น
virtualenv -p python34 py34

เริ่มใช้งาน virtualenv
venv/bin/activate

3. ติดตั้ง Flask
pip install Flask
output จะได้ประมาณนี้
Collecting Flask
  Downloading Flask-0.10.1.tar.gz (544kB)
    100% |################################| 544kB 410kB/s
Collecting Werkzeug>=0.7 (from Flask)
  Downloading Werkzeug-0.11.4-py2.py3-none-any.whl (305kB)
    100% |################################| 307kB 531kB/s
Collecting Jinja2>=2.4 (from Flask)
  Downloading Jinja2-2.8-py2.py3-none-any.whl (263kB)
    100% |################################| 266kB 935kB/s
Collecting itsdangerous>=0.21 (from Flask)
  Downloading itsdangerous-0.24.tar.gz (46kB)
    100% |################################| 49kB 1.6MB/s
Collecting MarkupSafe (from Jinja2>=2.4->Flask)
  Downloading MarkupSafe-0.23.tar.gz
Installing collected packages: Werkzeug, MarkupSafe, Jinja2, itsdangerous, Flask
  Running setup.py install for MarkupSafe
  Running setup.py install for itsdangerous
  Running setup.py install for Flask
Successfully installed Flask-0.10.1 Jinja2-2.8 MarkupSafe-0.23 Werkzeug-0.11.4 itsdangerous-0.24

** ในการ Dev มีการใช้งาน package หลายตัว ซึ่งเวลาจะไป deploy ต้องสร้าง รายชื่อ package ที่ต้องการใช้งาน โดย เราสามารถใช้ คำสั่งนี้ เพื่อ รวม package list เพื่อ เตรียมไป deploy ได้
pip freeze > requirements.txt
ตัวอย่าง requirements.txt
click==6.7
Flask==0.12.2
itsdangerous==0.24
Jinja2==2.9.6
MarkupSafe==1.0
Werkzeug==0.12.2

และ เมื่ออยุ่บนระบบที่เราต้องการ deploy ให้ใช้ command
pip install -r requirements.txt



















































































14 มีนาคม 2560

MongoDB 


เปรียบเทียบ mysql ⇨ mongo

     table ⇨ collection
     row ⇨ document
     column ⇨ field

Install on Windows8.1


download from MongoDB

       เลือก window server 2008 R2 64-bit and later with SSL

      โหลดเสร็จ กดติดตั้งตามปกติ

configuration


      default path "C:\Program Files\MongoDB\Server\3.4"

      เพิ่มไฟล์ mongd.cfg ระบุ path ที่จัดเก็บ DB , log

systemLog:
    destination: file
    path: d:\mongoDB\data\log\mongod.log
storage:
    dbPath: d:\mongoDB\data\db

Start mongod
  

        เปิด command prompt (Winkey+R พิมพ์ cmd แล้วกด ctrl+shift+enter)

          cd C:\Program Files\MongoDB\Server\3.4\bin

          mongod --config "C:\Program Files\MongoDB\Server\3.4\mongd.cfg"
     

Test mongo


          เปิด command prompt (Winkey+R พิมพ์ cmd แล้วกด ctrl+shift+enter)

          cd C:\Program Files\MongoDB\Server\3.4\bin

           mongo

          จะเข้าสู่ mongo shell

Mongo Command

   insert    db.collectionName.save()
                example : db.test.save({name:'screen',last:'lap'})
                                 db.test.save([{name:'screen'},{name:'danai'}])

   query    db.collectionName.find()
                example : db.test.find()
                                 db.test.find({name:'screen'})
                                 db.test.find().pretty()

   update   db.collectionName.update()
                 example : db.test.update({name:'screen'},{$set:{age:30}})

   delete    db.collectionName.remove()      
                 example : db.test.remove({name:'screen'})
                                  db.test.remove({name:'screen'},{justonce:true})


mongodb GUI download robomongo


8 กุมภาพันธ์ 2560



kerberos

มันคืออะไร
    มันคือ เครื่องมือที่เอาไว้ใช้สำหรับ Authentication

แล้วใช้ที่มีติดมากับ os หรือ app ไม่ได้หรอ
    1. เพื่อให้ง่ายสำหรับการ manage password
    2. ป้องกันการโดนดักจับ packet ระหว่างทาง
    3. ทำ single sign-on

แล้วมันทำงานยังไงหละ
    ก่อนอื่น ต้องรู้จักแต่ละ components ก่อน ว่ามีอะไรบ้าง
        1. realm หรือ อาณาจักร เอาไว้ระบุว่ามีใครบ้างที่อยู่ใน realm เดียวกัน และ จะติดต่อกันได้เฉพาะภายใน realm เดียวกัน
        2. Key Distribute Center (KDC) เก็บ secret key ของทั้ง user , service
            (secret key คือ password ผสมกับ HASH โดยใช้ symmetric-key cryptography*)
             โดยใน KDC จะประกอบได้ 2 ส่วน คือ
                   - Autthentication server
                   - Ticket Granting Server



















         สมมุติให้เราต้องการที่จะใช้ http service 


              1. เราต้องยืนยันตัวตนกับ Authentication Server ก่อนเพื่อขอ Ticket จาก Ticket Granting Server ด้วย
                  คำสั่ง "kinit username" จากคำสั่งนี้ เพื่อขอ Ticket Granting Ticket (TGT) คือ
                    - name/ID
                    - requested service (Ticket Granting Server)
                    - IP address(es) หรือ null สำหรับ any ip
                    - requested lifetime TGT


                  Authentication จะตรวจสอบว่า user นี้มีตัวตนอยู่ KDC database หรือไม่ (no password checking)
                  ถ้าทุกอย่างถูกต้อง Authentication Server จะสร้าง Session key เพื่อใช้ติดต่อกับ TGS
                  โดยจะส่ง messages ออกมา 2 ฉบับ
                      1.1 Ticket Granting Server Session Key (TGSS)
                            Message ที่ถูก encrypted โดย Client Secret Key
                            - TGS name/ID
                            - Timestamp
                            - lifetime of TGT
                            - TGS Session key

                      1.2 Ticket Granting Ticket (TGT)
                            Message ที่ถูก encrypted โดย TGS secret key
                            - name/ID
                            - TGS name/ID
                            - Timestamp
                            - Source IP address(es)
                            - lifetime of TGT
                            - TGS Session key


           

               ระบบจะถาม password (Client Secret Key) เพื่อใช้สำหรับ decrypt TGSS ในข้อ 1.1



                    2. หลังจากยืนยันตัวเรียบร้อย client จะไปขอ ticket เพื่อให้สามารถใช้งาน HTTP Service
                        client จะสร้าง message ขึ้นมา 2 ฉบับ และ ส่งไปพร้อมกับ TGT ในข้อ 1.2 ส่งไปยัง TGS
                         2.1 Plained text Message
                               - Service name/ID
                               - lifetime for Service

                         2.2 Authenticator Message ที่ถูก encrypted โดย TGS Session Key
                               - name/ID
                               - Timestamp



                   Ticket Granting Server จะตรวจสอบว่า HTTP service นี้มีตัวตนอยู่ KDC database หรือไม่



                   Ticket Granting Server decrypt TGT (1.2) ด้วย TGS Secret Key และจะได้ TGS Session Key
                   เพื่อนำมา decrypt Authenticator (2.2) อีกทีนึง

                    เมื่อได้ข้อมูลครบถ้วน TGS จะเริ่มตรวจสอบข้อมูลทั้งหมดก่อนจะออก Ticket สำหรับ HTTP Service
                       - ยืนยันตัวตนจาก name/ID ที่ได้มาจาก TGT และ Authenticator
                       - เปรียบเทียบ Timestamp ที่ได้จาก TGT และ Authenticator (เวลาห่างกันไม่เกิน 2 นาที)
                       - ตรวจสอบว่า TGT expired หรือยัง
                       - ตรวจสอบว่ามี Authenticator ใน cache ของ TGS หรือไม่ เพื่อป้องกัน replay attack
                       - เปรียบเทียบ Source IP address(es) จาก TGT กับ client
                   


                  3. เมื่อทุกอย่างเรียบร้อย Ticket Granting Server จะสร้าง messages 2 ฉบับ ส่งกลับมายัง client
                         3.1 HTTP Service Session Key message ถูก encrypt โดยใช้ TGS Session Key
                             - HTTP service/ID
                             - Timestamp
                             - lifetime of ticket
                             - HTTP Service Session Key

                         3.2 Ticket for HTTP Service ถูก encrypt ด้วย HTTP Service Secret Key (TGS generate)
                             - name/ID
                             - HTTP service/ID
                             - Source IP address(es)
                             - Timestamp
                             - lifetime of ticket
                             - HTTP Service Session Key







                   4. Client ทำการ decrypt HTTP Service Session Key message (3.1)
                      โดยใช้ TGS Session Key ใน cache จาก 1.1 และสร้าง Authenticator เพื่อส่งต่อไปยัง HTTP Service

                          4.1 Authenticator message และ encypt ด้วย HTTP Service Session Key
                              - name/ID
                              - Timestamp
                     และส่ง message 2 ฉบับ ไปยัง HTTP Service

               



                     HTTP service ทำการ decrypt Ticket for HTTP Service (3.2) ด้วย HTTP Secret Key
                     จะได้ HTTP Session Key มาเพื่อนำไป decrypt Authenticator (4.1)
                     แล้วเริ่มขั้นตอนการตรวจสอบเหมือน TGS อีกรอบนึง

                       - ยืนยันตัวตนจาก name/ID ที่ได้มาจาก TGT และ Authenticator
                       - เปรียบเทียบ Timestamp ที่ได้จาก TGT และ Authenticator (เวลาห่างกันไม่เกิน 2 นาที)
                       - ตรวจสอบว่า TGT expired หรือยัง
                       - ตรวจสอบว่ามี Authenticator ใน cache ของ HTTP server หรือไม่ เพื่อป้องกัน replay attack
                       - เปรียบเทียบ Source IP address(es) จาก TGT กับ client

                      จากนั้น HTTP service จะสร้าง Authenticator message และ encrypt โดย HTTP Session Key
                          - name/ID
                          - Timestamp

                     เมื่อ Client ได้รับจะ decrypt โดยใช้ HTTP Session Key ที่อยู่ใน cache เพื่อยืนยันความถูกต้อง
                     และ สามารถเริ่มใช้ HTTP Service ได้จนกว่า Ticket จะ expired
     
                                                     




                 








สรุป
 1. Client --> AS : Check
                              kinit ไป Authentication Server เพื่อยืนยันว่ามีตัวตนจริง
     Client <-- AS : msg1 (Client Secret Key) TGT (TGS Secret Key)
                              ส่ง 2 msg โดยใช้ passwd สำหรับ decrypt  msg1 จะได้ Client Secret Key

 2. Client --> TGS : ส่ง Authenticator (TGS Session Key) และ TGT (TGS Secret Key)
     Client <-- TGS : msg1 (TGS Sesssion Key) msg2 (Service Secret Key)
                                ตรวจสอบ service  ว่ามีตัวตน และทำการ decrypt TGT ได้ TGS Session Key
                                decrypt Authenticator ด้วย TGS Session Key
                                ตรวจสอบ ข้อมูลที่ได้จาก TGT และ Authencator
                                ส่ง 2 encrypted msg

3. Client --> Service :ส่ง Authenticator (Service Session Key) และ msg2 (Service Secret Key)
                                   decrpyt msg แรก ด้วย TGS Session Key จะได้ Service Session Key
                                   ส่ง 2 encrypted msg
    Client <-- Service : Authenticator (Service Session Key)
                                   ทำการ decrypt msg2 ด้วย Service Secret Key จะได้ Service Session Key
                                  decrypt Authenticator ด้วย Service Session Key
                                   ตรวจสอบ ข้อมูลที่ได้จาก msg2 และ Authencator
                                   ส่ง 1 encrypted msg
4. Client decrypt Authenticator ด้วย Service Session Key และสามารถเริ่มใช้งาน Service ได้



*symmetric-key cryptography คือ การเข้ารหัสข้อมูลด้วยกุญแจเดี่ยว ทั้งผู้ส่งและผู้รับ โดยวิธีการนี้ผู้รับกับผู้ส่งต้องตกลงกันก่อนว่าจะใช้รูปแบบไหนในการเข้ารหัสข้อมูล
 asymmetric-key cryptography คือ การเข้ารหัสโดยใช้กุญแจคู่ทำการเข้ารหัสและถอดรหัส โดยกุญแจคู่ที่ว่านี้จะประกอบไปด้วย กุญแจส่วนตัว (private key) และกุญแจสาธารณะ (public key) โดยหลักการทำงานจะทำดังนี้ ถ้าใช้กุญแจลูกใดเข้ารหัส ก็ต้องใช้กุญแจอีกลูกหนึ่งถอดรหัส สำหรับการเข้ารหัสและถอดรหัสด้วยกุญแจคู่นี้จะใช้ฟังก์ชันทางคณิตศาสตร์เข้ามาช่วยโดยที่ฟังก์ชันทางคณิตศาสตร์ที่นำมาใช้ 
  credit : วิทยาการเข้ารหัสลับ

credit. http://www.roguelynn.com/words/explain-like-im-5-kerberos/