引子
描述下当我们在浏览器输入一个url网址,比如说www,jd.com, 然后显示整个页面的过程
- 当输入http://www.jd.com的时候,先把请求发送到本地DNS服务器里找www.jd.com对应的Ip地址和端口,如果有,返回ip地址和端口
- 如果本地DNS服务器里面没有字符串 www.jd.com对应的ip地址和端口,则会去DNS根域服务器去找(root-server.net),根域会把字符串交给顶级域服务器.com DNS服务器,然后会 将字符串交个权威DNS服务器找到jd.com,然后再去二级域名服务器找到www.jd.com
- 当客户端(浏览器)获得了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>