模拟一个简单的web请求

引子

描述下当我们在浏览器输入一个url网址,比如说www,jd.com, 然后显示整个页面的过程

  1. 当输入http://www.jd.com的时候,先把请求发送到本地DNS服务器里找www.jd.com对应的Ip地址和端口,如果有,返回ip地址和端口
  2. 如果本地DNS服务器里面没有字符串 www.jd.com对应的ip地址和端口,则会去DNS根域服务器去找(root-server.net),根域会把字符串交给顶级域服务器.com DNS服务器,然后会 将字符串交个权威DNS服务器找到jd.com,然后再去二级域名服务器找到www.jd.com
  3. 当客户端(浏览器)获得了ip地址和端口,客户端发送请求到响应的服务器,服务器响应需求,客户端拿到响应结果,渲染页面。


字符串和字节的转换

\>>> s="hello"
\>>> s
'hello'
\>>> bytes(s,encoding="utf-8")
b'hello'
\>>> b=bytes(s,encoding="utf-8")
\>>> b
b'hello'
\>>> str(b,encoding='utf-8')
'hello'


模拟一个简单的web请求

from socket import *

sock = socket(AF_INET, SOCK_STREAM)
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8800))
sock.listen(5)

while True:  # 不断地等待接收客户端的链接,保证服务器不中断
    print('\033[32mserver waiting...\033[0m')
    client, addr = sock.accept()  # 客户端套接字对象,客户端地址端口
    data = client.recv(1024)
    print('data', data)
    # client.send(b'hello world') # 浏览器端会显示 127.0.0.1 发送的响应无效
    # 浏览器要访问数据必须加上HTTP/1.1 状态码
    # HTTP/1.1 200 OK 响应首行  hello world 响应体  响应首行和响应体之前用\r\n\r\n,否则响应体无法渲染显示
    with open('index.html','rb') as f:
        data=f.read()
    client.send(b'HTTP/1.1 200 OK\r\n\r\n%s'%data)
    client.close()
    
   
  # index.html
    <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
</head>
<body>
<h1 style="color: red;">宝贝 我来了</h1>
<img src="http://s14.sinaimg.cn/middle/4b955cd7xcb1739815c4d&690" alt="">
<a href="http://www.w3school.com.cn/">click</a>

<div style="height: 200px"></div>
<div>
    <h1 style="color: yellow; background-color: gray">张帆喜欢的类型</h1>
    <img src="http://s8.sinaimg.cn/bmiddle/005DIuBKgy6KewCX5iv97&690" alt="">
</div>
</body>
</html>
/usr/local/bin/python3.6 /Users/cjw/Desktop/pycharm_project/Django/Weby应用程序/JDserver.py
server waiting...
data b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8800\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\n\r\n'
server waiting...
data b'GET /favicon.ico HTTP/1.1\r\nHost: 127.0.0.1:8800\r\nConnection: keep-alive\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36\r\nAccept: image/webp,image/apng,image/*,*/*;q=0.8\r\nReferer: http://127.0.0.1:8800/\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\n\r\n'
server waiting...


浏览器会解释url请求发生了什么

response: 从客户端接收过来的一堆HTML字符串(要放到页面的一堆字符串)
Preview: 浏览器解析服务端发送的的HTML字符串,将这些字符串解析成用户能看到的格式,preview能够看到一个这样的结果

所以浏览器也可以看成一个解释期,解释HTML标记语言
image
image
image
image

HTTP协议

主要由请求协议和响应协议构成
请求协议:浏览器给服务器发的格式的限定
响应协议:服务器给浏览器发的格式的限定

HTTP请求协议

结构:请求首行+请求头+\r\n\r\n+请求体

服务器通过\r\n来识别请求首行,请求头以及请求体
请求首行和请求头之间,多个请求体之间用一个\r\n分割,最后一个请求头和请求体之间用两个\r\n分割

请求方法主要分get和post

get 没有请求体  post 有请求体

get 和 post请求

1. GET请求提交的数据会放在url之后,以?分割URL和传输的数据,参数之间用&相连,比如http://127.0.0.1:8800/?name=changwei&pwd=GET
2. GET 提交的数据大小有限制(因为浏览器对URL长度有限制),而POST方法提交的数据没有限制
3. GET与POST都是提交数据的方式,当需要对服务器所在的数据库的数据进行查询的时候提交GET请求,添加或删除提交POST请求
案例1 (get请求)
'GET / HTTP/1.1\r\nHost: 127.0.0.1:8800\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\n\r\n'
请求首行
GET / HTTP/1.1\r\nHost: 127.0.0.1:8800
格式: 方法(get/post)+发送请求的服务器的路径(URI /form/entry)+协议版本(HTTP/1.1)
请求头
请求头 Connection: keep-alive
请求头 Pragma: no-cache
请求头 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
请求头 Accept: image/webp,image/apng,image/*,*/*;q=0.8
请求头 Referer: http://127.0.0.1:8800
请求头 Accept-Encoding: gzip, deflate, br
请求头 Accept-Language: zh-CN,zh;q=0.9
请求体
1. get请求没有请求体 所以最后的请求体部分是空的
2. get请求提交的数据会放在url之后,以?分割URL和传输的数据,参数之间用&相连,比如http://127.0.0.1:8800/?name=changwei&pwd=123

