Web 課上半場(chǎng)

HTTP, Socket, TCP/IP

HTTP 由 HeaderBody 兩部分組成,發(fā)送 HTTP 請(qǐng)求(Request)的叫客戶(hù)端,接受到 HTTP 請(qǐng)求并返回信息(Response)的叫服務(wù)器?,F(xiàn)時(shí)流行的 HTTP 協(xié)議版本是 1.1,當(dāng)然也有用 HTTP 2 的,不表。最常用的兩種 method 是 GETPOST。PUT現(xiàn)在也會(huì)被提到不少。一般的 HTTP 頭是這樣的:

GET / HTTP/1.1
Host: vip.cocode.cc
Connection: close
Content-Type: text/html

GET表示我們所用的方式,/login表示我們?cè)讷@取這個(gè)網(wǎng)站根目錄下的 login 的數(shù)據(jù),HTTP/1.1表示所用的 HTTP 協(xié)議。


當(dāng)然,我們寫(xiě)的時(shí)候?yàn)榱丝招袝?huì)這樣寫(xiě):

  • http_client.py
import socket

# 創(chuàng)建 Socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 建立連接
s.connect(('vip.cocode.cc', 80))

# 發(fā)起 HTTP 請(qǐng)求
s.send(b'GET /login HTTP/1.1\r\nHost:vip.cocode.cc\r\nConnection:close\r\nContent-Type:text/html\r\n\r\n')

# 接收數(shù)據(jù)
buffer = []
while True:
    d = s.recv(1024)
    if d:
        buffer.append(d)
    else:
        break
data = b''.join(buffer)
s.close()
print(data)

Socket 是一個(gè)高大上名詞,具體這里不解釋。創(chuàng)建 Socket 是一個(gè)套路,第一個(gè)參數(shù)socket.AF_INET代表著在這里我們是用 IPv4 模式,而第二個(gè)參數(shù)socket.SOCK_STREAM意思是這里我們用 TCP 協(xié)議。
建立連接,給s.connect傳入一個(gè)tuple,分別是 address 和 port 兩個(gè)參數(shù),一般都是 80,因?yàn)?HTTP 默認(rèn)就是 80,套路。
建立連接后,我們就向 server 發(fā)起 HTTP 請(qǐng)求,要注意\r\n\r\n\r\n,規(guī)定的套路,如果不按照這個(gè)來(lái),這就不是一個(gè)合規(guī)的 HTTP 請(qǐng)求,會(huì)導(dǎo)致你無(wú)法獲得你想要的首頁(yè)內(nèi)容。如果沒(méi)問(wèn)題,我們就可以接收服務(wù)器返回的數(shù)據(jù)了。
接收數(shù)據(jù)的這段代碼的意思是,s.recv(1024)每次最多接受 1024 字節(jié)的數(shù)據(jù),然后嵌套在一個(gè)while循環(huán)內(nèi),當(dāng)s.recv()返回空數(shù)據(jù),證明數(shù)據(jù)都被接收過(guò)來(lái)了,這時(shí)候就可以結(jié)束循環(huán)。
s.close()用作關(guān)閉 socket ,和服務(wù)器的一次通信就此結(jié)束。
最后,返回的數(shù)據(jù)是這樣的:

b'HTTP/1.1 200 OK\r\nDate: Fri, 01 Jul 2016 04:58:34 GMT\r\nServer: Apache/2.4.7 (Ubuntu)\r\nContent-Length: 1181\r\nVary: Accept-Encoding\r\nConnection: close\r\nContent-Type: text/html; charset=utf-8\r\n\r\n<!DOCTYPE html>\n<html lang="en">\n<head>\n    <meta charset="UTF-8">\n    <title>\xe7\x99\xbb\xe5\xbd\x95\xe9\xa1\xb5\xe9\x9d\xa2</title>\n    <link rel="stylesheet" >\n</head>\n<body>\n    <ul class=flashes>\n    \n    <h1>\xe7\x99\xbb\xe5\xbd\x95</h1>\n    <form class="pure-form" action="/login" method="POST">\n        <input name="username" class="form-control" placeholder="\xe8\xbe\x93\xe5\x85\xa5\xe7\x94\xa8\xe6\x88\xb7\xe5\x90\x8d" />\n        <br>\n        <input name="password" class="form-control" placeholder="\xe8\xbe\x93\xe5\x85\xa5\xe5\xaf\x86\xe7\xa0\x81" />\n        <br>\n        <button class="pure-button pure-button-primary" type="submit">\xe7\x99\xbb\xe5\xbd\x95</button>\n    </form>\n    <hr>\n    <!--<h1>\xe6\xb3\xa8\xe5\x86\x8c</h1>-->\n    <!--<form class="pure-form"  action="/register" method="POST">-->\n        <!--<input name="username" class="form-control" placeholder="\xe8\xbe\x93\xe5\x85\xa5\xe7\x94\xa8\xe6\x88\xb7\xe5\x90\x8d" />-->\n        <!--<br>-->\n        <!--<input name="password" class="form-control" placeholder="\xe8\xbe\x93\xe5\x85\xa5\xe5\xaf\x86\xe7\xa0\x81" />-->\n        <!--<br>-->\n        <!--<br>-->\n        <!--<input name="note" class="form-control" placeholder="\xe8\xbe\x93\xe5\x85\xa5\xe4\xb8\xaa\xe6\x80\xa7\xe7\xad\xbe\xe5\x90\x8d" />-->\n        <!--<br>-->\n        <!--<button class="pure-button pure-button-primary" type="submit">\xe6\xb3\xa8\xe5\x86\x8c</button>-->\n    <!--</form>-->\n</body>\n</html>'

