原文链接:https://www.cnblogs.com/maple-shaw/p/8862330.html

Web框架本质

我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。

import socket

sk = socket.socket()
sk.bind(("127.0.0.1", ))
sk.listen() while True:
conn, addr = sk.accept()
data = conn.recv()
conn.send(b"OK")
conn.close()

sk.bind(("127.0.0.1", 8000))   当绑定为8000的时候,浏览器报错;ERR_INVALID_HTTP_RESPONSE。无效的响应,响应格式有问题

socket服务端

  1. import socket
  2. sk = socket.socket()
  3. sk.bind(("127.0.0.1", 8000))
  4. sk.listen()
  5. while True:
  6. conn, addr = sk.accept()
  7. data = conn.recv(8096)
  8. conn.send(b"OK")
  9. conn.close()

如果将8000端口改为80,就能浏览器就能显示出ok,但是我们想要的是其它端口也能显示内容

可以说Web服务本质上都是在这十几行代码基础上扩展出来的。这段代码就是它们的祖宗。

用户在浏览器中输入网址,浏览器会向服务端发送数据,那浏览器会发送什么数据?怎么发?这个谁来定? 你这个网站是这个规定,他那个网站按照他那个规定,那互联网还能玩么?

所以,必须有一个统一的规则,让大家发送消息、接收消息的时候都有个格式依据,不能随便写。

这个规则就是HTTP协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来。

HTTP协议主要规定了客户端和服务器之间的通信格式,那HTTP协议是怎么规定消息格式的呢?

让我们首先打印下我们在服务端接收到的消息是什么。

  1. import socket
  2. sk = socket.socket()
  3. sk.bind(("127.0.0.1", 8000))
  4. sk.listen()
  5. while True:
  6. conn, addr = sk.accept()
  7. data = conn.recv(8096)
  8. print(data)  # 将浏览器发来的消息打印出来
  9. conn.send(b"OK")
  10. conn.close()

输出:

接收回来的是bytes类型,打印解码的为(字符串类型,换行了):

print(data.decode()) # 将浏览器发来的消息打印出来

b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3355.4 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\nCookie: csrftoken=CtHePYARJOKNx5oNVwxIteOJXpNyJ29L4bW4506YoVqFaIFFaHm0EWDZqKmw6Jm8\r\n\r\n'

我们将\r\n替换成换行看得更清晰点:

  1. GET / HTTP/1.1
  2. Host: 127.0.0.1:8080
  3. Connection: keep-alive
  4. Cache-Control: max-age=0
  5. Upgrade-Insecure-Requests: 1
  6. User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3355.4 Safari/537.36
  7. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
  8. Accept-Encoding: gzip, deflate, br
  9. Accept-Language: zh-CN,zh;q=0.9
  10. Cookie: csrftoken=CtHePYARJOKNx5oNVwxIteOJXpNyJ29L4bW4506YoVqFaIFFaHm0EWDZqKmw6Jm8

然后我们再看一下我们访问博客园官网时浏览器收到的响应信息是什么。

响应相关信息可以在浏览器调试窗口的Network标签页中看到。响应数据不是都能看的view sourse

点击view source之后显示如下图:

response是请求的数据体部分,复制粘贴到自己创建的socket里,让它发送给浏览器

当发送给浏览器的为HTML时:conn.send(b"HTTP/1.1 200 OK\r\n\r\n <h1>ok</h1> ")。渲染了

我们发现收发的消息需要按照一定的格式来,这里就需要了解一下HTTP协议了。

HTTP协议介绍

HTTP协议对收发消息的格式要求

每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。

HTTP响应的Header中有一个 Content-Type表明响应的内容格式。它的值如text/html; charset=utf-8。

text/html则表示是网页,charset=utf-8则表示编码为utf-8。

HTTP GET请求的格式:

HTTP响应的格式:

自定义web框架

经过上面的学习,那我们基于socket服务端的十几行代码写一个我们自己的web框架。我们先不处理浏览器发送的请求,先让浏览器能显示我们web框架返回的信息,那我们就要按照HTTP协议的格式来发送响应。

  1. import socket
  2. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  3. sock.bind(('127.0.0.1', 8000))
  4. sock.listen()
  5. while True:
  6. conn, addr = sock.accept()
  7. data = conn.recv(8096)
  8. # 给回复的消息加上响应状态行
  9. conn.send(b"HTTP/1.1 200 OK\r\n\r\n")
  10. conn.send(b"OK")
  11. conn.close()

我们通过十几行代码简单地演示了web 框架的本质。

接下来就让我们继续完善我们的自定义web框架吧!

根据不同的路径返回不同的内容


这样就结束了吗? 如何让我们的Web服务根据用户请求的URL不同而返回不同的内容呢?

