前言:了解一件事情本质的那一瞬间总能让我得到巨大的愉悦感,但愿这篇文章也能帮助到您。html
目的:本文主要简单介绍Web开发中三大基本功能:Socket实现、路由系统、模板引擎渲染。python
进入正题。编程
Web开发中最基础的三大功能分别是:浏览器
下面将对这三部分一一说明。服务器
既然几乎全部的Web开发框架底层都是由Socket实现的,咱们就从Socket编程开始,用Socket实现一个服务端和浏览器进行通讯(细想一下,这就是Web服务最基本的需求了吧)。网络
# 例1
import socket
# 生成一个socket对象 server = socket.socket() # 绑定机器的ip端口 server.bind(("127.0.0.1", 8001)) # 配置最多只能有五个请求在等待链接 server.listen(5) while True: # 阻塞,等待接受请求 conn, addr = server.accept() # 创建链接后接受数据,规定一次数据大小为8096字节 data = conn.recv(8096)
print(data) # 在该链接通道中发送数据,注意要是字节形式 conn.send(b"HTTP/1.1 200 OK\r\n\r\n") conn.send(b"Hello World!") # 关闭链接 conn.close()
这段代码实现了一个Socket服务端,server.accept()会让服务器阻塞等待客户端的链接,当接收到链接请求,就返回数据。下面用浏览器发送链接请求:app
能够看到,浏览器已经成功接收到了Socket服务端发来"Hello World",成功解析并显示在了网页上,一个最基本的Web服务就顺利完成啦!框架
Http/Https协议,简单的对象访问协议,对应于应用层,简单而言就是一个你们都遵循的格式、规范,根据这个规范咱们能够获取本身所需的信息。Http协议是基于TCP连接的。但与TCP一直保存链接不主动断开相比,HTTP/HTTPS的链接在一次传输后就会断开,而且不会保存链接信息,下次再链接时没有上次链接的状态,因此说特色是无状态、短链接。socket
先来看看上面代码中在服务端接收到链接请求的内容(具体对应代码:data = conn.recv(8096))函数
b'GET / HTTP/1.1\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: zh-CN\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362\r\nAccept-Encoding: gzip, deflate\r\nHost: 127.0.0.1:8001\r\nConnection: Keep-Alive\r\n\r\n'
解释:
'method url protocol\r\nheaders-param1\r\nheaders-param2\r\nheaders-param3...headers-paramn\r\n\r\n'
由此咱们能够窥见一点HTTP/HTTPS协议内容,它规定GET请求的格式就如上面所示,那么咱们拿到请求的数据后,经过简单的字符串处理就能拿到这个请求的method,url,protocol,headers,例如url = data.split('\r\n')[0].split(' ')[1]。
此外,由于这个请求只是一个简单的GET请求,请求信息到\r\n\r\n就结束了,事实上\r\n\r\n也是一个分割符,分割的是请求头和请求体,当一个请求是POST请求时,POST的参数就在请求体中,也就是说post_params = data.split("\r\n\r\n")[1]。而在例1中,也用"\r\n\r\n"分割开了响应头和响应体:conn.send(b"HTTP/1.1 200 OK\r\n\r\n"),conn.send(b"Hello World!")。
路由系统要完成的功能是:根据不一样的请求信息作不一样的数据处理,返回不一样的数据响应。例如分别访问“http://www.javashuo.com/article/p-zuajxdaq-kn.html”、“https://i-beta.cnblogs.com/posts/edit”,请求都会发送到博客园的服务器,根据url的不一样,第一个请求会响应对应文章内容,而第二个请求会响应编辑后台。
接回上面的话题,当接收到一个遵循HTTP/HTTPS协议的请求时,咱们能够经过字符串处理获取到请求的url,而后根据不一样的url调用不一样功能的模块或者函数来处理该请求,生成不一样的数据来响应请求。如今来改写咱们的Socket服务端,加入路由系统:
# 例2 import socket def index(): return b'Hello World!' def func1(): return b"I'm not hungry yet!" def func2(): return b"Cheers!" # 路由表 routers = [ ('/', index), ('/eat', func1), ('/drank', func2), ] server = socket.socket() server.bind(("127.0.0.1", 8001)) server.listen(5) while True: conn, addr = server.accept() data = str(conn.recv(8096), encoding='utf-8') headers, bodys = data.split('\r\n\r\n') # 分割出请求头,请求体 temp_list = headers.split('\r\n') method, url, protocal = temp_list[0].split(' ') # 分割出请求方法,url,协议 conn.send(b'HTTP/1.1 200 OK\r\n\r\n') func_name = None for item in routers: # 路由匹配,根据url获取相应的处理函数 if url == item[0]: func_name = item[1] break if func_name: response = func_name() else: response = b'404 not found' # 假如url不在路由系统中,模拟返回404 conn.send(response) conn.close()
在例2中,咱们加入了路由表,经过字符串处理分割出请求url,并根据url去匹配路由表,找到合适的处理函数,若是没有则返回404,再看看浏览器访问结果:
能够看到,例2的Socket服务器已经可以根据请求url的不一样调用合适的处理函数来处理返回正确的数据了,这就是Web开发框架中路由系统的本质!
由上面的例子中咱们能够发现,Socket编程中的数据传输的数据都是字节,而咱们获取请求信息和响应信息构造其实都是字符串的处理。前面的例子咱们响应的数据是简单的字节串,此外咱们响应的数据还能够是HTML代码,这些代码传输到浏览器等客户端时会被渲染成咱们经常看到的网页。看下个例子:
# 例3 # server.py import socket def index(): with open('index.html', 'r') as f: # 读取html文件内容做为响应体数据 response = f.read() return response.encode('utf-8') # 路由表 routers = [ ('/', index), ] server = socket.socket() server.bind(("127.0.0.1", 8001)) server.listen(5) while True: conn, addr = server.accept() data = str(conn.recv(8096), encoding='utf-8') headers, bodys = data.split('\r\n\r\n') temp_list = headers.split('\r\n') method, url, protocal = temp_list[0].split(' ') conn.send(b'HTTP/1.1 200 OK\r\n\r\n') func_name = None for item in routers: if url == item[0]: func_name = item[1] break if func_name: response = func_name() else: response = b'404 not found' conn.send(response) conn.close() # index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>"HELLO WORLD"</title> </head> <body> <table border="1"> <tr> <th>Month</th> <th>Savings</th> </tr> <tr> <td>January</td> <td>$100</td> </tr> </table> </body> </html>
在这个例子中,咱们修改了index(),在例2中的直接返回字节串做为响应体数据,而例3中咱们读取了index.html文件内容做为响应体数据返回,在浏览器中已被渲染成一个带表格的网页了。在Web开发中,这个html文件就称之为模板,这种一成不变的网页,称之为静态网页。
在本例中咱们已经实现渲染静态页面了,可是目前大部分的网页并非静态的,存在着大量的动态数据,动态网页的渲染须要咱们获取最新的数据,拼接到模板合适的位置,而后做为响应体数据返回。动态拼接的功能咱们能够经过字符串替换来实现,在模板中合适的位置用特殊字符来作占位符,当要响应数据时候,拿到最新的数据替换掉模板中的占位符,便可作到用最新的数据做为返回结果了。看下个例子:
# 例4 # server.py import socket import datetime def index(): with open('index2.html', 'r') as f: # 读取html文件内容做为响应体数据 response = f.read() response = response.replace('@temp@', str(datetime.datetime.now())) # 获取当前时间,替换html文件内容中的占位符 return response.encode('utf-8') # 路由表 routers = [ ('/', index), ] server = socket.socket() server.bind(("127.0.0.1", 8001)) server.listen(5) while True: conn, addr = server.accept() data = str(conn.recv(8096), encoding='utf-8') headers, bodys = data.split('\r\n\r\n') temp_list = headers.split('\r\n') method, url, protocal = temp_list[0].split(' ') conn.send(b'HTTP/1.1 200 OK\r\n\r\n') func_name = None for item in routers: if url == item[0]: func_name = item[1] break if func_name: response = func_name() else: response = b'404 not found' conn.send(response) conn.close()
# index2.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>"HELLO WORLD"</title> </head> <body> <table border="1"> <tr> <th>Month</th> <th>Savings</th> </tr> <tr> <td>January</td> <td>$100</td> </tr> <tr> <td>time</td> <!--用特殊符号做为占位符--> <td>@temp@</td> </tr> </table> </body> </html>
在这个例子中,咱们修改了index.html,添加了一个占位符,并在server.py中对这个占位符用当前时间替换,因而在新的访问结果中,能够看到网页已经显示了最新的时间,用简单的字符串替换咱们就实现了动态网页的模板渲染啦!这其实就是模板渲染的本质,用于模板渲染的模块称之为模板引擎,固然咱们不须要本身实现,经常使用的python模板引擎有:jinjia2。
介绍完基本Web框架功能的本质后,咱们来简单聊聊Python Web框架。若是按照上面介绍的Web开发基本功能来分类,能够分为三类:
固然Web框架除了以上介绍的基础功能外还实现了不少其余的功能,正是有了这些利器,咱们的Web开发才能驾轻就熟。