iT邦幫忙

2021 iThome 鐵人賽

DAY 19
0

講完前端之後,就一定要說到 Cookie 跟 Session 這兩個東西了,這兩個是什麼東西呢?又能幹什麼呢?

開始之前,首先要先說到 http 的無狀態特性。舉個例子,現在在逛網拍,想要把某個商品加入購物車,要入一遍帳號密碼;接著要送出訂單,又要輸入一遍帳號密碼;不管做什麼都需要輸入一遍帳號密碼,為什麼會這樣呢?因為 http 沒有紀錄狀態。為了解決這樣的問題,所以就出現了 Cookie 跟 Session。

Cookie

首先先來說 Cookie,Cookie 就像是麥X勞的甜心卡,上面有著資訊(買A送B),甚至會有期限(2021/12/31),並且由商家交給顧客自行留存,且只能在原商店使用。

http 中的 Cookie 也是這樣,有 key-value 、期限,同樣由 Server 送給瀏覽器保存,也只能在相同的 Domain(網域) 使用。優點當然是解決了 http 無狀態造成的問題;不過缺點則是 Cookie 有可能會被竄改,所以只適合紀錄一些不重要的數據(話說根據瀏覽器不同,有不同的大小及數量的限制。標準^[1]上是每個 Cookie 4096Bytes,最少每個 Domain 要可以有 20 個 Cookie)。

好了,大概了解完 Cookie 了,那麼就來看一下 Flask 中如何設置 Cookie 吧!讓我們用繼續使用前面的架構,新增幾個檔案:

ithome
├── static
│   └── logo.svg
├── templates
│   ├── res
│   │   ├── home.html
│   │   └── login.html  # 新增它
│   ├── base.html
│   ├── index.html
│   └── page_not_found.htmlindex.html
├── app.py
├── configs.py
├── Pipfile
└── Pipfile.lock

舉個例子,假設現在要做個登入後要在 Cookie 中設定 username 並回傳登入後頁面,可以這樣寫:

login.html

{% extends 'base.html' %}

{% block title %}
    template value
{% endblock %}

{% block img %}
    <img src={{ url_for('static', filename='logo.svg' ) }} />
{% endblock %}

{% block content %}
    <h1>Hello</h1>
    <form action={{ url_for('login') }} method="POST">
        <fieldset>
            <legend>Login</legend>
            <label>Username: </label>
            <input type="text" name="username" /><br />
            <label>Password: </label>
            <input type="password" name="password" /><br />
            <input type="submit" value="Login" />
        </fieldset>
    </form>
{% endblock %}

app.py

from flask import redirect, request, make_response, render_template


@app.route('/')
def index():
    return render_template('res/index.html')


@app.route('/home', methods=['GET'])
def home():
    if 'username' in request.cookies:
        user = request.cookies.get('username')
    else:
        user = None
    
    return render_template('res/home.html', username=user)


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':  # 輸入網址會進到這裡
        response = make_response(render_template('res/login.html'))
    elif request.method == 'POST':  # 表單送出後會到這裡
        account = request.values.get('username', None)
        # 驗證是否有這個使用者以及密碼是否正確,生出驗證結果 auth_result
        auth_result = 'success'  # 假設成功
        ''' 建立回應 '''
        if auth_result == 'success':  # 如果都正確
            response = make_response(redirect(url_for('home')))

            ''' 設定 Cookie '''
            response.set_cookie('username', account)
        else:  # 如果錯誤
            response = make_response(redirect(url_for('login')))
    else:
        response = make_response(redirect(url_for('index')))

    return response

如果實際跑一遍就會像這樣:

這樣就確定有放上去了。

Cookie 要包在回傳的東西裡面,在回傳給使用者的瀏覽器設,所以需要使用 Day 19 的 make_response 包進去後再回傳。

看到這裡應該已經會基礎的設定 Cookie 了吧,讓我們仔細的看一下還可以設定什麼,set_cookie 所有可以設定的參數如下:

set_cookie(key, value='', max_age=None, expires=None, path='/', domain=None, secure=False, httponly=False, samesite=None)
  1. key: Cookie 名稱。必填
  2. value: Cookie 值。
  3. max_age: 多久後過期(在夢中有人跟我說並不是全部的瀏覽器都支持,我也不知道)。
  4. expires: 什麼時候過期。跟上一個很像,但上一個是填秒數,過了多久後就過期;這個是填時間,什麼時候過期。
  5. path: 可以存取這個 Cookie 的路徑(路徑是啥?就是 url 扣掉網域剩下的)。
  6. domain: 可以存取這個 Cookie 的網域。
  7. secure: 如果為 True,Cookie 只會在 Https (有 s 時)時才會被傳送。
  8. httponly: 如果為 True,JavaScript 無法取得這個 Cookie。
  9. samesite: 可設為 Strict、Lax 和 None,最嚴格的傳輸 Cookie 到 最不嚴格的傳輸 Cookie。

如果現在多了一個頁面需要使用到 username,要從 Cookie 取得怎麼辦。讓我們在做一個頁面來實測一下。同樣是使用相同的架構,不過又新增了一個檔案:

ithome
├── static
│   └── logo.svg
├── templates
│   ├── res
│   │   ├── home.html
│   │   ├── index.html
│   │   ├── login.html
│   │   ├── page_not_found.html
│   │   └── settings.html  # 新增它
│   └── base.html
├── app.py
├── configs.py
├── Pipfile
└── Pipfile.lock

又舉個例子,假設現在要做個登入後使用者要可以有個人設定的頁面,可以這樣寫:

settings.html

{% extends 'base.html' %}

{% block title %}
    template value
{% endblock %}

{% block img %}
    <img src={{ url_for('static', filename='logo.svg' ) }} />
{% endblock %}

{% block content %}
    {% if username %}
        <h1>{{ username }}'s settings</h1>
    {% else %}
        <a href={{ url_for('index') }}><button>Index</button></a>
    {% endif %}
{% endblock %}
@app.route('/settings', methods=['GET'])
def settings():
    if 'username' in request.cookies:
        user = request.cookies.get('username')
    else:
        user = None
    
    return render_template('res/settings.html', username=user)

如果已經登入過了(因為這邊沒有設過期時間,所以 Cookie 會一直在,可以手動刪除它),Cookie 的 username 還在,那麼 URL 後面直接改成 settings 就可以跳過去了(雖然可以弄一個 link,直接點就過去了),這樣就可以抓到 Cookie 的值了。

如果要設定時間可以使用 datetime.datetime.now() + datetime.timedelta(<units>=<number>) 這個方式設定。

最後說一下如何刪除,Cookie 如果沒有設定時間就會是關閉瀏覽器的時候刪除,有設定時間不會是那個時間刪除,而是要重新設定一次過期的時間才會被刪除喔。

參考資料

^[RFC 2965 HTTP State Management Mechanism #5.3]RFC 2965 HTTP State Management Mechanism #5.3

那麼就大概這樣,今天本來打算一次講完 Cookie 跟 Session 的,但是發現講不完,所以下一篇再講 Session 吧。

大家掰~掰~


上一篇
Day 18 Flask 錯誤處理與回應
下一篇
Day 20 Flask Session
系列文
月光下的Flask之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言