小事一桩,我们可以从请求相关数据里面拿到请求URL的路径,然后拿路径做一个判断...

  1. """
  2. 根据URL中不同的路径返回不同的内容
  3. """
  4. import socket
  5. sk = socket.socket()
  6. sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
  7. sk.listen()  # 监听
  8. while True:
  9. # 等待连接
  10. conn, add = sk.accept()
  11. data = conn.recv(8096)  # 接收客户端发来的消息
  12. # 从data中取到路径
  13. data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
  14. # 按\r\n分割
  15. data1 = data.split("\r\n")[0]
  16. url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
  17. conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
  18. # 根据不同的路径返回不同内容
  19. if url == "/index/":
  20. response = b"index"
  21. elif url == "/home/":
  22. response = b"home"
  23. else:
  24. response = b"404 not found!"
  25. conn.send(response)
  26. conn.close()

当浏览器访问对应的目录时,后台显示/home,我们要根据访问的目录而send给浏览器不同的数据。

获取路径:

data = conn.recv(8096)
print(data.decode().split()) # 将浏览器发来的消息打印出来

根据索引打印

print(data.decode().split()[1])  # 将浏览器发来的消息打印出来

favicon.ico不用管,是谷歌浏览器 的title

因为获取到的有斜线,而这里判断是没有斜线的字符串,所以么有匹配成功。正则的话有米有都能实现也可以的。

先发请求头,再发数据,分两次发。

根据不同的路径返回不同的内容--函数版

上面的代码解决了不同URL路径返回不同内容的需求。

我们返回的内容是简单的几个字符,那如果我可以将返回的结果封装成一个函数呢?

  1. """
  2. 根据URL中不同的路径返回不同的内容--函数版
  3. """
  4. import socket
  5. sk = socket.socket()
  6. sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
  7. sk.listen()  # 监听
  8. # 将返回不同的内容部分封装成函数
  9. def func(url):
  10. s = "这是{}页面!".format(url)
  11. return bytes(s, encoding="utf8")
  12. while True:
  13. # 等待连接
  14. conn, add = sk.accept()
  15. data = conn.recv(8096)  # 接收客户端发来的消息
  16. # 从data中取到路径
  17. data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
  18. # 按\r\n分割
  19. data1 = data.split("\r\n")[0]
  20. url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
  21. conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
  22. # 根据不同的路径返回不同内容,response是具体的响应体
  23. if url == "/index/":
  24. response = func(url)
  25. elif url == "/home/":
  26. response = func(url)
  27. else:
  28. response = b"404 not found!"
  29. conn.send(response)
  30. conn.close()
def func(url):
s = "this is {} page !".format(url)
return bytes(s, encoding="utf8")

根据不同的路径返回不同的内容--函数进阶版

看起来上面的代码写了一个函数,那肯定可以写多个函数,不同的路径对应执行不同的函数拿到结果,但是我们要一个个判断路径,是不是很麻烦?我们有简单的办法来解决。

  1. """
  2. 根据URL中不同的路径返回不同的内容--函数进阶版
  3. """
  4. import socket
  5. sk = socket.socket()
  6. sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
  7. sk.listen()  # 监听
  8. # 将返回不同的内容部分封装成不同的函数
  9. def index(url):
  10. s = "这是{}页面XX!".format(url)
  11. return bytes(s, encoding="utf8")
  12. def home(url):
  13. s = "这是{}页面。。!".format(url)
  14. return bytes(s, encoding="utf8")
  15. # 定义一个url和实际要执行的函数的对应关系
  16. list1 = [
  17. ("/index/", index),
  18. ("/home/", home),
  19. ]
  20. while True:
  21. # 等待连接
  22. conn, add = sk.accept()
  23. data = conn.recv(8096)  # 接收客户端发来的消息
  24. # 从data中取到路径
  25. data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
  26. # 按\r\n分割
  27. data1 = data.split("\r\n")[0]
  28. url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
  29. conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
  30. # 根据不同的路径返回不同内容
  31. func = None  # 定义一个保存将要执行的函数名的变量
  32. for item in list1:
  33. if item[0] == url:
  34. func = item[1]
  35. break
  36. if func:
  37. response = func(url)
  38. else:
  39. response = b"404 not found!"
  40. # 返回具体的响应消息
  41. conn.send(response)
  42. conn.close()

分析:

1)定义多个函数,将取出url后面的访问目录传入到函数中,执行函数。
2)定义一个列表,列表中有很多个字符串和函数名对应的元组
3)定义一个变量为None,循环列表元素,判断元素第一个字元素是否为url中取出的的访问文件,是就让变量重新赋值为这个这个元素的第二个字元素,即这个变量等于4一个定义好的函数名。
4)如果func是真,访问url中的匹配成功,那么就执行这个函数并用返回数据变量接收函数返回值,否则让请求返回的变量是404未找到。
5)然后发送请求返回的数据给浏览器。这样就实现了一个框架。

返回具体的HTML文件

完美解决了不同URL返回不同内容的问题。 但是我不想仅仅返回几个字符串,我想给浏览器返回完整的HTML内容,这又该怎么办呢?

