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

1、自定义web框架

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

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

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

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

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

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

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

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

输出:

  1. b'GET / HTTP/1.1
    Host: 127.0.0.1:8080
    Connection: keep-alive
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
    DNT: 1
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9
    Cookie: csrftoken=RKBXh1d3M97iz03Rpbojx1bR6mhHudhyX5PszUxxG3bOEwh1lxFpGOgWN93ZH3zv\r\n\r\n'

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

响应相关信息可以在浏览器调试窗口的network标签页中看到。

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

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

HTTP协议介绍

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

每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。 HTTP响应的Header中有一个 Content-Type表明响应的内容格式。如 text/html表示HTML网页。

HTTP GET请求的格式:

HTTP响应的格式:

2、自定义web框架

经过上面的补充学习,我们知道了要想让我们自己写的web server端正经起来,必须要让我们的Web server在给客户端回复消息的时候按照HTTP协议的规则加上响应状态行,

这样我们就实现了一个正经的Web框架了。

  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.  
  6. while True:
  7. conn, addr = sock.accept()
  8. data = conn.recv(8096)
  9. # 给回复的消息加上响应状态行
  10. conn.send(b"HTTP/1.1 200 OK\r\n\r\n")
  11. conn.send(b"OK")
  12. conn.close()

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

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

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

这样就结束了吗? 如何让我们的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.  
  9. while 1:
  10. # 等待连接
  11. conn, add = sk.accept()
  12. data = conn.recv(8096) # 接收客户端发来的消息
  13. # 从data中取到路径
  14. data = str(data, encoding="utf8") # 把收到的字节类型的数据转换成字符串
  15. # 按\r\n分割
  16. data1 = data.split("\r\n")[0]
  17. url = data1.split()[1] # url是我们从浏览器发过来的消息中分离出的访问路径
  18. conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 因为要遵循HTTP协议,所以回复的消息也要加状态行
  19. # 根据不同的路径返回不同内容
  20. if url == "/index/":
  21. response = b"index"
  22. elif url == "/home/":
  23. response = b"home"
  24. else:
  25. response = b"404 not found!"
  26. conn.send(response)
  27. conn.close()

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

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

但是问题又来了,如果有很多很多路径要判断怎么办?难道要挨个写if判断? 当然不用,我们有更聪明的办法。

  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. # 将返回不同的内容部分封装成函数
  10. def index(url):
  11. s = "这是{}页面!".format(url)
  12. return bytes(s, encoding="utf8")
  13.  
  14. def home(url):
  15. s = "这是{}页面!".format(url)
  16. return bytes(s, encoding="utf8")
  17.  
  18. while 1:
  19. # 等待连接
  20. conn, add = sk.accept()
  21. data = conn.recv(8096) # 接收客户端发来的消息
  22. # 从data中取到路径
  23. data = str(data, encoding="utf8") # 把收到的字节类型的数据转换成字符串
  24. # 按\r\n分割
  25. data1 = data.split("\r\n")[0]
  26. url = data1.split()[1] # url是我们从浏览器发过来的消息中分离出的访问路径
  27. conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 因为要遵循HTTP协议,所以回复的消息也要加状态行
  28. # 根据不同的路径返回不同内容,response是具体的响应体
  29. if url == "/index/":
  30. response = index(url)
  31. elif url == "/home/":
  32. response = home(url)
  33. else:
  34. response = b"404 not found!"
  35.  
  36. conn.send(response)
  37. conn.close()

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

看起来上面的代码还是要挨个写if判断,怎么办?我们还是有办法!(只要思想不滑坡,方法总比问题多!)

  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. # 将返回不同的内容部分封装成函数
  10. def index(url):
  11. s = "这是{}页面!".format(url)
  12. return bytes(s, encoding="utf8")
  13. def home(url):
  14. s = "这是{}页面!".format(url)
  15. return bytes(s, encoding="utf8")
  16.  
  17. # 定义一个url和实际要执行的函数的对应关系
  18. list1 = [
  19. ("/index/", index),
  20. ("/home/", home),
  21. ]
  22.  
  23. while 1:
  24. # 等待连接
  25. conn, add = sk.accept()
  26. data = conn.recv(8096) # 接收客户端发来的消息
  27. # 从data中取到路径
  28. data = str(data, encoding="utf8") # 把收到的字节类型的数据转换成字符串
  29. # 按\r\n分割
  30. data1 = data.split("\r\n")[0]
  31. url = data1.split()[1] # url是我们从浏览器发过来的消息中分离出的访问路径
  32. conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 因为要遵循HTTP协议,所以回复的消息也要加状态行
  33. # 根据不同的路径返回不同内容
  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.  
  44. # 返回具体的响应消息
  45. conn.send(response)
  46. conn.close()

6、返回具体的HTML文件

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

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

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

