# 自主學習: Python Flask 與 Web
## 序和介紹
### 哈囉各位(偷偷給自己打個廣告)
* Insgram: @[chao28661](https://www.instagram.com/chao28661/)
* 我ㄉ自介: https://ncves.com/
* contact me with email: peer@ncves.net
#### 當前職況
* 中部高中電資聯合會議-資訊組
> !!插個小廣告!!
> 目前正在招收第三屆成員,如果對電資有興趣歡迎在6/17前和我聯絡([ig](https://www.instagram.com/chao28661/)或[email](mailto:peer@ncves.net)都可以),我會幫你引薦~~
* 今年寒訓ㄉ教學組兼攝影
* 今年7/2幹訓ㄉ資訊安全講師兼攝影
* RIPE(歐洲網際網路資源協調中心)ASN [AS199331](https://irrexplorer.nlnog.net/asn/AS199331)
* 某網路訓練中心創辦人
#### 擅長區塊
* 資訊安全
* Python腳本開發
* 網頁編寫、美化
* Linux作業系統操作
* 遊戲伺服器搭建(Minectaft、FiveM)
* 網站、網域部屬
* 網路BGP
## 簡報前言
動機與目的:
1. 提升程式語言編寫能力,掌握前後端開發
內容摘要:
1. 前言
1. 環境架設
1. 功能測試
所遇挑戰與克服過程
1. 一開始在處理POST回傳值時一直跳==405 Method Not Allowd==,處理很久後才發現是html的action沒設定好,將它設定好後再運行一次就正常ㄌ
反思與新的學習及收穫
1. 這次最大的收穫就是掌握了後端開發的領域,未來想轉向用JavaScript開發後端
### 為什麼要學習前後端資料互傳?
> Q: 前後端資料互傳很重要?
>
> A: 是,而且是非常重要
>
> 舉個例子:
> 平時逛購物網站、IG都要登入自己的帳號,那後端系統是如何驗證你輸入的帳號密碼是否正確?
讓我們看下流程圖

在這張圖中,最左邊是我們一般使用者
假設他現在想登入某購物網站
他在登入頁面填上的帳密為admin, 1234
他輸入的資料將會以==POST==的==封包==傳遞方式傳給後端伺服器
此時後端伺服器會將他輸入的值拿去和資料庫比對
這時會有2種狀況:
1. 比對相符(成功登入)
1. 比對不符合(登入失敗)
## 環境搭建
必需安裝的東西有以下:
1. Python
1. Python Flask
### 安裝Python

前往Python官網下載 https://www.python.org/downloads/
其實這邊除了用瀏覽器到官網下載,還有另一種酷酷的安裝方法:
> 這邊先介紹一個酷東東
> 
> 這是一個內建在Windows預設的工具,無須另外下載,直接搜尋即可
> 這是我們俗稱的PowerShell,他類似==Linux==的==Terminal==,他擁有豐富的==指令介面(CLI)==
打開後輸入:
```
winget install python
```
就會開始自動下載ㄌ
以下這些是一些其他用法

### 安裝Flask
打開PowerShell後應該會看到以下介面

接著我們輸入
```
pip install flask
```
會跑出類似這樣的東東(我的已安裝過)

### 搭建資料庫
這次是使用一款輕量級的資料庫軟體叫做sqlite3
這款軟體也是內建ㄉ,不須而外下載
我們可以透過一個python檔案快速完成布署這個資料庫方便後續使用
```python=
import sqlite3
connect = sqlite3.connect("database.db")
sql = connect.cursor()
sql.execute('CREATE TABLE IF NOT EXISTS database (USERNAME TEXT, PASSWORD TEXT)')
connect.commit()
connect.close()
```
### 其他好用ㄉ軟體
1. [Visual Studio Code](https://code.visualstudio.com/Download) - 方便程式碼撰寫
3. [SQLiteBrowser](https://sqlitebrowser.org/dl/) - 方便觀察資料庫
## 開始編寫
### 前言
前言: 對於了解這部分的人應該會有些疑問:
> Q1: 比起使用Python撰寫後端邏輯,為何不使用JavaScript或php等等更適合的語言?
>
> A1: 問這個問題前,可以先往上看看我擅長的程式語言,在眾多的程式語言中,Python是我最拿手ㄉ。
>
> Q2: Python Flask在寫後端邏輯上方便嗎?
>
> A2: 要回答這個問題前,我們先來了解甚麼是Jinja。
其實Jinja就是一套為了寫Python後端邏輯而開發的模板套件,他的出現解決了很多繁瑣的問題,也因為他的出現,現在很多人選擇使用Python Flask作為開發後端的語言。([有興趣點我查看更多](https://jinja.palletsprojects.com/en/3.1.x/))
順帶一提Python Flask是一套輕框架,啟動他的環境僅僅需要以下7行程式碼
```python=
from flask import Flask
app = Flask(__name__)
@app.route("/index")
def func():
return "Hello World"
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=True)
```
### 基礎網概
各位是否有個疑慮,在上面的最後一段程式碼中==host==、==port==代表什麼?
==host==: 這邊輸入的127.0.0.1代表什麼意思呢?
> A: 在IPv4協定中,有一些被稱為保留段的IP,常聽到的有192.168.XXX.XXX或0.0.0.0等等,這邊輸入的127.0.0.1也是一段保留段,他代表的是當前本機
==port==: 我們先看張url定位的規則

這邊的端口就是port的意思。如果要簡單比喻這個協定的話,我們可以把scaict.org這個網域想像成一間大飯店,這間"大"飯店非常大有1~65535間房間,每間都可以出租給客人(將每間想成會開服務)
回到port這個協定,我們只要在網域後面加上:XXXX就可以進入對應的服務
有一些為==TCP/UDP==系統預設占用端口,可以在這裡查看
https://zh.wikipedia.org/zh-tw/TCP/UDP%E7%AB%AF%E5%8F%A3%E5%88%97%E8%A1%A8
> 一些常見的有80(http)、443(https)、20/21(ftp)、22(ssh)
### 導入套件
開始寫任何程式碼的第一步想必都是導入相關套件
這次我們寫的是Python Flask,因此第一步就會是導入相關套件
```python=
from flask import Flask
from flask import request
from flask import render_template
from flask import render_template_string
import sqlite3
app = Flask(__name__)
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=True)
```
> 如果覺得1~4行這樣寫太佔行數的話,可以改成以下:
```python=
from flask import *
```
### 網頁框架
套件導入完後讓我們接著繼續寫
接下來就是要寫網頁的框架,他看起來會像這樣
```python=
from flask import Flask
from flask import request
from flask import render_template
from flask import render_template_string
import sqlite3
app = Flask(__name__)
#設定網頁路由
@app.route("/create")
def create():
return "Hello World"
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=True)
```
### 連線資料庫
我們這次要寫的目標很明確,就是一套登入系統,說到登入系統,資料庫是必備的吧!
```python=
from flask import Flask
from flask import request
from flask import render_template
from flask import render_template_string
import sqlite3
app = Flask(__name__)
#設定網頁路由
@app.route("/create")
def create():
#連線資料庫
connect = sqlite3.connect('database.db')
sql = connect.cursor()
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=True)
```
### 前端門面
接下來我們要讓網頁能出現輸入的表單
```python=
from flask import Flask
from flask import request
from flask import render_template
from flask import render_template_string
import sqlite3
app = Flask(__name__)
#設定網頁路由
@app.route("/create")
def create():
#連線資料庫
connect = sqlite3.connect('database.db')
sql = connect.cursor()
#若傳輸協定為POST(網路概論內容),則觸發下面判斷式
if request.method == 'POST':
return "Hello World"
else:
#將表單回傳到網頁上
return'''
<form method="post" action="/create">
<p>創建帳號</p>
<input type="text" name="username">
<p>創建密碼</p>
<input type="text" name="password">
<input type="submit" name="submit" value="創建帳號">
</form>
'''
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=True)
```
寫完以上code後,我們連線到 127.0.0.1:5000/create 會看到以下東西

### 後端邏輯
現在大致的門面(前端)都好了,讓我們來準備後端吧!
接下來的後端都要寫在判斷式為==POST==的裡面
根據邏輯,我們的目標就是要拿到使用者輸入的資料,但是不見得每個使用者都會按照我們開發時的原意去做,因此我們可以寫個python中的例外處理避免這類問題發生
```python=20
#嘗試接收網頁回傳資料
try:
username = request.values["username"]
password = request.values["password"]
#若沒成功接收回傳"請輸入帳密"
except:
return render_template_string("請輸入帳密")
```
最後成功收到回傳值後,我們就可以將他寫入資料庫啦~~
```python=30
#成功接收執行以下敘述
else:
#判斷回傳資料是否為空字串
if len(username) == 0:
return render_template_string("帳號請勿留白")
if len(password) == 0:
return render_template_string("密碼請勿留白")
if len(username) and len(password) == 0:
return render_template_string("帳號密碼請勿留白")
#成功通過前面檢查則將回傳資料寫進資料庫
sql.execute(f"INSERT INTO database (USERNAME, PASSWORD) VALUES ('{username}', '{password}')")
connect.commit()
#回傳資串"帳號創建完畢"
return render_template_string("帳號創建完畢!")
```
這邊就展示創建帳號,登入帳號原理和創建帳號其實差不多XD
下面有源碼可以自行觀看~
## 程式碼最終型態
### main .py
```python=
from flask import Flask
from flask import request
from flask import render_template
from flask import render_template_string
import sqlite3
app = Flask(__name__)
#設定網頁路由
@app.route("/create", methods = ['GET', 'POST'])
def create():
#連線資料庫
connect = sqlite3.connect('database.db')
sql = connect.cursor()
#若傳輸協定為POST(網路概論內容),則觸發下面判斷式
if request.method == 'POST':
#嘗試接收網頁回傳資料
try:
username = request.values["username"]
password = request.values["password"]
#若沒成功接收回傳"請輸入帳密"
except:
return render_template_string("請輸入帳密")
#成功接收執行以下敘述
else:
#判斷回傳資料是否為空字串
if len(username) == 0:
return render_template_string("帳號請勿留白")
if len(password) == 0:
return render_template_string("密碼請勿留白")
if len(username) and len(password) == 0:
return render_template_string("帳號密碼請勿留白")
#成功通過前面檢查則將回傳資料寫進資料庫
sql.execute(f"INSERT INTO database (USERNAME, PASSWORD) VALUES ('{username}', '{password}')")
connect.commit()
#回傳資串"帳號創建完畢"
return render_template_string("帳號創建完畢!")
else:
#將表單回傳到網頁上
return'''
<form method="post" action="/create">
<p>創建帳號</p>
<input type="text" name="username">
<p>創建密碼</p>
<input type="text" name="password">
<input type="submit" name="submit" value="創建帳號">
</form>
'''
#設定網頁路由
@app.route("/login", methods = ['GET', 'POST'])
def login():
#連線資料庫
connect = sqlite3.connect('database.db')
sql = connect.cursor()
#若傳輸協定為POST(網路概論內容),則觸發下面判斷式
if request.method == 'POST':
#嘗試接收網頁回傳資料
try:
username = request.values["username"]
password = request.values["password"]
#若沒成功接收回傳"請輸入帳密"
except:
return render_template_string("請輸入帳密")
#成功接收執行以下敘述
else:
#判斷回傳資料是否為空字串
if len(username) == 0:
return render_template_string("帳號請勿留白")
if len(password) == 0:
return render_template_string("密碼請勿留白")
if len(username) and len(password) == 0:
return render_template_string("帳號密碼請勿留白")
#驗證輸入是否等於資料庫存在的資料
user = sql.execute(f"SELECT USERNAME FROM database WHERE USERNAME = '{username}' AND PASSWORD = '{password}'")
#驗證資料庫中是否存在該項
if user.fetchone() is None:
return render_template_string("請輸入有效帳號密碼")
#調出使用者輸入的帳號名稱
user_name = user.fetchone()
#導向welcome.html且在welcome.html上的username變數顯示使用者登入時輸入的帳號
return render_template("welcome.html", username = user_name)
return'''
<form method="post" action="/login">
<p>登入帳號</p>
<input type="text" name="username">
<p>登入密碼</p>
<input type="text" name="password">
<input type="submit" name="submit" value="登入">
</form>'''
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=True)
```
### templates/welcome.html
```html=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登入</title>
</head>
<body>
<p>Hello,</p>
<p>你已成功以 {{ username }} 登入</p>
</body>
</html>
```
## 功能測試
### 開始運作
使用==Terminal==或其他東西讓他跑起來~~
(這邊使用Visual Studio Code內建的Terminal)

啟動後我們可以連上 127.0.0.1:5000/
### 創建帳號
連上 127.0.0.1:5000/create
進入頁面後,填入想創建的帳號及密碼

按下創建帳號後,我們前往資料庫查看會發現以下:

看到這列代表程式已成功將資料寫進資料庫了
這樣就創建完ㄌㄡ~
### 登入帳號
連上 127.0.0.1:5000/login
我們利用剛剛創建的那組帳密來嘗試登入看看!

按下登入後我們可以發現頁面跳到成功登入了

剛才是成功登入,我們接下來試試看將帳號密碼輸入錯誤會怎樣
我們故意輸入錯誤的密碼

按下登入後發現他回傳了登入失敗的訊息

## 結束~~
感謝各位聆聽
(偷偷告訴你,這是github開源專案,快到這裡查看吧!!)
[點我傳送](https://github.com/Charlie28661/Password-validation)