雖然看上去很亂,但是相信你可以看出,這里既包括了 HTTP 頭的數(shù)據(jù),也包括了網(wǎng)頁(yè)(Body)數(shù)據(jù),可以用代碼把它們分離一下:

header, body = data.split('\r\n\r\n')
print(header.decode('utf-8'), body.decode('utf-8'))

最后,我們就得到了一個(gè)比較直觀(guān)的數(shù)據(jù):

# HTTP 頭
HTTP/1.1 200 OK
Date: Fri, 01 Jul 2016 05:07:06 GMT
Server: Apache/2.4.7 (Ubuntu)
Content-Length: 1181
Vary: Accept-Encoding
Connection: close
Content-Type: text/html; charset=utf-8
# Body
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登錄頁(yè)面</title>
    <link rel="stylesheet" >
</head>
<body>
    <ul class=flashes>
    
    <h1>登錄</h1>
    <form class="pure-form" action="/login" method="POST">
        <input name="username" class="form-control" placeholder="輸入用戶(hù)名" />
        <br>
        <input name="password" class="form-control" placeholder="輸入密碼" />
        <br>
        <button class="pure-button pure-button-primary" type="submit">登錄</button>
    </form>
    <hr>
    <!--<h1>注冊(cè)</h1>-->
    <!--<form class="pure-form"  action="/register" method="POST">-->
        <!--<input name="username" class="form-control" placeholder="輸入用戶(hù)名" />-->
        <!--<br>-->
        <!--<input name="password" class="form-control" placeholder="輸入密碼" />-->
        <!--<br>-->
        <!--<br>-->
        <!--<input name="note" class="form-control" placeholder="輸入個(gè)性簽名" />-->
        <!--<br>-->
        <!--<button class="pure-button pure-button-primary" type="submit">注冊(cè)</button>-->
    <!--</form>-->
</body>
</html>

當(dāng)然,我們也可以利用socket庫(kù)構(gòu)造一個(gè) HTTP 服務(wù)器。

  • http_server.py
import socket


def index():
    html = b'HTTP/1.x 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>Hello World</h1>'
    return html


host = ''
port = 3000
s = socket.socket()
s.bind((host, port)) 
s.listen(5)

while True:
    s.listen(3)
    connection, address = s.accept()
    request = connection.recv(1024)
    request = request.decode('utf-8')
    connection.sendall(response)
    connection.close()

運(yùn)行它,它就會(huì)一直處于監(jiān)聽(tīng)狀態(tài)。然后修改之前的客戶(hù)端代碼給我們這個(gè)自己造的服務(wù)器就 OK。
還有一些知識(shí)點(diǎn)是,手寫(xiě)路徑,手寫(xiě)解析GET的查詢(xún)字符串(query string),先挖坑,以后填。GETPOST簡(jiǎn)單的區(qū)別就是,一個(gè)顯式(在地址欄上),一個(gè)隱式(在 Body 里),所以HTTPS協(xié)議配合POST方法,這樣傳送隱私數(shù)據(jù)就能保證安全。


Cookie

服務(wù)器確認(rèn)你的身份是利用 Cookie,比方說(shuō)驗(yàn)證你的登錄狀態(tài)。你給服務(wù)器提交了用戶(hù)名密碼,它驗(yàn)證 OK 了,它會(huì)給你一段 Cookie,從來(lái)在后面你發(fā)起的 HTTP 請(qǐng)求里驗(yàn)證你的身份。因此,Cookie 不可以是明文的(譬如說(shuō) username=arischow,這安全性就太差了),因?yàn)?HTTP 的請(qǐng)求頭是可以愛(ài)寫(xiě)啥寫(xiě)啥的(之前的代碼里面就是手寫(xiě)的 HTTP 請(qǐng)求頭),假設(shè)是明文的,對(duì)方把 Cookie 改成 username=admin,那樣它就可以偽造成管理員身份做壞事,這會(huì)產(chǎn)生安全問(wèn)題。簡(jiǎn)單的解決方法是,造一個(gè)無(wú)規(guī)律高強(qiáng)度的隨機(jī)字符作為 Cookie,導(dǎo)致無(wú)規(guī)律可循。