7、让网页动态起来

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

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

  1. """
  2. 根据URL中不同的路径返回不同的内容--函数进阶版
  3. 返回HTML页面
  4. 让网页动态起来
  5. """
  6. import socket
  7. import time
  8.  
  9. sk = socket.socket()
  10. sk.bind(("127.0.0.1", 8080)) # 绑定IP和端口
  11. sk.listen() # 监听
  12.  
  13. # 将返回不同的内容部分封装成函数
  14. def index(url):
  15. with open("index.html", "r", encoding="utf8") as f:
  16. s = f.read()
  17. now = str(time.time())
  18. s = s.replace("@@oo@@", now) # 在网页中定义好特殊符号,用动态的数据去替换提前定义好的特殊符号
  19. return bytes(s, encoding="utf8")
  20.  
  21. def home(url):
  22. with open("home.html", "r", encoding="utf8") as f:
  23. s = f.read()
  24. return bytes(s, encoding="utf8")
  25.  
  26. # 定义一个url和实际要执行的函数的对应关系
  27. list1 = [
  28. ("/index/", index),
  29. ("/home/", home),
  30. ]
  31.  
  32. while 1:
  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 i in list1:
  45. if i[0] == url:
  46. func = i[1]
  47. break
  48. if func:
  49. response = func(url)
  50. else:
  51. response = b"404 not found!"
  52.  
  53. # 返回具体的响应消息
  54. conn.send(response)
  55. conn.close()

好了,在这停顿...

服务器程序和应用程序

对于真实开发中的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.  
  8. import time
  9. from wsgiref.simple_server import make_server
  10.  
  11. # 将返回不同的内容部分封装成函数
  12. def index(url):
  13. with open("index.html", "r", encoding="utf8") as f:
  14. s = f.read()
  15. now = str(time.time())
  16. s = s.replace("@@oo@@", now)
  17. return bytes(s, encoding="utf8")
  18.  
  19. def home(url):
  20. with open("home.html", "r", encoding="utf8") as f:
  21. s = f.read()
  22. return bytes(s, encoding="utf8")
  23.  
  24. # 定义一个url和实际要执行的函数的对应关系
  25. list1 = [
  26. ("/index/", index),
  27. ("/home/", home),
  28. ]
  29.  
  30. def run_server(environ, start_response):
  31. start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息
  32. url = environ['PATH_INFO'] # 取到用户输入的url
  33. func = None
  34. for i in list1:
  35. if i[0] == url:
  36. func = i[1]
  37. break
  38. if func:
  39. response = func(url)
  40. else:
  41. response = b"404 not found!"
  42. return [response, ]
  43.  
  44. if __name__ == '__main__':
  45. httpd = make_server('127.0.0.1', 8090, run_server)
  46. print("我在8090等你哦...")
  47. httpd.serve_forever()

jinja2

上面的代码实现了一个简单的动态,我完全可以从数据库中查询数据,然后去替换我html中的对应内容,然后再发送给浏览器完成渲染。

这个过程就相当于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: jinja2

下载jinja2:

  1. pip install jinja2
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="x-ua-compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1">
  7. <title>Title</title>
  8. </head>
  9. <body>
  10. <h1>姓名:{{name}}</h1>
  11. <h1>爱好:</h1>
  12. <ul>
  13. {% for hobby in hobby_list %}
  14. <li>{{hobby}}</li>
  15. {% endfor %}
  16. </ul>
  17. </body>
  18. </html>

index2.html文件

使用jinja2渲染index2.html文件:

  1. from wsgiref.simple_server import make_server
  2. from jinja2 import Template
  3.  
  4. def index():
  5. with open("index2.html", "r") as f:
  6. data = f.read()
  7. template = Template(data) # 生成模板文件
  8. ret = template.render({"name": "duoduo", "hobby_list": ["泡吧"]}) # 把数据填充到模板里面
  9. return [bytes(ret, encoding="utf8"), ]
  10.  
  11. def home():
  12. with open("home.html", "rb") as f:
  13. data = f.read()
  14. return [data, ]
  15.  
  16. # 定义一个url和函数的对应关系
  17. URL_LIST = [
  18. ("/index/", index),
  19. ("/home/", home),
  20. ]
  21.  
  22. def run_server(environ, start_response):
  23. start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息
  24. url = environ['PATH_INFO'] # 取到用户输入的url
  25. func = None # 将要执行的函数
  26. for i in URL_LIST:
  27. if i[0] == url:
  28. func = i[1] # 去之前定义好的url列表里找url应该执行的函数
  29. break
  30. if func: # 如果能找到要执行的函数
  31. return func() # 返回函数的执行结果
  32. else:
  33. return [bytes("404没有该页面", encoding="utf8"), ]
  34.  
  35. if __name__ == '__main__':
  36. httpd = make_server('', 8000, run_server)
  37. print("Serving HTTP on port 8000...")
  38. httpd.serve_forever()

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

使用pymysql连接数据库:

  1. improt pymysql
    conn = pymysql.connect(host="127.0.0.1", port=3306, user="root", password="123", db="xxx", charset="utf8")
  2. cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
  3. cursor.execute("select name, age, department_id from userinfo")
  4. user_list = cursor.fetchall()
  5. cursor.close()
  6. conn.close()