没问题,不管是什么内容,最后都是转换成字节数据发送出去的。 我们可以打开HTML文件,读取出它内部的二进制数据,然后再发送给浏览器。

  1. """
  2. 根据URL中不同的路径返回不同的内容--函数进阶版
  3. 返回独立的HTML页面
  4. """
  5. import socket
  6. sk = socket.socket()
  7. sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
  8. sk.listen()  # 监听
  9. # 将返回不同的内容部分封装成不同的函数
  10. def index(url):
  11. # 读取index.html页面的内容
  12. with open("index.html", "r", encoding="utf8") as f:
  13. s = f.read()
  14. # 返回字节数据
  15. return bytes(s, encoding="utf8")
  16. def home(url):
  17. with open("home.html", "r", encoding="utf8") as f:
  18. s = f.read()
  19. return bytes(s, encoding="utf8")
  20. # 定义一个url和实际要执行的函数的对应关系
  21. list1 = [
  22. ("/index/", index),
  23. ("/home/", home),
  24. ]
  25. while True:
  26. # 等待连接
  27. conn, add = sk.accept()
  28. data = conn.recv(8096)  # 接收客户端发来的消息
  29. # 从data中取到路径
  30. data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
  31. # 按\r\n分割
  32. data1 = data.split("\r\n")[0]
  33. url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
  34. conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
  35. # 根据不同的路径返回不同内容
  36. func = None  # 定义一个保存将要执行的函数名的变量
  37. for item in list1:
  38. if item[0] == url:
  39. func = item[1]
  40. break
  41. if func:
  42. response = func(url)
  43. else:
  44. response = b"404 not found!"
  45. # 返回具体的响应消息
  46. conn.send(response)
  47. conn.close()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>index</title>
</head>
<body>
<div>这是index页面</div>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>index</title>
</head>
<body>
<div>这是home页面</div>
</body>
</html>

home.html

1)将之前的请求过来执行的函数中,函数的返回值由字符串改为返回对应html的文件内容。
2)实现方法是在函数中将文件内容都读取出来,然后由之前的函数中定义的字符串改为返回读取出来的文件数据。
3)后面程序与上的相同,这样就实现了根据不同url执行不同的函数,返回不同的数据给浏览器:
  2)定义一个列表,列表中有很多个字符串和函数名对应的元组
  3)定义一个变量为None,循环列表元素,判断元素第一个字元素是否为url中取出的的访问文件,是就让变量重新赋值为这个这个元素的第二个字元素,即这个变量等于4一个定义好的函数名。
  4)如果func是真,访问url中的匹配成功,那么就执行这个函数并用返回数据变量接收函数返回值,否则让请求返回的变量是404未找到。
  5)然后发送请求返回的数据给浏览器。这样就实现了一个框架。

让网页动态起来

这网页能够显示出来了,但是都是静态的啊。页面的内容都不会变化的,我想要的是动态网站。

没问题,我也有办法解决。我选择使用字符串替换来实现这个需求。(这里使用时间戳来模拟动态的数据)

  1. """
  2. 根据URL中不同的路径返回不同的内容--函数进阶版
  3. 返回独立的HTML页面
  4. """
  5. import socket
  6. sk = socket.socket()
  7. sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
  8. sk.listen()  # 监听
  9. # 将返回不同的内容部分封装成不同的函数
  10. def index(url):
  11. # 读取index.html页面的内容
  12. with open("index.html", "r", encoding="utf8") as f:
  13. s = f.read()
  14. # 返回字节数据
  15. return bytes(s, encoding="utf8")
  16. def home(url):
  17. with open("home.html", "r", encoding="utf8") as f:
  18. s = f.read()
  19. return bytes(s, encoding="utf8")
  20. def timer(url):
  21. import time
  22. with open("time.html", "r", encoding="utf8") as f:
  23. s = f.read()
  24. s = s.replace('@@time@@', time.strftime("%Y-%m-%d %H:%M:%S"))
  25. return bytes(s, encoding="utf8")
  26. # 定义一个url和实际要执行的函数的对应关系
  27. list1 = [
  28. ("/index/", index),
  29. ("/home/", home),
  30. ("/time/", timer),
  31. ]
  32. while True:
  33. # 等待连接
  34. conn, add = sk.accept()
  35. data = conn.recv(8096)  # 接收客户端发来的消息
  36. # 从data中取到路径
  37. data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
  38. # 按\r\n分割
  39. data1 = data.split("\r\n")[0]
  40. url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
  41. conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
  42. # 根据不同的路径返回不同内容
  43. func = None  # 定义一个保存将要执行的函数名的变量
  44. for item in list1:
  45. if item[0] == url:
  46. func = item[1]
  47. break
  48. if func:
  49. response = func(url)
  50. else:
  51. response = b"404 not found!"
  52. # 返回具体的响应消息
  53. conn.send(response)
  54. conn.close()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>time</title>
</head>
<body>
<div>当前时间是:@@time@@</div>
</body>
</html>
<div>当前时间是:@@time@@</div>   在代码中将文件读取出来,然后字符串替换成当前的时间,然后作为函数返回值编码发送给访问这个地址的请求。实现网页中数据不固定,用数据渲染显示页面。