數(shù)據(jù)庫(kù)

其實(shí)數(shù)據(jù)也可以用文本文件保存:

Aris, 123456, xxx@xxx.com
Alex, 566555, alex@126.com
Susan, 455721, susan@163.com 

數(shù)據(jù)庫(kù)儲(chǔ)存數(shù)據(jù)更有條理,更方便查詢(xún)和調(diào)用特定部分。
知識(shí)點(diǎn):SQL 的 CRUD

Flask

了解上面所羅列的一些知識(shí)點(diǎn)之后,來(lái)看 Flask。用了 Flask,上面很多掏糞的事情都變得簡(jiǎn)單,具體到render_template, url_for, flash, request, redirect那樣的沒(méi)什么好講。MVC 的概念,我這么理解:

  • Model - 數(shù)據(jù)請(qǐng)求 / 操作 (像現(xiàn)在用到的 Flask-SQLAlchemy, Flask-WTForms 的東西, 我都放在這里)
  • View - 視圖展示 / 操作 (給 View 傳入數(shù)據(jù), 再寫(xiě)一些簡(jiǎn)單的判斷, 決定視圖怎么顯示, 譬如用戶(hù)已經(jīng)登錄到系統(tǒng)了, 導(dǎo)航欄就不可能還顯示"登錄"這個(gè)按鈕, 這個(gè)可以把 Session 傳進(jìn) View, 然后 View 根據(jù) Session 的值判斷應(yīng)該展示登錄還是登出按鈕)
  • Controller - 事件綁定 (建立路由, 判斷什么時(shí)候該調(diào)用什么, 譬如用戶(hù)注冊(cè), 用戶(hù)輸入的帳號(hào)密碼郵箱是需要判斷格式/長(zhǎng)度是否正確的, 這時(shí)候涉及到數(shù)據(jù)的操作, Controller 就去調(diào)用 Model 里面的某一個(gè)函數(shù), 這個(gè)函數(shù)返回值后, Controller 根據(jù)值作出下一步?jīng)Q定:如果合規(guī),調(diào)用 Model 里面的函數(shù),保存數(shù)據(jù), 如果不合規(guī), 返回錯(cuò)誤……)

SQLAlchemy

在 Flask 里面我們會(huì)用到 SQLAlchemy,它做好了 API 接口,用了它我們不用裸寫(xiě) SQL 語(yǔ)句。
難點(diǎn):對(duì)應(yīng)關(guān)系是一個(gè)難點(diǎn),比較常用的是一對(duì)多關(guān)系。假設(shè)我們有兩張表,一張是users,里面的字段有id, username, password, 還有一張posts,里面的字段有id, title, content,我們要為這兩張表建立連接:一個(gè)博客帖子只會(huì)有一個(gè)作者(用戶(hù)),而一個(gè)作者(用戶(hù))可以有很多博客帖子。

# ...
class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String, unique=True)
    password = db.Column(db.String, nullable=False)

    # 下面這行重點(diǎn)
    posts = db.relationship('Post', backref='user')

    def __repr__(self):
        return u'<User: {}>'.format(self.username)

class Post(db.Model):
    __tablename__ = 'posts'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String, nullable=False)
    content = db.Column(db.Text)

    # 下面這行重點(diǎn)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'))

    def __repr__(self):
        return u'<Post: {}>'.format(self.title)

外鍵部分待編輯...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 22年12月更新:個(gè)人網(wǎng)站關(guān)停,如果仍舊對(duì)舊教程有興趣參考 Github 的markdown內(nèi)容[https://...
    tangyefei閱讀 35,390評(píng)論 22 257
  • 轉(zhuǎn)載,覺(jué)得這篇寫(xiě) SQLAlchemy Core,寫(xiě)得非常不錯(cuò)。不過(guò)后續(xù)他沒(méi)寫(xiě)SQLAlchemy ORM... ...
    非夢(mèng)nj閱讀 5,591評(píng)論 1 14
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,759評(píng)論 11 349
  • 本文系轉(zhuǎn)載于網(wǎng)絡(luò)。 Web 應(yīng)用框架,或者簡(jiǎn)單的說(shuō)是“Web 框架”,其實(shí)是建立 web 應(yīng)用的一種方式。從簡(jiǎn)單的...
    everfight閱讀 340評(píng)論 0 3
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評(píng)論 19 139

友情鏈接更多精彩內(nèi)容