'GET /?name=changwei&pwd=123 HTTP/1.1\r\nHost: 127.0.0.1:8800\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\n\r\n'
案例2 (post请求)
# 服务端
from socket import *

sock = socket(AF_INET, SOCK_STREAM)
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8800))
sock.listen(5)

while True:  # 不断地等待接收客户端的链接,保证服务器不中断
    print('\033[32mserver waiting...\033[0m')
    client, addr = sock.accept()  # 客户端套接字对象,客户端地址端口
    data = client.recv(1024)
    print('data', data)
    # client.send(b'hello world') # 浏览器端会显示 127.0.0.1 发送的响应无效
    # 浏览器要访问数据必须加上HTTP/1.1 状态码
    # HTTP/1.1 200 OK 响应首行  hello world 响应体  响应首行和响应体之前用\r\n\r\n,否则响应体无法渲染显示
    with open('login.html','rb') as f:
        data=f.read()
    client.send(b'HTTP/1.1 200 OK\r\n\r\n%s'%data)
    client.close()

# index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
</head>
<body>
<form action="http://127.0.0.1:8800/" method="post">
    用户名 <input type="text" name="user">
    密码 <input type="password" name="pwd">
    <input type="submit">
</form>
</body>
</html>
'POST / HTTP/1.1\r\nHost: 127.0.0.1:8800\r\nConnection: keep-alive\r\nContent-Length: 21\r\nCache-Control: max-age=0\r\nOrigin: http://127.0.0.1:8800\r\nUpgrade-Insecure-Requests: 1\r\nContent-Type: application/x-www-form-urlencoded\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\nReferer: http://127.0.0.1:8800/\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\n\r\nuser=changwei&pwd=123'
请求首行
POST / HTTP/1.1\r\nHost: 127.0.0.1:8800
Cache-Control: max-age=0\r\nOrigin: http://127.0.0.1:8800
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://127.0.0.1:8800/\r\nAccept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
请求头
Connection: keep-alive\r\nContent-Length: 21
请求体
user=changwei&pwd=123

HTTP响应协议

和请求协议一样,主要由响应首行,响应头,响应体组成

image
image
常见的响应状态码
1xx Informational(信息性状态码) 接受的请求正在处理
2xx Success(成功状态码) 请求正常处理完毕
3xx Redirection(重定向状态码) 需要进行附加操作以完成请求  (域名地址变了了,跳转到新的域名地址,这种情况浏览器发2次请求)
4xx Client Error (客户端错误状态码) 服务器无法处理请求   404 (not found)  403(forbidden 禁止访问)  
5xx Server Error(服务器错误状态码) 服务器处理请求出错  500(服务端代码错误) 502(网关错误 bad gateway)


通过socket实现web框架

server.py

import time
from socket import *


def f1():
    # 静态网站
    fp = open('index.html', 'r', encoding='utf-8')
    data = fp.read()
    fp.close()
    return bytes(data, encoding='utf-8')


def f2():
    fp = open('article.html', 'r', encoding='utf-8')
    data = fp.read()
    ctime = time.time()
    data = data.replace("@@content@@", str(ctime))
    fp.close()

    return bytes(data, encoding='utf-8')


def f3():
    import pymysql
    conn = pymysql.connect(host='127.0.0.1', user='root', password='123', db='youku', charset='utf8')
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
    sql = "select id,name,password,register_time from user"

    cursor.execute(sql)
    res = cursor.fetchall()
    print(res)

    '''
    [{'id': 1, 'name': 'lxx', 'password': '202cb962ac59075b964b07152d234b70', 'register_time': '2019-06-26 21:48:47'},
    {'id': 2, 'name': '111', 'password': '698d51a19d8a121ce581499d7b701668', 'register_time': '2019-06-26 21:49:10'}]

    '''

    res_list = []
    for user in res:
        res_str = '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>' % (
        user['id'], user['name'], user['password'], user['register_time'])
        res_list.append(res_str)

    s = ''.join(res_list)

    fp = open('content.html', 'r', encoding='utf-8')
    data = fp.read()
    data = data.replace("@@content@@", s)

    return bytes(data, encoding='utf-8')

    ### 需要将html代码和mysql结果融合


def f4():
    pass