服务器程序和应用程序

对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。

服务器程序负责对socket服务端进行封装,并在请求到来时,对请求的各种数据进行整理。

应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。

这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。

这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。

WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。

常用的WSGI服务器有uWSGI、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。

wsgiref

我们利用wsgiref模块来替换我们自己写的web框架的socket server部分:

  1. """
  2. 根据URL中不同的路径返回不同的内容--函数进阶版
  3. 返回HTML页面
  4. 让网页动态起来
  5. wsgiref模块版
  6. """
  7. from wsgiref.simple_server import make_server
  8. # 将返回不同的内容部分封装成函数
  9. def index(url):
  10. # 读取index.html页面的内容
  11. with open("index.html", "r", encoding="utf8") as f:
  12. s = f.read()
  13. # 返回字节数据
  14. return bytes(s, encoding="utf8")
  15. def home(url):
  16. with open("home.html", "r", encoding="utf8") as f:
  17. s = f.read()
  18. return bytes(s, encoding="utf8")
  19. def timer(url):
  20. import time
  21. with open("time.html", "r", encoding="utf8") as f:
  22. s = f.read()
  23. s = s.replace('@@time@@', time.strftime("%Y-%m-%d %H:%M:%S"))
  24. return bytes(s, encoding="utf8")
  25. # 定义一个url和实际要执行的函数的对应关系
  26. list1 = [
  27. ("/index/", index),
  28. ("/home/", home),
  29. ("/time/", timer),
  30. ]
  31. def run_server(environ, start_response):
  32. start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])  # 设置HTTP响应的状态码和头信息
  33. url = environ['PATH_INFO']  # 取到用户输入的url
  34. func = None
  35. for i in list1:
  36. if i[0] == url:
  37. func = i[1]
  38. break
  39. if func:
  40. response = func(url)
  41. else:
  42. response = b"404 not found!"
  43. return [response, ]
  44. if __name__ == '__main__':
  45. httpd = make_server('127.0.0.1', 8090, run_server)
  46. print("我在8090等你哦...")
  47. httpd.serve_forever()
{
'ALLUSERSPROFILE': 'C:\\ProgramData',
'APPDATA': 'C:\\Users\\Administrator\\AppData\\Roaming',
'COMMONPROGRAMFILES': 'C:\\Program Files (x86)\\Common Files',
'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files',
'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files',
'COMPUTERNAME': 'PC-20190328RVNA',
'COMSPEC': 'C:\\Windows\\system32\\cmd.exe',
'FP_NO_HOST_CHECK': 'NO',
'HOMEDRIVE': 'C:',
'HOMEPATH': '\\Users\\Administrator',
'LOCALAPPDATA': 'C:\\Users\\Administrator\\AppData\\Local',
'LOGONSERVER': '\\\\PC-20190328RVNA',
'NUMBER_OF_PROCESSORS': '',
'OS': 'Windows_NT',
'PATH': 'C:\\mcw\\venv\\Scripts;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\软件安装\\Git\\cmd;C:\\mysql\\mysql-5.6.44-winx64\\bin;C:\\python3\\Scripts\\;C:\\python3\\',
'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC',
'PROCESSOR_ARCHITECTURE': 'x86',
'PROCESSOR_ARCHITEW6432': 'AMD64',
'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 58 Stepping 9, GenuineIntel',
'PROCESSOR_LEVEL': '',
'PROCESSOR_REVISION': '3a09',
'PROGRAMDATA': 'C:\\ProgramData',
'PROGRAMFILES': 'C:\\Program Files (x86)',
'PROGRAMFILES(X86)': 'C:\\Program Files (x86)',
'PROGRAMW6432': 'C:\\Program Files',
'PROMPT': '(venv) $P$G',
'PSMODULEPATH': 'C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\',
'PUBLIC': 'C:\\Users\\Public',
'PYCHARM_HOSTED': '',
'PYCHARM_MATPLOTLIB_PORT': '',
'PYTHONIOENCODING': 'UTF-8',
'PYTHONPATH': 'C:\\mcw;C:\\软件安装\\PyCharm 2018.3.5\\helpers\\pycharm_matplotlib_backend',
'PYTHONUNBUFFERED': '',
'SESSIONNAME': 'Console',
'SYSTEMDRIVE': 'C:',
'SYSTEMROOT': 'C:\\Windows',
'TEMP': 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp',
'TMP': 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp',
'USERDOMAIN': 'PC-20190328RVNA',
'USERNAME': 'Administrator',
'USERPROFILE': 'C:\\Users\\Administrator',
'VIRTUAL_ENV': 'C:\\mcw\\venv',
'WINDIR': 'C:\\Windows',
'WINDOWS_TRACING_FLAGS': '',
'WINDOWS_TRACING_LOGFILE': 'C:\\BVTBin\\Tests\\installpackage\\csilogfile.log',
'_OLD_VIRTUAL_PATH': 'C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\杞\ue219欢瀹夎\ue5ca\\Git\\cmd;C:\\mysql\\mysql-5.6.44-winx64\\bin;C:\\python3\\Scripts\\;C:\\python3\\',
'_OLD_VIRTUAL_PROMPT': '$P$G',
'SERVER_NAME': 'PC-20190328RVNA',
'GATEWAY_INTERFACE': 'CGI/1.1',
'SERVER_PORT': '',
'REMOTE_HOST': '',
'CONTENT_LENGTH': '',
'SCRIPT_NAME': '',
'SERVER_PROTOCOL': 'HTTP/1.1',
'SERVER_SOFTWARE': 'WSGIServer/0.2',
'REQUEST_METHOD': 'GET',
'PATH_INFO': '/time/',
'QUERY_STRING': '',
'REMOTE_ADDR': '127.0.0.1',
'CONTENT_TYPE': 'text/plain',
'HTTP_HOST': '127.0.0.1:8090',
'HTTP_CONNECTION': 'keep-alive',
'HTTP_CACHE_CONTROL': 'max-age=0',
'HTTP_UPGRADE_INSECURE_REQUESTS': '',
'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br',
'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9',
'wsgi.input': < _io.BufferedReader name = > ,
'wsgi.errors': < _io.TextIOWrapper name = '<stderr>'
mode = 'w'
encoding = 'UTF-8' > ,
'wsgi.version': (, ),
'wsgi.run_once': False,
'wsgi.url_scheme': 'http',
'wsgi.multithread': True,
'wsgi.multiprocess': False,
'wsgi.file_wrapper': < class 'wsgiref.util.FileWrapper' >
} <
bound method BaseHandler.start_response of < wsgiref.simple_server.ServerHandler object at 0x02A06950 >>
127.0 . . - -[ / Jun / : : ]
"GET /time/ HTTP/1.1"
{
'ALLUSERSPROFILE': 'C:\\ProgramData',
'APPDATA': 'C:\\Users\\Administrator\\AppData\\Roaming',
'COMMONPROGRAMFILES': 'C:\\Program Files (x86)\\Common Files',
'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files',
'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files',
'COMPUTERNAME': 'PC-20190328RVNA',
'COMSPEC': 'C:\\Windows\\system32\\cmd.exe',
'FP_NO_HOST_CHECK': 'NO',
'HOMEDRIVE': 'C:',
'HOMEPATH': '\\Users\\Administrator',
'LOCALAPPDATA': 'C:\\Users\\Administrator\\AppData\\Local',
'LOGONSERVER': '\\\\PC-20190328RVNA',
'NUMBER_OF_PROCESSORS': '',
'OS': 'Windows_NT',
'PATH': 'C:\\mcw\\venv\\Scripts;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\软件安装\\Git\\cmd;C:\\mysql\\mysql-5.6.44-winx64\\bin;C:\\python3\\Scripts\\;C:\\python3\\',
'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC',
'PROCESSOR_ARCHITECTURE': 'x86',
'PROCESSOR_ARCHITEW6432': 'AMD64',
'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 58 Stepping 9, GenuineIntel',
'PROCESSOR_LEVEL': '',
'PROCESSOR_REVISION': '3a09',
'PROGRAMDATA': 'C:\\ProgramData',
'PROGRAMFILES': 'C:\\Program Files (x86)',
'PROGRAMFILES(X86)': 'C:\\Program Files (x86)',
'PROGRAMW6432': 'C:\\Program Files',
'PROMPT': '(venv) $P$G',
'PSMODULEPATH': 'C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\',
'PUBLIC': 'C:\\Users\\Public',
'PYCHARM_HOSTED': '',
'PYCHARM_MATPLOTLIB_PORT': '',
'PYTHONIOENCODING': 'UTF-8',
'PYTHONPATH': 'C:\\mcw;C:\\软件安装\\PyCharm 2018.3.5\\helpers\\pycharm_matplotlib_backend',
'PYTHONUNBUFFERED': '',
'SESSIONNAME': 'Console',
'SYSTEMDRIVE': 'C:',
'SYSTEMROOT': 'C:\\Windows',
'TEMP': 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp',
'TMP': 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp',
'USERDOMAIN': 'PC-20190328RVNA',
'USERNAME': 'Administrator',
'USERPROFILE': 'C:\\Users\\Administrator',
'VIRTUAL_ENV': 'C:\\mcw\\venv',
'WINDIR': 'C:\\Windows',
'WINDOWS_TRACING_FLAGS': '',
'WINDOWS_TRACING_LOGFILE': 'C:\\BVTBin\\Tests\\installpackage\\csilogfile.log',
'_OLD_VIRTUAL_PATH': 'C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\杞\ue219欢瀹夎\ue5ca\\Git\\cmd;C:\\mysql\\mysql-5.6.44-winx64\\bin;C:\\python3\\Scripts\\;C:\\python3\\',
'_OLD_VIRTUAL_PROMPT': '$P$G',
'SERVER_NAME': 'PC-20190328RVNA',
'GATEWAY_INTERFACE': 'CGI/1.1',
'SERVER_PORT': '',
'REMOTE_HOST': '',
'CONTENT_LENGTH': '',
'SCRIPT_NAME': '',
'SERVER_PROTOCOL': 'HTTP/1.1',
'SERVER_SOFTWARE': 'WSGIServer/0.2',
'REQUEST_METHOD': 'GET',
'PATH_INFO': '/favicon.ico',
'QUERY_STRING': '',
'REMOTE_ADDR': '127.0.0.1',
'CONTENT_TYPE': 'text/plain',
'HTTP_HOST': '127.0.0.1:8090',
'HTTP_CONNECTION': 'keep-alive',
'HTTP_PRAGMA': 'no-cache',
'HTTP_CACHE_CONTROL': 'no-cache',
'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
'HTTP_ACCEPT': 'image/webp,image/apng,image/*,*/*;q=0.8',
'HTTP_REFERER': 'http://127.0.0.1:8090/time/',
'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br',
'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9',
'wsgi.input': < _io.BufferedReader name = > ,
'wsgi.errors': < _io.TextIOWrapper name = '<stderr>'
mode = 'w'
encoding = 'UTF-8' > ,
'wsgi.version': (, ),
'wsgi.run_once': False,
'wsgi.url_scheme': 'http',
'wsgi.multithread': True,
'wsgi.multiprocess': False,
'wsgi.file_wrapper': < class 'wsgiref.util.FileWrapper' >
}

