Web 课上半场

HTTP, Socket, TCP/IP

HTTP 由 HeaderBody 两部分组成,发送 HTTP 请求(Request)的叫客户端,接受到 HTTP 请求并返回信息(Response)的叫服务器。现时流行的 HTTP 协议版本是 1.1,当然也有用 HTTP 2 的,不表。最常用的两种 method 是 GETPOSTPUT现在也会被提到不少。一般的 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\n\n\n    \n    \xe7\x99\xbb\xe5\xbd\x95\xe9\xa1\xb5\xe9\x9d\xa2\n    \n\n\n    
    \n \n

    \xe7\x99\xbb\xe5\xbd\x95

    \n
    \n \n
    \n \n
    \n \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n\n'

虽然看上去很乱,但是相信你可以看出,这里既包括了 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



    
    登录页面
    


    

    登录




当然,我们也可以利用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

Hello World

' 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),先挖坑,以后填。GETPOST简单的区别就是,一个显式(在地址栏上),一个隐式(在 Body 里),所以HTTPS协议配合POST方法,这样传送隐私数据就能保证安全。


Cookie

服务器确认你的身份是利用 Cookie,比方说验证你的登录状态。你给服务器提交了用户名密码,它验证 OK 了,它会给你一段 Cookie,从来在后面你发起的 HTTP 请求里验证你的身份。因此,Cookie 不可以是明文的(譬如说 username=arischow,这安全性就太差了),因为 HTTP 的请求头是可以爱写啥写啥的(之前的代码里面就是手写的 HTTP 请求头),假设是明文的,对方把 Cookie 改成 username=admin,那样它就可以伪造成管理员身份做坏事,这会产生安全问题。简单的解决方法是,造一个无规律高强度的随机字符作为 Cookie,导致无规律可循。

数据库

其实数据也可以用文本文件保存:

Aris, 123456, [email protected]
Alex, 566555, [email protected]
Susan, 455721, [email protected] 

数据库储存数据更有条理,更方便查询和调用特定部分。
知识点: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''.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''.format(self.title)

外键部分待编辑...

你可能感兴趣的:(Web 课上半场)