---
# System prepended metadata

title: Python 系列： 建立簡單的伺服器
tags: [Blog]

---

伺服器(server)根據[維基百科定義](https://zh.wikipedia.org/zh-tw/%E6%9C%8D%E5%8A%A1%E5%99%A8#%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%88%86%E7%B1%BB%EF%BC%88%E8%BD%AF%E4%BB%B6%EF%BC%89)，為提供服務的電腦軟體，根據軟體種類可分為:
- 檔案伺服器: NAS儲存裝置...
- 網頁伺服器: Apache...
- 資料庫伺服器: MySQL, MongoDB...


建置正式的伺服器需要諸多考量和維護；如果想檢查網頁專案的目前成果，或簡單分享電腦資源給其他協作者的話，可以透過python建立臨時的網頁伺服器。

在指定目錄內包含`index.html`就能預覽網頁，若沒有則會顯示當前資料夾結構。以下使用 `http.server` 模組建立靜態網頁伺服器

## http.server 產生靜態伺服器
**注意:Python2 和 Python3 使用的模組不同，這裡介紹的為適用於Python3套件與使用方式**

### 下載並引入套件
```
pip install httpserver
import http.server
from http.server import SimpleHTTPRequestHandler, HTTPServer
```

還需多引入兩個物件: `SimpleHTTPRequestHandler` 和 `HTTPServer` 下面會簡單介紹他們的功能

### 最簡單的建置伺服器程式碼
```
## example code to create and run the server from python docs
def run(server_class=HTTPServer, handler_class=BaseHTTPRequestHandler):
     server_address = ('', 8000)
     httpd = server_class(server_address, handler_class)
     httpd.serve_forever()
```

### HTTPServer 和 SimpleHTTPRequestHandler
`HTTPServer` 為socketserver.TCPServer的子類別，能建立一個伺服器實例
還需要給個 `RequestHandlerClass` 向HTTP提出請求與回應，有以下三種: 
- BaseHTTPRequestHandler
- SimpleHTTPRequestHandler: 讀取與發佈當前目錄 (比較多人用此object) 
- CGIHTTPRequestHandler
最後給個協定版本(protocol)

```
HandlerClass = SimpleHTTPRequestHandler
ServerClass  = HTTPServer
Protocol     = "HTTP/1.0"
```


:star2: Handler 是什麼?
![image](https://hackmd.io/_uploads/HJxOqrw5C.png)
圖片引用自: https://blog.csdn.net/weixin_62079735/article/details/132797418


輸入伺服器相關資訊，包含ip位址，指定port(預設8000)等，並存為`httpd`變數
```
if sys.argv[1:]:
  port = int(sys.argv[1])
else:
  port = 8000
server_address = ('127.0.0.1', port)
HandlerClass.protocol_version = Protocol

## standard formula: ip + port + handlerclass
httpd = ServerClass(server_address, HandlerClass)
```

實體化與運作伺服器 `serve_forever()`可以透過 `shutdown()` 關閉
```
sa = httpd.socket.getsockname() ## ('127.0.0.1' 8000)
print ("Serving HTTP on", sa[0], "port", sa[1], "...")
httpd.serve_forever()
```

console 顯示 GET ... 200 代表請求成功
```
Serving HTTP on 127.0.0.1 port 8000 ...
127.0.0.1 - - "GET / HTTP/1.1" 200 -
```

最後打開瀏覽器，輸入 `http://127.0.0.1:8000/` 或是 `http://${your_ip}:8000/` 就可看到成果了


### 完整 co
```
# pip install httpserver
import sys
import http.server
from  http.server import SimpleHTTPRequestHandler, HTTPServer

HandlerClass = SimpleHTTPRequestHandler
ServerClass  = HTTPServer
Protocol     = "HTTP/1.0"

if sys.argv[1:]:
  port = int(sys.argv[1])
else:
  port = 8000
server_address = ('127.0.0.1', port)

HandlerClass.protocol_version = Protocol
httpd = ServerClass(server_address, HandlerClass)

sa = httpd.socket.getsockname() ## ('127.0.0.1' 8000)
print ("Serving HTTP on", sa[0], "port", sa[1], "...")
httpd.serve_forever()

## it will print 127.0.0.1 - - "GET / HTTP/1.1" 200 - on console
## <-> to shutdown()

```

### 觀看 html 效果
拿出之前寫的index.html 並放在指定路徑，透過 httpserver 產生 localhost 查看 html 網頁，若沒有 html 會顯示本機資料夾結構，可以透過這個方式與人分享網址，查看本機儲存的檔案內容


## 動態伺服器與網路框架
網頁包含動態資訊如資料庫串接，自定義的網頁跳轉等，可利用python `flask` 達成。此模組提供伺服器端的網頁框架，協助高效率架設網站。

### 下載與引入套件
這裡先引入最基本的 `Flask` 其他常用函數（`redirect, url_for, send_from_directory...`）會在後面介紹
```
#pip install Flask
from flask import Flask 
```

### 簡單的範例
- create `app.py` 
```
# save this as app.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello, World!"
    
if __name__ == '__main__':
    app.debug = True ## not shown in formal case
    app.run()
```
- 在 `app.py` 同層路徑下執行 `flask run` flask 會生成網頁於 http://127.0.0.1:5000/ 

### 名詞和概念筆記
- flask 內含有 httpserver，但僅適合測試用，正式發布會建議用 **WSGI server**
- 裝飾器(decorator): `@app.route` 做為路由，紀錄 URL/HTTP動作 與其對應的處理函式
    - `@app.route('/')` 這裡的 `/` 代表 http://127.0.0.1:5000/ 這個URL 
    - 若連結到此網址，會執行 hello() 函數並在網頁印出 Hello, World!
    - 若今天還有其他路由如下，打開 http://127.0.0.1:5000/**page/5** 就會看到函式執行結果
    ```
    app.route('/page/5')
    def welcome():
    return "Welcome"
    ```
- flask 可以吃URL變數，方法為 `<variable_name>`

### flask 函式 `url_for` `redirect`
`url_for()` 可以讓路由具有追蹤彈性，並搭配 `redirect()` 跳轉網頁
下面例子可知: http://127.0.0.1:5000/b 會被引導回 http://127.0.0.1:5000/ 
```
from flask import url_for, redirect
@app.route('/')
def hello():
    return 'hello'
    
@app.route('/b')
def b():
    return redirect(url_for('hello'))
```


其他套件如結合 html css 模板的`render_template`，或是上傳下載檔案的 `send_from_directory` 會在下篇介紹我的side project時補上～

## References
https://docs.python.org/3/library/http.server.html#
https://chwang12341.medium.com/coding%E8%B5%B7%E4%BE%86-python-%E4%B8%80%E8%A1%8C%E6%8C%87%E4%BB%A4%E5%B0%B1%E8%83%BD%E8%BC%95%E9%AC%86%E5%BB%BA%E7%AB%8B%E7%B6%B2%E9%A0%81%E4%BC%BA%E6%9C%8D%E5%99%A8-simplehttpserver%E5%A5%97%E4%BB%B6-http-server%E4%BD%BF%E7%94%A8%E6%95%99%E5%AD%B8-34c30b81c26
https://hackmd.io/@peterju/B18gmJ7Ph#15Flask-%E7%B6%B2%E9%A0%81%E6%A1%86%E6%9E%B6
https://medium.com/@charming_rust_oyster_221/%E4%BD%BF%E7%94%A8-flask-%E5%89%B5%E5%BB%BA-web-api-%E7%AD%86%E8%A8%98-b5618543632e
https://medium.com/@charming_rust_oyster_221/flask-%E6%AA%94%E6%A1%88%E4%B8%8A%E5%82%B3%E5%88%B0%E4%BC%BA%E6%9C%8D%E5%99%A8%E7%9A%84%E6%96%B9%E6%B3%95-1-c11097c23137
https://hackmd.io/@shaoeChen/HJiZtEngG/https%3A%2F%2Fhackmd.io%2Fs%2FSyP4YEnef