Web 课上半场

2016-07-01  本文已影响0人  ArisX

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 协议。


当然,我们写的时候为了空行会这样写:

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 服务器。

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),先挖坑,以后填。GETPOST简单的区别就是,一个显式(在地址栏上),一个隐式(在 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 的概念,我这么理解:

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)

外键部分待编辑...

上一篇 下一篇

猜你喜欢

热点阅读