網路編成-2
===
### 簡單複習
- 四層
- 應用層: 傳什麼數據
- 傳輸層:
- 如何傳輸數據tcp/udp
- 可以理解為『快遞公司』
- 網絡層: IP, 可以理解為地理位置
- 鏈路層: 具體的傳輸工具
- Client & Server
- C: socket>bind>listen>accept>send>recv>close
- S: socket>connect>send>recv>close
- connect與accept返回的Socket進行通訊
- connect後開始執行三次握手
- Q. Code沒有三次握手, 那誰完成的呢?
- OS完成的, 其依照協議完成後封裝成Socket, 故我們使用Socket時會完成自動完成這些工作
- Q. 為何必須使用accept返回的socket來與client通訊?能自己寫嗎?
- 如果只是寫簡單的socket、bind是無法的, 還有許多os寫好的, 如三次揮手的數據(seq, ack)等
- close後開始四次揮手
- 應用層處理的事send<>recv這段, 當傳輸內容不僅是簡單的文檔時, 應按照特定協議來完成各式功能
- ftp: 傳輸文件
- ssh: 遠程連接
- smyp,pop3: 郵件
- Http
HTTP
---
### HTML: 整體網頁框架
`$ vi index.html`
```htmlembedded
<htm>
<head>
<title>包軌</title>
</head>
<body>
<h1>Toyz</h1>
<p>喊在</p>
</body>
</htm>
```
### CSS: 更改標籤的默認行為
### JavaScript: 腳本
- 與瀏覽器互動
- 過去JS因為解析器運行效率太低而快淘汰, 直到[V8引擎](https://zh.wikipedia.org/wiki/V8_(JavaScript%E5%BC%95%E6%93%8E))誕生
### 瀏覽器工作?
- 依照HTML語法將HTML文本顯示
- 各瀏覽器的區別在於其解析算法
- 主流核心
- IE 6/7/8/9/10
- FireFox(Chrome)
- Opera
- Safari
- Edge
- 不同核心支持的內容或顯示方式可能不同
- JavaScript解析器
- Socket
- 當我們打開瀏覽器進到一個網站, 意即瀏覽器是客戶端, 而socket,connect由瀏覽器完成
> \# Request Headers
>
> GET / HTTP/1.1
>- HTTP請求方式
> - GET: 請求數據
> - POST: 修改數據
> - PUT: 保存數據
> - DELETE: 刪除
> - OPTION: 詢問特性
> - HEAD: 返回報文頭
>- 請求方式(method)+路徑(path)+傳輸協定(protocol)
>- 第一行稱為起始行(請求行), 必備的一行
>
> Host: www.baidu.com
> Connection: keep-alive
> Upgrade-Insecure-Requests: 1
> User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
> Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
> Referer: http://baidu.com/
> Accept-Encoding: gzip, deflate, br
> Accept-Language: en-US,en;q=0.9,zh-TW;q=0.8,zh;q=0.7
> Cookie: ZD_ENTRY=google; BAIDUID=1319AEEE912E7B348E22D51093F80C28:FG=1; BIDUPSID=1319AEEE912E7B348E22D51093F80C28; PSTM=1561451361; delPer=0; BD_HOME=0; BD_UPN=123253; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; BDRCVFR[QxxZVyx49rf]=I67x6TjHwwYf0; BD_CK_SAM=1; PSINO=7; H_PS_PSSID=1459_21123_29135_29237_28518_29098_29131_28836_29220
>- 後面整段稱為[協議頭](https://zh.wikipedia.org/wiki/HTTP%E5%A4%B4%E5%AD%97%E6%AE%B5)(請求頭): 描述本次請求的相關訊息
>- 整個數據為一般的文本, 以\r\n換行
- Q. 傳資料(ex.圖片, 帳密)給服務器的運作方式?
> H_PS_PSSID=1459_21123_29135_29237_28518_29098_29131_28836_29220\r\n
> Content-Length: xxx\r\n
>> 傳數據時, 在協議頭會告訴服務器傳多少數據
> \r\n
> 01010100010
> password:
> - 在協議頭的後面以\r\n分開後傳輸資訊
> - 此處稱為請求體
### 服務器開發:
- recv<>send 之間
- 收到數據後處理發回
> HTTP/1.1 200 OK
>> [HTTP狀態碼](https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81)
>
> Bdpagetype: 1
> Bdqid: 0xa7bfeeaa003fc2a1
> Cache-Control: private
> Connection: Keep-Alive
> Content-Encoding: gzip
> Content-Type: text/html
> Cxy_all: baidu+dfde13153c0ed9b0cdfae51b54b6724a
> Date: Wed, 26 Jun 2019 03:05:44 GMT
> Expires: Wed, 26 Jun 2019 03:05:00 GMT
> Server: BWS/1.1
> Set-Cookie: delPer=0; path=/; domain=.baidu.com
> Set-Cookie: BDSVRTM=0; path=/
> Set-Cookie: BD_HOME=0; path=/
> Set-Cookie: H_PS_PSSID=1459_21123_29135_29237_28518_29098_29131_28836_29220; path=/; domain=.baidu.com
> Strict-Transport-Security: max-age=172800
> Vary: Accept-Encoding
> X-Ua-Compatible: IE=Edge,chrome=1
> Transfer-Encoding: chunked\r\n
> \r\n
> <html\>
> <title\><title\\>
> <html\\>
>> 服務器發回來也在\r\n之後(請求體)發送數據
>> 瀏覽器接收數據後顯示出來
- HTTP就是規定這些數據如何收發
- [URI](https://zh.wikipedia.org/wiki/%E7%BB%9F%E4%B8%80%E8%B5%84%E6%BA%90%E6%A0%87%E5%BF%97%E7%AC%A6)
- [URL](https://zh.wikipedia.org/wiki/%E7%BB%9F%E4%B8%80%E8%B5%84%E6%BA%90%E5%AE%9A%E4%BD%8D%E7%AC%A6)
- [URN](https://zh.wikipedia.org/wiki/%E7%BB%9F%E4%B8%80%E8%B5%84%E6%BA%90%E5%90%8D%E7%A7%B0)
- 當我搜尋時, 會返回搜索頁面, 由於**標準的GET不會有請求體**, 而此時搜索頁面的關鍵詞描述訊息會放到URL中
> https://www.baidu.com/s?&wd=64&ie=utf-8
>- https://www.baidu.com/s為搜索頁面
>- 後面用?來傳遞參數
>- Query String Parameters: &wd=64&ie=utf-8
>- 其他(ex.POST,PUT)除了用這方式,還可以用請求體傳送
- 查詢字符串QueryString
- ?起始
- &為下一個參數
- 格式: ?key1=value1&key2=value2
- 此str是有大小限制
- HTTP是[無狀態協議](https://zh.wikipedia.org/wiki/%E6%97%A0%E7%8A%B6%E6%80%81%E5%8D%8F%E8%AE%AE)
- HTTP舊版是短鏈接, 傳送一次即關閉
- 未來會往長鏈接發展, 但HTTP依舊是無狀態
- 有些APP也是使用HTTP協議
### 搜索引擎公司
#### 爬蟲
```sequence
爬蟲->Google: HTTP GET
Google-->爬蟲: Index.html
Note left of 爬蟲: 拿到頁面資源後開始解析後\n主動對頁面中的鏈結發送請求
爬蟲->A:HTTP GET
A-->爬蟲: xxx.html
Note left of 爬蟲: 不僅主動訪問, \n並會將頁面訊息存至數據庫中
```
## 靜態server
#### 構建架構
```python
# coding=utf-8
from socket import *
from multiprocessing import Process
def handleClient(newSocket):
request_data = newSocket.recv(1024)
print("request data:", request_data)
# newSocket.send(("HTTP/1.1 200 OK\r\n\r\nGodJJ").encode("utf-8"))
newSocket.send(bytes("HTTP/1.1 200 OK\r\n\r\nGodJJ", "utf-8"))
newSocket.close()
def main():
# AF_INET > 常量
# 通常使用大寫字母表示常量
s = socket(AF_INET, SOCK_STREAM)
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(("", 5566))
s.listen(5)
while True:
newSocket, cliInfo = s.accept()
# print("[%s,%s]連上了"%(cliInfo[0], cliInfo[1]))
print("[%s,%s]連上了"%cliInfo)
p = Process(target=handleClient, args=(newSocket,))
p.start()
newSocket.close()
if __name__ == "__main__":
main()
```
##### [常量](https://zh.wikibooks.org/zh-tw/Java/%E5%8F%98%E9%87%8F%E4%B8%8E%E5%B8%B8%E9%87%8F)
> 127.0.0.1:5566
>> HTTP/1.1 200 OK
>> Server: Toyz
>> GodJJ
>>
#### 解析request_data
```python
In [1]: s = "GET / HTTP/1.1\r\nHost: 127.0.0.1:5566\r\nCon"
In [2]: s.splitlines()
Out[2]: ['GET / HTTP/1.1', 'Host: 127.0.0.1:5566', 'Con']
In [1]: import re
In [2]: re.match(r"\w+ +/[^ ]* ", 'GET / HTTP/1.1')
Out[2]: <re.Match object; span=(0, 6), match='GET / '>
In [3]: re.match(r"\w+ +(/[^ ]*) ", 'GET / HTTP/1.1').group(1)
Out[3]: '/'
In [4]: re.match(r"\w+ +(/[^ ]*) ", 'GET /abc.py HTTP/1.1').group(1)
Out[4]: '/abc.py'
```
```python
# coding=utf-8
import re
from socket import *
from multiprocessing import Process
# 設置靜態文件根目錄
HTML_ROOT_DIR = "./html"
def handle_client(newSocket):
# 獲取客戶端請求數據
request_data = newSocket.recv(1024)
# print(request_data)
request_lines = request_data.splitlines()
# for line in request_lines:
# print(line)
# 解析請求報文
request_start_line = request_lines[0]
# 提取請求文件名
# 'GET / HTTP/1.1'
request_file_name = re.match(r"\w+ +(/[^ ]*) ", request_start_line.decode("utf-8")).group(1)
# if request_file_name == "/"
# 通常會反過來寫, 避免少寫一個=而沒發現
# ""不可變, 一個等號會有返回值, 此時解析器就會報錯而發現
# 如果是 if xx = "", xx僅為變量, 不會有報錯
# 所以判斷式通常會把變量寫到右邊
if "/" == request_file_name:
request_file_name = "/index.html"
try:
# 檔案不一定是純文檔, 用二進制開最保險
file = open(HTML_ROOT_DIR + request_file_name, "rb")
except IOError:
response_start_line = "HTTP/1.1 404 Not Found\r\n"
response_headers = "Server: My Server\r\n"
response_body = "The file is Not Found"
else:
file_data = file.read()
file.close()
response_start_line = "HTTP/1.1 200 OK\r\n"
response_headers = "Server: My Server\r\n"
response_body = file_data.decode("utf-8")
finally:
response_data = response_start_line + response_headers + "\r\n" + response_body
newSocket.send(bytes(response_data, "utf-8"))
newSocket.close()
def main():
s = socket(AF_INET, SOCK_STREAM)
# set socket option
# SOL > Socket option level
# REUSEADDR > re use addr
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(("", 5566))
s.listen(5)
while True:
newSocket, cliInfo = s.accept()
# print("[%s,%s]連上了"%(cliInfo[0], cliInfo[1]))
print("[%s,%s]連上了"%cliInfo)
p = Process(target=handle_client, args=(newSocket,))
p.start()
newSocket.close()
if __name__ == "__main__":
main()
```
#### 整理一下
```python
# coding=utf-8
import re
from socket import *
from multiprocessing import Process
# 設置靜態文件根目錄
HTML_ROOT_DIR = "./html"
class HTTPServer(object):
""""""
def __init__(self):
self.s = socket(AF_INET, SOCK_STREAM)
self.s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
def start(self):
self.s.listen(5)
while True:
newSocket, cliInfo = self.s.accept()
print("[%s,%s]連上了"%cliInfo)
p = Process(target=self.handle_client, args=(newSocket,))
p.start()
newSocket.close()
def handle_client(self ,newSocket):
# 獲取客戶端請求數據
request_data = newSocket.recv(1024)
request_lines = request_data.splitlines()
for line in request_lines:
print(line)
# 解析請求報文
request_start_line = request_lines[0]
# 提取請求文件名
request_file_name = re.match(r"\w+ +(/[^ ]*) ", request_start_line.decode("utf-8")).group(1)
if "/" == request_file_name:
request_file_name = "/index.html"
try:
file = open(HTML_ROOT_DIR + request_file_name, "rb")
except IOError:
response_start_line = "HTTP/1.1 404 Not Found\r\n"
response_headers = "Server: My Server\r\n"
response_body = "The file is Not Found"
else:
file_data = file.read()
file.close()
response_start_line = "HTTP/1.1 200 OK\r\n"
response_headers = "Server: My Server\r\n"
response_body = file_data.decode("utf-8")
finally:
response_data = response_start_line + response_headers + "\r\n" + response_body
newSocket.send(bytes(response_data, "utf-8"))
newSocket.close()
def bind(self, port):
self.s.bind(("", port))
def main():
http_server = HTTPServer()
http_server.bind(5566)
http_server.start()
if __name__ == "__main__":
main()
```
### 補充
- `open(xx, "rb")` 文本與二進制有何區別?
- 對於文本\r\n 為換行
- linux, unix > \n
- 新mac > \n, 舊mac > \r
- windows > \r\n
- 對於二進制, 純粹為\r\n
`CMD`
```python
>>> python
>>> f = open("wb", "wb")
>>> f.write(b"ha\nha")
5
>>> f.close()
>>>
>>> f = open("w", "w")
>>> f.write("ha\nha")
5
>>> f.close()
>>>
>>> f = open("wb", "rb")
>>> f.read()
b'ha\nha'
>>> f.close()
>>>
>>> f = open("w", "rb")
>>> f.read()
b'ha\r\nha'
>>> # 系統在解析文本時, 會自動轉換
>>>f.close()
```
- Terminal > CTRL Z: 把程序放到背景,而非殺死
```
$ python3 a.py
^Z
[1]+ Stopped python3 a.py
# 查看背景 bg
$ bg
[1]+ python3 a.py &
# 叫出來 fg
$ fg
python3 a.py
```
## 動態Server
### WSGI
- 需有一個同名函數或類能夠被調用, 以下用application說明
- class > `def __call__`
- 該對象應該產生一個響應值, 返回給服務器接收
- 服務器就可以返回給前端
- WSGI要求return只能傳響應體(body)
- 執行時, 需傳兩個參數 `application(environ, start_response)`
- environ
- 在執行application時, 可能需要用到客戶端傳回來的相關信息
- 故程序就需要有個變量來接收
- {}
- `def start_response(status_code, [("k1","v1"),("k2","v2"),...])`
- start_response是一個函數
- 由於return 只能傳 body, 而響應頭即在此函數處理
- start_response傳兩個參數
- status_code
- 200 404 ...
- [("K1", "V1"),("K2", "V2")]
- 響應頭可能會有好幾個,分別放入元組
```python
# 判斷
In [1]: a = "fajdlfksjd"
In [5]: a.endswith("jd")
Out[5]: True
In [6]: a.endswith("a")
Out[6]: False
In [7]: a.startswith("a")
Out[7]: False
In [8]: a.startswith("fa")
Out[8]: True
# 切片
In [2]: s = "/time.py"
In [3]: s[1:-3]
Out[3]: 'time'
# 搜索路徑
In [4]: import sys
In [5]: sys.path
Out[5]:
['/Library/Frameworks/Python.framework/Versions/3.7/bin',
...
'packages/IPython/extensions',
'/Users/haha/.ipython']
```
`vi server.py`
```python
# coding=utf-8
import re
import sys
from socket import *
from multiprocessing import Process
HTML_ROOT_DIR = "./html"
# WSGI路徑
WSGI_PYTHON_DIR = "./wsgipython"
class HTTPServer(object):
""""""
def __init__(self):
self.s = socket(AF_INET, SOCK_STREAM)
self.s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
def start(self):
self.s.listen(5)
while True:
newSocket, cliInfo = self.s.accept()
print("[%s,%s]連上了"%cliInfo)
p = Process(target=self.handle_client, args=(newSocket,))
p.start()
newSocket.close()
# 處理狀態碼跟響應頭, 需有兩個參數來接收
def start_response(self, status, headers):
"""從time.py接收了兩個東西
status = "200 OOK"
headers = [
("Content-Type", "text/plain")
]
"""
response_headers = "HTTP/1.1 " + status + "\r\n"
for header in headers:
response_headers += "%s: %s\r\n" % header
self.response_headers = response_headers
def handle_client(self ,newSocket):
request_data = newSocket.recv(1024)
request_lines = request_data.splitlines()
#for line in request_lines:
# print(line)
request_start_line = request_lines[0]
request_file_name = re.match(r"\w+ +(/[^ ]*) ", request_start_line.decode("utf-8")).group(1)
print(request_file_name)
# /ctime.py
if request_file_name.endswith(".py"):
# 導入模塊, 當模塊經常變換時,可以使用__import__(module_name)
# 使用切片取出模塊名[1:-3]
m = __import__(request_file_name[1:-3])
env = {}
response_body = m.application(env, self.start_response)
response_data = self.response_headers + "\r\n" + response_body
else:
if "/" == request_file_name:
request_file_name = "/index.html"
try:
file = open(HTML_ROOT_DIR + request_file_name, "rb")
except IOError:
response_start_line = "HTTP/1.1 404 Not Found\r\n"
response_headers = "Server: My Server\r\n"
response_body = "The file is Not Found"
else:
file_data = file.read()
file.close()
response_start_line = "HTTP/1.1 200 OK\r\n"
response_headers = "Server: My Server\r\n"
response_body = file_data.decode("utf-8")
response_data = response_start_line + response_headers + "\r\n" + response_body
print(response_data)
newSocket.send(bytes(response_data, "utf-8"))
newSocket.close()
def bind(self, port):
self.s.bind(("", port))
def main():
sys.path.insert(1, WSGI_PYTHON_DIR)
#print(sys.path)
http_server = HTTPServer()
http_server.bind(5566)
http_server.start()
if __name__ == "__main__":
main()
```
`mkdir wsgipython`
`vi ./wsgipython/ctime.py`
```python
#coding:utf-8
import time
def application(env, start_response):
# 請求有可能有參數, 此時就可以從env取得來處理
# env.get("METHOD")
# env.get("PATH_INFO)
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
start_response(status, headers)
return time.ctime()
```
#### 講解
> Q. 為何響應頭要在application裡面完成, 不能在服務器中完成嗎?
- 當然可以在服務器中完成,
- 但動態網頁的成功失敗或回傳的類型為何等, 通常是在appli中決定的
- 故邏輯上應該是在appli中告訴服務器stuts, type...等
> 流程
```sequence
note left of server.py: 接收cli請求
note left of server.py: accept()
note left of server.py: handle_cli()
note left of server.py: rq_data = recv()
note left of server.py: f_name = re.match()
note left of server.py: path = re.match()
note left of server.py: f_name.endswith(.py)
note left of server.py: m = __import__(m_name)
note left of server.py: env={}, def s(status,headers)
note right of ctime.py: def application(env, s)
note left of server.py: m.application(env, s)
server.py->ctime.py: env, s
note right of ctime.py: status = "200 OK"
note right of ctime.py: headers = [("K1","V1"),(K2,V2)]
note right of ctime.py: s(status, headers)
ctime.py->server.py: s,h
note left of server.py: def s(status, headers)
note left of server.py: self.respon = s+h
server.py->ctime.py:
note right of ctime.py: return time.ctime()
ctime.py->server.py: ctime()
note left of server.py: body = m.appli(env,s)
note left of server.py: send(self.respon+body)
```
## 框架
` vi framework.py`
```python
#coding:utf-8
import time
from WebServer import HTTPServer
HTML_ROOT_DIR = "./html"
# 思考: 框架是要給別人使用,
# 要如何讓別人在不變動框架的前提下傳入urls> class
class application(object):
"""框架核心"""
def __init__(self, urls):
self.urls = urls
# 讓類可以被調用,
# 調用時, 相當於調用__call__
def __call__(self, env, start_response):
path = env.get("PATH_INFO", "/") # 有傳就取, 沒傳默認/
# 框架規定靜態語言以/static/ 開頭 > 與urls區分
# /ststic/index.html
if path.startswith("/static/"):
request_file_name = path[7:]
try:
file = open(HTML_ROOT_DIR + request_file_name, "rb")
except IOError:
status = "404 Not Found"
headers = []
start_response(status, headers)
return "Not Found"
else:
file_data = file.read()
file.close()
status = "200 OK"
headers = []
start_response(status, headers)
return file_data.decode("utf-8")
for url, handler in self.urls:
# ("/ctime", handle_ctime)
if path == url:
return handler(env, start_response)
status = "404 Not Found"
headers = []
start_response(status, headers)
return "Not Found"
def handle_ctime(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
start_response(status, headers)
return time.ctime()
def handle_haha(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
start_response(status, headers)
return "haha"
def main():
urls = [
("/", handle_ctime),
("/ctime", handle_ctime),
("/haha", handle_haha)
]
app = application(urls)
http_server = HTTPServer(app)
http_server.bind(5566)
http_server.start()
if __name__ == "__main__":
main()
```
`vi WebServer`
```python
# coding=utf-8
import re
import sys
from socket import *
from multiprocessing import Process
HTML_ROOT_DIR = "./html"
# WSGI路徑
WSGI_PYTHON_DIR = "./wsgipython"
class HTTPServer(object):
""""""
def __init__(self, application):
"""application指向框架app"""
self.s = socket(AF_INET, SOCK_STREAM)
self.s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
self.app = application
def start(self):
self.s.listen(5)
while True:
newSocket, cliInfo = self.s.accept()
print("[%s,%s]連上了"%cliInfo)
p = Process(target=self.handle_client, args=(newSocket,))
p.start()
newSocket.close()
# 處理狀態碼跟響應頭, 需有兩個參數來接收
def start_response(self, status, headers):
response_headers = "HTTP/1.1 " + status + "\r\n"
for header in headers:
response_headers += "%s: %s\r\n" % header
self.response_headers = response_headers
def handle_client(self ,newSocket):
request_data = newSocket.recv(1024)
request_lines = request_data.splitlines()
request_start_line = request_lines[0]
request_file_name = re.match(r"\w+ +(/[^ ]*) ", request_start_line.decode("utf-8")).group(1)
print(request_file_name)
env = {
"PATH_INFO": request_file_name,
}
response_body = self.app(env, self.start_response)
response_data = self.response_headers + "\r\n" + response_body
print(response_data)
newSocket.send(bytes(response_data, "utf-8"))
newSocket.close()
def bind(self, port):
self.s.bind(("", port))
```
```python
# [] vs get()
In [1]: a = {"a":2, "b":4}
# 如果{}裡面沒有相應K, get()不會報錯
In [3]: a.get("a")
Out[3]: 2
In [4]: a.get("c")
# {} 沒有相應K, []會報錯
In [5]: a["a"]
Out[5]: 2
In [6]: a["c"]
KeyError: 'c'
# get()默認值
# 如果找無k, 默認cc
In [8]: a.get("c", "cc")
Out[8]: 'cc'
In [9]: a.get("a", "cc")
Out[9]: 2
```
> - 一般來說應該是WebServer去對接框架. 亦即, WebServer導入framework, 以WebServer啟動
> - 惟, 以導入的方式不方便, 因為如果要對接別的框架, 那還要再改服務器代碼
> - 需求: 直接在命令行中指定對接
> - 即$ python3 Webserver.py xx:xxx
#### 說明
```python
import sys
print(sys.argv)
```
```
$ python3 arg.py a b c
['arg.py', 'a', 'b', 'c']
# 可知如果要取a 應該是sys.argv[1] , 以此類推
```
```python
# split
In [1]: a = "kkbox:pikachu"
In [2]: a.split(":")
Out[2]: ['kkbox', 'pikachu']
In [3]: b,c = a.split(":")
In [4]: b
Out[4]: 'kkbox'
In [5]: c
Out[5]: 'pikachu'
# getattr
In [1]: cat test1.py
class xxx(object):
def __call__(self):
return "xxx"
In [2]: import test1
In [3]: c = getattr(test1, "xxx")
In [4]: c
Out[4]: test1.xxx
In [5]: d = c()
In [10]: c()
Out[10]: <test1.xxx at 0x10bceec50>
In [11]: d()
Out[11]: 'xxx'
```
`$ vi WebServer.py`
```python3
# coding=utf-8
import re
import sys
from socket import *
from multiprocessing import Process
class HTTPServer(object):
""""""
def __init__(self, application):
"""application指向框架app"""
self.s = socket(AF_INET, SOCK_STREAM)
self.s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
self.app = application
def start(self):
self.s.listen(128)
while True:
newSocket, cliInfo = self.s.accept()
print("[%s,%s]連上了"%cliInfo)
p = Process(target=self.handle_client, args=(newSocket,))
p.start()
newSocket.close()
# 處理狀態碼跟響應頭, 需有兩個參數來接收
def start_response(self, status, headers):
response_headers = "HTTP/1.1 " + status + "\r\n"
for header in headers:
response_headers += "%s: %s\r\n" % header
self.response_headers = response_headers
def handle_client(self ,newSocket):
request_data = newSocket.recv(1024)
request_lines = request_data.splitlines()
request_start_line = request_lines[0]
request_file_name = re.match(r"\w+ +(/[^ ]*) ", request_start_line.decode("utf-8")).group(1)
print(request_file_name)
env = {
"PATH_INFO": request_file_name,
}
response_body = self.app(env, self.start_response)
response_data = self.response_headers + "\r\n" + response_body
print(response_data)
newSocket.send(bytes(response_data, "utf-8"))
newSocket.close()
def bind(self, port):
self.s.bind(("", port))
def main():
# $ python3 WebServer.py FrameWork:app
# IndexError: list index out of range
if len(sys.argv) < 2:
sys.exit("python3 WebServer.py FrameWork:app")
module_name, app_name = sys.argv[1].split(":")
# module_name = "FrameWork"
# app_name = "app"
m = __import__(module_name)
# m.app_name()? > FrameWork沒有app_name這東西
app = getattr(m, app_name) # "m.application"
http_server = HTTPServer(app)
http_server.bind(5566)
http_server.start()
if __name__ == "__main__":
main()
```
`$ vi FrameWork.py`
```python
#coding:utf-8
import time
HTML_ROOT_DIR = "./html"
# 思考: 框架是要給別人使用, 要如何讓別人在不變動框架的前提下傳入data> class
class application(object):
"""框架核心"""
def __init__(self, urls):
self.urls = urls
# 讓類可以被調用,
# 調用時, 相當於調用了類中的__call__
def __call__(self, env, start_response):
path = env.get("PATH_INFO", "/") # 有傳就取, 沒傳默認/
# 框架規定靜態語言以/static/ 開頭
# /ststic/index.html
if path.startswith("/static/"):
request_file_name = path[7:]
try:
file = open(HTML_ROOT_DIR + request_file_name, "rb")
except IOError:
status = "404 Not Found"
headers = []
start_response(status, headers)
return "Not Found"
else:
file_data = file.read()
file.close()
status = "200 OK"
headers = []
start_response(status, headers)
return file_data.decode("utf-8")
for url, handler in self.urls:
# ("/ctime", handle_ctime)
if path == url:
return handler(env, start_response)
status = "404 Not Found"
headers = []
start_response(status, headers)
return "Not Found"
def handle_ctime(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
start_response(status, headers)
return time.ctime()
def handle_haha(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
start_response(status, headers)
return "haha"
urls = [
("/", handle_ctime),
("/ctime", handle_ctime),
("/haha", handle_haha)
]
app = application(urls)
```
```$
$ python3 WebServer.py
python3 WebServer.py FrameWork:app
$ python3 WebServer.py FrameWork:app
[127.0.0.1,60784]連上了
[127.0.0.1,60785]連上了
/static/
HTTP/1.1 404 Not Found
Not Found
[127.0.0.1,60814]連上了
/
HTTP/1.1 200 OK
Content-Type: text/plain
Thu Jul 18 13:18:44 2019
```