创建一个测试的user表:

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

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

python3开发进阶-Web框架的前奏的更多相关文章

  1. python3开发进阶-Django框架的自带认证功能auth模块和User对象的基本操作

    阅读目录 auth模块 User对象 认证进阶 一.auth模块 from django.contrib import auth django.contrib.auth中提供了许多方法,这里主要介绍其 ...

  2. python3开发进阶-Django框架的起飞加速一(ORM)

    阅读目录 ORM介绍 Django中的ORM ORM中的Model ORM的操作 一.ORM介绍 1.ORM概念 对象关系映射(Object Relational Mapping,简称ORM)模式是一 ...

  3. python3开发进阶-Django框架起飞前的准备

    阅读目录 安装 创建项目 运行 文件配置的说明 三个组件 一.安装(安装最新LTS版) Django官网下载页面 根据官方的图版本,我们下载1.11版本的,最好用! 有两种下载方式一种直接cmd里: ...

  4. python3开发进阶-Django框架的Form表单系统和基本操作

    阅读目录 什么是Form组件 常用字段和插件 自定义校验的方式 补充进阶 一.什么是Form组件 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标 ...

  5. python3开发进阶-Django框架的中间件的五种用法和逻辑过程

    阅读目录 什么是中间件 中间件的执行流程 中间件的逻辑过程 一.什么是中间件? 官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子.它是一个轻量.低级别的插件系统,用于在全局范围 ...

  6. python3开发进阶-Django框架的详解

    一.MVC框架和MTV框架 MVC,全名是Model View Controller,是软件工程中的一种软件架构模式,把软件系统分为三个基本部分: 模型(Model).视图(View)和控制器(Con ...

  7. python3开发进阶-Django框架学习前的小项目(一个简单的学员管理系统)

    ''' 自己独立写一个学员管理系统 表结构: 班级表: -id -grade_name 学生表: -id -student_name -grade 关联外键班级表 老师表: -id -teacher_ ...

  8. python3开发进阶-Django框架中form的查看校验方法is_valid()的源码,自定义验证方法

    form表单的校验方法is_valid() 点开我们发现这个函数里面只有两个方法方法,最终返回True or False 我们点进.is_bound属性,里面判断传输的数据不是空和上传文件不是空 点进 ...

  9. python3开发进阶-Djamgo框架中的JSON和AJAX

    阅读目录 什么是JSON 什么是AJAX AJAX常见的应用情景 jQery实现AJAX AJAX请求如何设置csrf_token AJAX上传文件 补充Django内置的serializers 一. ...

随机推荐

  1. php魔术方法的使用

    本文测试环境为 php5.5.12 一.__get .__set 将对象的属性进行接管. 一般来说,总是把类的属性定义为private,但是对属性的读取和赋值操作非常频繁,在php5+,预定义__se ...

  2. SpringMVC学习 -- ModelAndView , Model , ModelMap , Map 及 @SessionAttributes 的使用

    输出模型数据: ModelAndView:处理方法返回值类型为 ModelAndView 时 , 其中包含视图和模型信息.方法体即可通过该对象添加模型数据 , 即 SpringMVC 会把 Model ...

  3. nginx 设置client header 的大小与400错误

    nginx默认的header长度上限是4k,如果超过了这个值 如果header头信息请求超过了,nginx会直接返回400错误可以通过以下2个参数来调整nginx的header上限 client_he ...

  4. 【HDU5785】Interesting [Manacher]

    Interesting Time Limit: 30 Sec  Memory Limit: 256 MB[Submit][Status][Discuss] Description Input Outp ...

  5. 1552: [Cerc2007]robotic sort

    这道题用splay写 先离散化数据保证按题目所述顺序来写 按原序作为键值建树 维护区间最小值去跑 每次将i的位置 和 n的位置x和y找出来后 将x旋转到root y旋转到x的有儿子 这时y的左子树就是 ...

  6. java中 快捷键输入System.out.println();

    syso 然后:alt+ /(就是问号键)

  7. python学习笔记 可变参数关键字参数**kw相关学习

    在Python中可以定义可变参数,顾名思义,可变参数就是传入参数是可变的.可以是任意个,以一个简单的数学编程为例,计算 sum = a * a + b * b + .....z * z 函数定义可以如 ...

  8. Android源码的BUG

    在Android系统移植过程中,遇到很多源码上的BUG.但是我们看到市面上都是没有这些问题的.难道这些BUG在每个开发商都要经历一次解BUG的过程吗?Android释放的源码是否是最新的?暂时没有想法 ...

  9. 输入子系统--event层分析【转】

    转自:http://blog.csdn.net/beyondioi/article/details/9186723 ########################################## ...

  10. Linux驱动 读写文件【转】

    转自:http://blog.csdn.net/h_armony/article/details/7546624 在VFS的支持下,用户态进程读写任何类型的文件系统都可以使用read和write着两个 ...