打印environ并格式化后显示所有变量

获取环境变量一个键:environ['PATH_INFO']

键的值是后面的值,就是url中获取到的值,: 'PATH_INFO': '/time/',

'PATH': 'C:\\mcw\\venv\\Scripts;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\软件安装\\Git\\cmd;C:\\mysql\\mysql-5.6.44-winx64\\bin;C:\\python3\\Scripts\\;C:\\python3\\',

start_response是个对象:<bound method BaseHandler.start_response of <wsgiref.simple_server.ServerHandler object at 0x02A16930>>

from wsgiref.simple_server import make_server

def run_server(environ, start_response): #2)定义执行函数,传参environ, start_response
start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 4) 设置HTTP响应的状态码和头信息start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) 
url = environ['PATH_INFO'] #3)取到用户输入的url environ['PATH_INFO']
if url=='/home/':
response=("response:"+url).encode('utf-8')
else:
response = b"404 not found!" #)url满足条件返回内容定义
return [response, ] #6)执行函数返回列表,列表一个元素是返回的内容 if __name__ == '__main__':
httpd = make_server('127.0.0.1', 8090, run_server) #1)make_server 做服务传ip端口和执行函数
httpd.serve_forever() #启动这个服务

用wsgiref代替服务端socket创建简易web框架:

