美文网首页
Web 课上半场

Web 课上半场

作者: ArisX | 来源:发表于2016-07-01 16:19 被阅读0次

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<!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),先挖坑,以后填。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 的概念,我这么理解:

  • 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)

外键部分待编辑...

相关文章

  • Web 课上半场

    HTTP, Socket, TCP/IP HTTP 由 Header 和 Body 两部分组成,发送 HTTP 请...

  • 今日份打卡 149/365

    行业文章前端的去向前端vs后端国内前端现状,1:10前端下半场,web assembly & web gpu机遇,...

  • XSS简述&入门

    上课上自闭了,我真的不是web手也打不来web题……写个总结,算是补上一年前跟大佬py到的那两道web的wp。……...

  • 余生很贵,坦然面对

    人生有两场, 上半场和下半场, 上半场恍如隔世, 下半场近在眼前, 上半场就是前半生, 下半场就叫做余生。 前半生...

  • 课上

    体育老师不住地叮嘱着 行住坐卧,都是修行 学生们低头 一群此时已降低为兽类的人 被手机俘虏着 老师苍老了 但又淡然...

  • 课上

    今年我大三了,已经走过了期中,却自己越来越迷茫了,此时呢,我在课上听着,大学不迷茫这本书,插着耳机,享受着自己的世...

  • 课上

    静静的看着老师在黑板上画的标识 直到双眼只剩模糊 多少是沉溺于追逐 给自己太多难受 倔强的不要闭上眼睛 也不要转...

  • 课上

    下午第一节线代 坐在最后一排的我们完全没有学习的自觉 然后我就在那儿自拍 被发现后 就一起在课上摆姿势自拍 一边怕...

  • 课上

    夜中,学堂上课。课上,某女生问: “老师,您结婚了吗?” 老师一时语塞。但老师随即觉得她不应该问这样的问题,于是走...

  • Angular、React 和 Vue 三大框架,Web 开发该

    前言 在乐字节公开课上老师问了我们这个这问题 ,Angular、React 和 Vue 三大框架,Web 开发该如...

网友评论

      本文标题:Web 课上半场

      本文链接:https://www.haomeiwen.com/subject/dgbfjttx.html