# 路由系统
routes = [
    ('/xxx', f1),
    ('/ooo', f2),
    ('/hhh', f3),
    ('/kkk', f4)
]


def run():
    sk = socket()
    sk.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    sk.bind(('127.0.0.1', 8081))
    sk.listen(5)

    while True:
        conn, addr = sk.accept()

        buf = conn.recv(8096)

        data = str(buf, encoding='utf-8')
        header_list = data.split('\r\n\r\n')[0].split('\r\n')[0]
        print(header_list)
        uri = header_list.split(' ')[1]

        func_name = None
        for items in routes:
            if items[0] == uri:
                func_name = items[1]
                break

        if func_name:
            res = func_name()

        else:
            res = b'404 not found'

        conn.send(b'HTTP/1.1 200 OK\r\nCache-Control: private\r\n\r\n')
        conn.send(res)


if __name__ == '__main__':
    run()

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
</head>
<body>
<h1>this is index</h1>
</body>
</html>

content.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
</head>
<body>
<table border="1px">
    <thead>
    <tr>
        <th>ID</th>
        <th>Name</th>
        <th>password</th>
        <th>register_time</th>
    </tr>
    </thead>
    <tbody>
        <tr>
            @@content@@
        </tr>
    </tbody>
</table>
</body>
</html>

article.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
</head>
<body>
    @@content@@
</body>
</html>


通过wsgiref实现web框架

step 1

访问不同的url路径可以跳转到不同的网页

from wsgiref.simple_server import make_server


def application(environ, start_response):
    # 按着httpd协议解析数据(将所有请求存到一个字典里): environ
    # 按着httpd协议组装数据(生成数据 响应首行,响应头): start_response

    # 当前的请求路径
    path = environ.get('PATH_INFO')
    start_response('200 OK', [('Content-Type', 'text/html')])  # 生成数据 请求首行,请求头,请求体

    if path == "/login":
        with open('login.html', 'rb') as f:
            global data
            data = f.read()

    if path == '/index':
        with open('index.html', 'rb') as f:
            data = f.read()

    return [data]


# 封装socket
httped = make_server("127.0.0.1", 8060, application)

# 等待客户连接: conn,addr=sock.accept()
httped.serve_forever()

# login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
</head>
<body>
<form action="http://127.0.0.1:8800/" method="post">
    用户名 <input type="text" name="user">
    密码 <input type="password" name="pwd">
    <input type="submit">
</form>
</body>
</html>

# index.html
    <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
</head>
<body>
<h1 style="color: red;">宝贝 我来了</h1>
<img src="http://s14.sinaimg.cn/middle/4b955cd7xcb1739815c4d&690" alt="">
<a href="http://www.w3school.com.cn/">click</a>

<div style="height: 200px"></div>
<div>
    <h1 style="color: yellow; background-color: gray">张帆喜欢的类型</h1>
    <img src="http://s8.sinaimg.cn/bmiddle/005DIuBKgy6KewCX5iv97&690" alt="">
</div>
</body>
</html>

设置页面图标为京东图标

from wsgiref.simple_server import make_server


def application(environ: dict, start_response):

    start_response('200 ok', [('Content-Type','text/html')])

    print("PATH",environ.get('PATH_INFO'))

    path=environ.get('PATH_INFO')

    if path=='/favicon.ico':
        with open('favicon.ico','rb') as f:
            data=f.read()
        return [data]

    return [b'<h1>Hello,web!</h1>']



httped = make_server("127.0.0.1", 8080, application)
httped.serve_forever()

step 2

通过列表的映射来使得访问不同的url路径可以跳转到不同的网页

from wsgiref.simple_server import make_server

def login():
    with open('login.html', 'rb') as f:
        data = f.read()
    return data

def index():
    with open('index.html', 'rb') as f:
        data = f.read()
    return data

def favi():
    with open('favicon.ico', 'rb') as f:
        data = f.read()
    return data


def application(environ: dict, start_response):
    start_response('200 ok', [('Content-Type', 'text/html')])

    print("PATH", environ.get('PATH_INFO'))

    # 当前请求路径
    global path
    path = environ.get('PATH_INFO')

    url_patterns = [
        ("/login", login),
        ("/index", index),
        ("/favicon.ico", favi)
    ]

    func = None
    for item in url_patterns:
        print(item)
        if path == item[0]:
            func = item[1]
            break

    print(func)

    if func:
        return [func()]

    else:
        return [b'404!']

httped = make_server("127.0.0.1", 8080, application)
httped.serve_forever()

# login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>login</title>
</head>
<body>
<h4>登录页面</h4>

<form action="http://127.0.0.1:8080/" method="post">
   用户名 <input type="text" name="user">
    密码<input type="password" name="pwd">
    <input type="submit">
</form>
</body>
</html>

# index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
</head>
<body>
    <h4>welcome to here</h4>
</body>
</html>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容