1)make_server 做服务传ip端口和执行函数
2)定义执行函数,传参environ, start_response
3)取到用户输入的url environ['PATH_INFO']
4) 设置HTTP响应的状态码和头信息start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) 
5)url满足条件返回内容定义
6)执行函数返回列表,列表一个元素是返回的内容

7)启动这个服务

jinja2

上面的代码实现了一个简单的动态,我完全可以从数据库中查询数据,然后去替换我html中的对应内容,然后再发送给浏览器完成渲染。 这个过程就相当于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: jinja2

下载jinja2:

pip install jinja2
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
</head>
<body>
<h1>姓名:{{name}}</h1>
<h1>爱好:</h1>
<ul>
{% for hobby in hobby_list %}
<li>{{hobby}}</li>
{% endfor %}
</ul>
</body>
</html>

index2.html

使用jinja2渲染index2.html文件:

  1. from wsgiref.simple_server import make_server
  2. from jinja2 import Template
  3. def index(url):
  4. # 读取HTML文件内容
  5. with open("index2.html", "r", encoding="utf8") as f:
  6. data = f.read()
  7. template = Template(data)   # 生成模板文件
  8. ret = template.render({'name': '小郭吹雪', 'hobby_list': ['敲代码', '今日头条', '睡觉']} # 把数据填充到模板中
  9. return bytes(ret, encoding="utf8")
  10. def home(url):
  11. with open("home.html", "r", encoding="utf8") as f:
  12. s = f.read()
  13. return bytes(s, encoding="utf8")
  14. # 定义一个url和实际要执行的函数的对应关系
  15. list1 = [
  16. ("/index/", index),
  17. ("/home/", home),
  18. ]
  19. def run_server(environ, start_response):
  20. start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])  # 设置HTTP响应的状态码和头信息
  21. url = environ['PATH_INFO']  # 取到用户输入的url
  22. func = None
  23. for i in list1:
  24. if i[0] == url:
  25. func = i[1]
  26. break
  27. if func:
  28. response = func(url)
  29. else:
  30. response = b"404 not found!"
  31. return [response, ]
  32. if __name__ == '__main__':
  33. httpd = make_server('127.0.0.1', 8090, run_server)
  34. print("我在8090等你哦...")
  35. httpd.serve_forever()
jinja2使用方法;

