HTTP, Socket, TCP/IP
HTTP 由 Header
和 Body
两部分组成,发送 HTTP 请求(Request)的叫客户端,接受到 HTTP 请求并返回信息(Response)的叫服务器。现时流行的 HTTP 协议版本是 1.1,当然也有用 HTTP 2 的,不表。最常用的两种 method 是 GET
和 POST
。PUT
现在也会被提到不少。一般的 HTTP 头是这样的:
GET / HTTP/1.1
Host: vip.cocode.cc
Connection: close
Content-Type: text/html
GET
表示我们所用的方式,/login
表示我们在获取这个网站根目录下的 login 的数据,HTTP/1.1
表示所用的 HTTP 协议。
当然,我们写的时候为了空行会这样写:
- http_client.py
import socket
# 创建 Socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接
s.connect(('vip.cocode.cc', 80))
# 发起 HTTP 请求
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')
# 接收数据
buffer = []
while True:
d = s.recv(1024)
if d:
buffer.append(d)
else:
break
data = b''.join(buffer)
s.close()
print(data)
Socket 是一个高大上名词,具体这里不解释。创建 Socket 是一个套路,第一个参数socket.AF_INET
代表着在这里我们是用 IPv4 模式,而第二个参数socket.SOCK_STREAM
意思是这里我们用 TCP 协议。
建立连接,给s.connect
传入一个tuple
,分别是 address 和 port 两个参数,一般都是 80,因为 HTTP 默认就是 80,套路。
建立连接后,我们就向 server 发起 HTTP 请求,要注意\r\n
和\r\n\r\n
,规定的套路,如果不按照这个来,这就不是一个合规的 HTTP 请求,会导致你无法获得你想要的首页内容。如果没问题,我们就可以接收服务器返回的数据了。
接收数据的这段代码的意思是,s.recv(1024)
每次最多接受 1024 字节的数据,然后嵌套在一个while
循环内,当s.recv()
返回空数据,证明数据都被接收过来了,这时候就可以结束循环。
s.close()
用作关闭 socket ,和服务器的一次通信就此结束。
最后,返回的数据是这样的:
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" href="http://yui.yahooapis.com/pure/0.6.0/pure-min.css">\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 头的数据,也包括了网页(Body)数据,可以用代码把它们分离一下:
header, body = data.split('\r\n\r\n')
print(header.decode('utf-8'), body.decode('utf-8'))
最后,我们就得到了一个比较直观的数据:
# 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>登录页面</title>
<link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.6.0/pure-min.css">
</head>
<body>
<ul class=flashes>
<h1>登录</h1>
<form class="pure-form" action="/login" method="POST">
<input name="username" class="form-control" placeholder="输入用户名" />
<br>
<input name="password" class="form-control" placeholder="输入密码" />
<br>
<button class="pure-button pure-button-primary" type="submit">登录</button>
</form>
<hr>
<!--<h1>注册</h1>-->
<!--<form class="pure-form" action="/register" method="POST">-->
<!--<input name="username" class="form-control" placeholder="输入用户名" />-->
<!--<br>-->
<!--<input name="password" class="form-control" placeholder="输入密码" />-->
<!--<br>-->
<!--<br>-->
<!--<input name="note" class="form-control" placeholder="输入个性签名" />-->
<!--<br>-->
<!--<button class="pure-button pure-button-primary" type="submit">注册</button>-->
<!--</form>-->
</body>
</html>
当然,我们也可以利用socket
库构造一个 HTTP 服务器。
- 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()
运行它,它就会一直处于监听状态。然后修改之前的客户端代码给我们这个自己造的服务器就 OK。
还有一些知识点是,手写路径,手写解析GET
的查询字符串(query string),先挖坑,以后填。GET
和POST
简单的区别就是,一个显式(在地址栏上),一个隐式(在 Body 里),所以HTTPS
协议配合POST
方法,这样传送隐私数据就能保证安全。
Cookie
服务器确认你的身份是利用 Cookie,比方说验证你的登录状态。你给服务器提交了用户名密码,它验证 OK 了,它会给你一段 Cookie,从来在后面你发起的 HTTP 请求里验证你的身份。因此,Cookie 不可以是明文的(譬如说 username=arischow
,这安全性就太差了),因为 HTTP 的请求头是可以爱写啥写啥的(之前的代码里面就是手写的 HTTP 请求头),假设是明文的,对方把 Cookie 改成 username=admin
,那样它就可以伪造成管理员身份做坏事,这会产生安全问题。简单的解决方法是,造一个无规律高强度的随机字符作为 Cookie,导致无规律可循。
数据库
其实数据也可以用文本文件保存:
Aris, 123456, xxx@xxx.com
Alex, 566555, alex@126.com
Susan, 455721, susan@163.com
数据库储存数据更有条理,更方便查询和调用特定部分。
知识点:SQL 的 CRUD
Flask
了解上面所罗列的一些知识点之后,来看 Flask。用了 Flask,上面很多掏粪的事情都变得简单,具体到render_template, url_for, flash, request, redirect
那样的没什么好讲。MVC 的概念,我这么理解:
- Model - 数据请求 / 操作 (像现在用到的 Flask-SQLAlchemy, Flask-WTForms 的东西, 我都放在这里)
- View - 视图展示 / 操作 (给 View 传入数据, 再写一些简单的判断, 决定视图怎么显示, 譬如用户已经登录到系统了, 导航栏就不可能还显示"登录"这个按钮, 这个可以把 Session 传进 View, 然后 View 根据 Session 的值判断应该展示登录还是登出按钮)
- Controller - 事件绑定 (建立路由, 判断什么时候该调用什么, 譬如用户注册, 用户输入的帐号密码邮箱是需要判断格式/长度是否正确的, 这时候涉及到数据的操作, Controller 就去调用 Model 里面的某一个函数, 这个函数返回值后, Controller 根据值作出下一步决定:如果合规,调用 Model 里面的函数,保存数据, 如果不合规, 返回错误……)
SQLAlchemy
在 Flask 里面我们会用到 SQLAlchemy,它做好了 API 接口,用了它我们不用裸写 SQL 语句。
难点:对应关系是一个难点,比较常用的是一对多关系。假设我们有两张表,一张是users
,里面的字段有id, username, password
, 还有一张posts
,里面的字段有id, title, content
,我们要为这两张表建立连接:一个博客帖子只会有一个作者(用户),而一个作者(用户)可以有很多博客帖子。
# ...
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)
# 下面这行重点
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)
# 下面这行重点
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
def __repr__(self):
return u'<Post: {}>'.format(self.title)
外键部分待编辑...
网友评论