---
# System prepended metadata

title: 網路編成-2

---

網路編成-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
```