1)从jinja2导入模板类
2)实例化模板类,传参为带特别语法的文件。
3)对象.render(字典),把数据填充到模板中
4)支持{{name}}双括号中变量替换为字典键的值,for循环创建语句。
  {'name': '小郭吹雪', 'hobby_list': ['敲代码', '今日头条', '睡觉']

  双花中代表变量,字典的键:<h1>姓名:{{name}}</h1>
  上下花百分。上for循环字典键中列表,下endfor。中间是对for循环的每个变量生成html。
  {% for hobby in hobby_list %}
  <li>{{hobby}}</li>
  {% endfor %}

现在的数据是我们自己手写的,那可不可以从数据库中查询数据,来填充页面呢?

使用pymysql连接数据库:

conn = pymysql.connect(host="127.0.0.1", port=3306, user="root", passwd="xxx", db="xxx", charset="utf8")
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute("select name, age, department_id from userinfo")
user_list = cursor.fetchall()
cursor.close()
conn.close()

创建一个测试的user表:

CREATE TABLE user(
id int auto_increment PRIMARY KEY,
name CHAR(10) NOT NULL,
hobby CHAR(20) NOT NULL
)engine=innodb DEFAULT charset=UTF8;

模板的原理就是字符串替换,我们只要在HTML页面中遵循jinja2的语法规则写上,其内部就会按照指定的语法进行相应的替换,从而达到动态的返回内容。

Django

Django官网下载页面

安装(安装最新LTS版):

pip3 install django==1.11.20

指定安装源(清华源):

pip3 install django==1.11.21 -i https://pypi.tuna.tsinghua.edu.cn/simple

pycharm安装Django,指定版本

shift右击 在当前目录打开cmd

创建一个django项目:

下面的命令创建了一个名为"mysite"的Django 项目:

django-admin startproject mysite

目录介绍:

mysite/
├── manage.py # 管理文件
└── mysite # 项目目录
├── __init__.py
├── settings.py # 配置
├── urls.py # 路由 --> URL和函数的对应关系
└── wsgi.py # runserver命令就使用wsgiref模块做简单的web server

pycharm创建,社区版不支持创建django项目。

file->new project->Django

location项目路径,名称。

解释器

虚拟环境:能隔离开各个项目的环境

不写不自动生成这个目录

修改配置:

运行Django项目:

默认端口8000么?

python manage.py runserver 127.0.0.1:8000
.命令行

切换到项目的根目录下  manage.py

python36 manage.py runserver # http://127.0.0.1:8000/

python36 manage.py runserver    #指定端口启动  在80端口启动

python36 manage.py runserver 0.0.0.0:  #0.0.0.0:80 其它主机可访问,外部主机可访问

直接点击启动关闭

python36 manage.py runserver 0.0.0.0:80#并且settings设置为‘*’,允许所有主机访问

terminate是项目终止,disconnect是断开连接,项目不终止

命令行开启的可以crtl + c终止

disconect点击之后,要停止项目。可以在资源管理器中杀掉进程

多了个文件:

如果启动时报错哪个文件的问题,看看是不是启动错了,启动的不是Django项目而是某个文件:

模板文件配置:

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, "template")], # template文件夹位置
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

静态文件配置:

STATIC_URL = '/static/'  # HTML中使用的静态文件夹前缀
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"), # 静态文件存放位置
]

看不明白?有图有真相:

刚开始学习时可在配置文件中暂时禁用csrf中间件,方便表单提交测试。

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

Django基础必备三件套:

from django.shortcuts import HttpResponse, render, redirect

HttpResponse

内部传入一个字符串参数,返回给浏览器。

例如:

def index(request):
# 业务逻辑代码
return HttpResponse("OK")

创建一个页面

from django.contrib import admin
from django.urls import path from django.shortcuts import HttpResponse,render
def index(request):
return HttpResponse('index')
urlpatterns = [
path('admin/', admin.site.urls),
path('index/', index),
]

render

除request参数外还接受一个待渲染的模板文件和一个保存具体数据的字典参数。

将数据填充进模板文件,最后把结果返回给浏览器。(类似于我们上面用到的jinja2)

例如:

def index(request):
# 业务逻辑代码
return render(request, "index.html", {"name": "alex", "hobby": ["烫头", "泡吧"]})
from django.contrib import admin
from django.urls import path from django.shortcuts import HttpResponse,render
def index(request):
# return HttpResponse('index')
return render(request,'index.html')
urlpatterns = [
path('admin/', admin.site.urls),
path('index/', index),
]

redirect

接受一个URL参数,表示跳转到指定的URL。

例如:

def index(request):
# 业务逻辑代码
return redirect("/home/")

重定向是怎么回事?

启动Django报错:

Django 启动时报错 UnicodeEncodeError ...

报这个错误通常是因为计算机名为中文,改成英文的计算机名重启下电脑就可以了。

django框架介绍安装-自写框架的更多相关文章

  1. ORM框架介绍——什么是ORM框架?

    1.什么是ORM?对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术.ORM框架是连接数据库的桥梁,只要提供 ...

  2. fitnesse - 框架介绍

    fitnesse - 框架介绍 2017-09-29 目录: 1 fitnesse是什么?2 框架介绍3 与junit.testng比较,fitnesse教其他框架有什么优势 1 fitnesse是什 ...

  3. 『居善地』接口测试 — 12、Moco框架介绍

    目录 1.Mock功能介绍 2.Moco框架介绍 3.Moco框架在接口测试中的作用 4.Moco框架的优点 5.Moco框架的下载与启动 (1)Moco框架的下载 (2)Moco框架的启动 1.Mo ...

  4. [翻译]Spring框架参考文档(V4.3.3)-第二章Spring框架介绍 2.1 2.2 翻译--2.3待继续

    英文链接:http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/overview.ht ...

  5. 3.3.1 MyBatis框架介绍

    MyBatis框架介绍 1. 什么是框架 (1) 框架是偷懒的程序员将代码进行封装, 之后进行重复使用的过程. (2) 框架其实是一个半成品, 以连接数据库为例, 连接数据库使用的驱动, url, 用 ...

  6. golang学习笔记17 爬虫技术路线图,python,java,nodejs,go语言,scrapy主流框架介绍

    golang学习笔记17 爬虫技术路线图,python,java,nodejs,go语言,scrapy主流框架介绍 go语言爬虫框架:gocolly/colly,goquery,colly,chrom ...

  7. SQL Ssever 安装.NET3.5 框架

    SQL Ssever 安装.NET3.5 框架 我们在安装 SQL Sever 2014 的时候必须要安装 .NET3.5 框架,然后才能继续安装 SQL Server 2014. 您可能在安装 SQ ...

  8. Django简介以及安装

    目录 前言 Web框架本质 服务器和应用程序 基于第三方模块实现Web框架 Python三大主流Web框架 django flask tornado Django框架介绍 安装 创建项目 创建App ...

  9. Django 框架介绍

    Django 框架介绍 MVC框架和MTV框架 简单了解一下什么是MVC框架.MVC(Model View Controller),是模型(model)-视图(view)-控制器(controller ...

随机推荐

  1. 【Redis】Redis 发布订阅

    Redis 发布订阅介绍 Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息. Redis 客户端可以订阅任意数量的频道. 下图展示了频道 c ...

  2. GitLab - 一些基础使用

    1 - GitLab角色权限 1.1 组(同一组成员的行为权限) 管理员创建不同的分组,然后设定分组的负责人(Owner) Owner可以添加组员,为组创建项目,指定项目的负责人 项目负责人可以添加项 ...

  3. Consul ACL集群配置说明以及ACL Token的用法

    在上一篇文章里面,我们讲了如何搭建带有Acl控制的Consul集群.这一篇文章主要讲述一下上一篇文章那一大串配置文件的含义. 1.配置说明#1.1 勘误上一篇文章关于机器规划方面,consul cli ...

  4. 【VS开发】#pragma pack(push,1)与#pragma pack(1)的区别

    这是给编译器用的参数设置,有关结构体字节对齐方式设置, #pragma pack是指定数据在内存中的对齐方式. #pragma pack (n)             作用:C编译器将按照n个字节对 ...

  5. QT实现支持加密的Sqlite数据库引擎

    Sqlite数据库使用很广泛,我们经常会在发布一些小型软件的时候使用它,因为它不需要安装服务器.QT默认的数据库引擎是支持SQLITE数据库的,但并不支持对数据库加密,不加密的Sqlite数据库任何人 ...

  6. (CSDN迁移) 输入一个链表,从尾到头打印链表每个节点的值

    题目描述 输入一个链表,从尾到头打印链表每个节点的值. 思路1. 翻转链表,使用java自带的翻转函数或者从头到尾依次改变链表的节点指针 /** * public class ListNode { * ...

  7. LeetCode 872. 叶子相似的树(Leaf-Similar Trees)

    872. 叶子相似的树 872. Leaf-Similar Trees 题目描述 请考虑一颗二叉树上所有的叶子,这些叶子的值按从左到右的顺序排列形成一个叶值序列. LeetCode872. Leaf- ...

  8. [转帖]UID卡、CUID卡、FUID卡、UFUID卡的区别及写入方式

    UID卡.CUID卡.FUID卡.UFUID卡的区别及写入方式   版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://bl ...

  9. Linux中进行jdk的安装+Tomcat安装+mysql的安装

    1.上传所需要的文件(这里事先准备好了jdk和tomcat的安装包,mysql需要在线安装) jdk的安装包名称:jdk-8u151-linux-x64.tar.gz tomcat 的安装包名称:ap ...

  10. Java开发笔记(一百一十四)利用Socket传输文本消息

    前面介绍了HTTP协议的网络通信,包括接口调用.文件下载和文件上传,这些功能固然已经覆盖了常见的联网操作,可是HTTP协议拥有专门的通信规则,这些规则一方面有利于维持正常的数据交互,另一方面不可避免地 ...