接下来咱们学习的目的是为了开发一个Web应用程序,而Web应用程序是基于B/S架构的,其中B指的是浏览器,负责向S端发送请求信息,而S端会根据接收到的请求信息返回相应的数据给浏览器,须要强调的一点是:S端由server和application两大部分构成,如图所示:html
上图:Web应用组成python
咱们无需开发浏览器(本质即套接字客户端),只须要开发S端便可,S端的本质就是用套接字实现的,接下来将借助Python
语言手撸一个socket
编程web
1.0
server.py
数据库
import socket def make_server(ip, port, app): # 表明server sock = socket.socket() sock.bind((ip, port)) sock.listen(5) print('Starting development server at http://%s:%s/' %(ip,port)) while True: conn, addr = sock.accept() # 一、接收浏览器发来的请求信息 recv_data = conn.recv(1024) # print(recv_data.decode('utf-8')) # 二、将请求信息直接转交给application res = app(recv_data) # 三、向浏览器返回消息(此处并无按照http协议返回) conn.send(res) conn.close() def app(environ): # 表明application # 处理业务逻辑 return b'hello world' if __name__ == '__main__': make_server('127.0.0.1', 8008, app) # 在客户端浏览器输入:http://127.0.0.1:8008 若是报错(请注意:须要使用谷歌浏览器)
目前Server
端已经能够正常接收浏览器发来的请求消息了,可是浏览器在接收到S端回复的响应消息b'hello world'
时却没法正常解析 ,由于浏览器与S端之间收发消息默认使用的应用层协议是HTTP
,浏览器默认会按照HTTP
协议规定的格式发消息,而S端也必须按照HTTP协议的格式回消息才行,因此接下来咱们详细介绍HTTP
协议django
HTTP协议详解连接地址:HTTP协议详解连接地址编程
2.0
server.py
浏览器
import socket def make_server(ip, port, app): # 表明server sock = socket.socket() sock.bind((ip, port)) sock.listen(5) print('Starting development server at http://%s:%s/' %(ip,port)) while True: conn, addr = sock.accept() # 一、接收并处理浏览器发来的请求信息 # 1.1 接收浏览器发来的http协议的消息 recv_data = conn.recv(1024) # 1.2 对http协议的消息加以处理,简单示范以下 ll=recv_data.decode('utf-8').split('\r\n') head_ll=ll[0].split(' ') environ={} environ['PATH_INFO']=head_ll[1] environ['method']=head_ll[0] # 2:将请求信息处理后的结果environ交给application,这样application便无需再关注请求信息的处理,能够更加专一于业务逻辑的处理 res = app(environ) # 3:按照http协议向浏览器返回消息 # 3.1 返回响应首行 conn.send(b'HTTP/1.1 200 OK\r\n') # 3.2 返回响应头(能够省略) conn.send(b'Content-Type: text/html\r\n\r\n') # 3.3 返回响应体 conn.send(res) conn.close() def app(environ): # 表明application # 处理业务逻辑 return b'hello world' if __name__ == '__main__': make_server('127.0.0.1', 8008, app)
此时,重启S端后,再在客户端浏览器输入:http://127.0.0.1:8008 即可以看到正常结果hello world了。架构
咱们不只能够回复hello world
这样的普通字符,还能够夹杂HTML
标签,浏览器在接收到消息后会对解析出的HTML
标签加以渲染app
3.0
server.py
框架
# server.py import socket def make_server(ip, port, app): sock = socket.socket() sock.bind((ip, port)) sock.listen(5) print('Starting development server at http://%s:%s/' %(ip,port)) while True: conn, addr = sock.accept() recv_data = conn.recv(1024) ll=recv_data.decode('utf-8').split('\r\n') head_ll=ll[0].split(' ') environ={} environ['PATH_INFO']=head_ll[1] environ['method']=head_ll[0] res = app(environ) conn.send(b'HTTP/1.1 200 OK\r\n') conn.send(b'Content-Type: text/html\r\n\r\n') conn.send(res) conn.close() def app(environ): # 返回html标签 return b'<h1>hello web</h1><img src="https://www.baidu.com/img/bd_logo1.png"></img>' if __name__ == '__main__': make_server('127.0.0.1', 8008, app)
4.0
更进一步咱们还能够返回一个文件,例如time.html
,内容以下
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>{{ time }}</h2> </body> </html>
server.py
# S端 import socket def make_server(ip, port, app): # 表明server sock = socket.socket() sock.bind((ip, port)) sock.listen(5) print('Starting development server at http://%s:%s/' %(ip,port)) while True: conn, addr = sock.accept() recv_data = conn.recv(1024) ll=recv_data.decode('utf-8').split('\r\n') head_ll=ll[0].split(' ') environ={} environ['PATH_INFO']=head_ll[1] environ['method']=head_ll[0] res = app(environ) conn.send(b'HTTP/1.1 200 OK\r\n') conn.send(b'Content-Type: text/html\r\n\r\n') conn.send(res) conn.close() def app(environ): # 处理业务逻辑:打开文件,读取文件内容并返回 with open('timer.html', 'r', encoding='utf-8') as f: data = f.read() return data.encode('utf-8') if __name__ == '__main__': make_server('127.0.0.1', 8008, app)
5.0
上述S端为浏览器返回的都是静态页面(内容都固定的),咱们还能够返回动态页面(内容是变化的)
server.py
# S端 import socket def make_server(ip, port, app): # 表明server sock = socket.socket() sock.bind((ip, port)) sock.listen(5) print('Starting development server at http://%s:%s/' %(ip,port)) while True: conn, addr = sock.accept() recv_data = conn.recv(1024) ll=recv_data.decode('utf-8').split('\r\n') head_ll=ll[0].split(' ') environ={} environ['PATH_INFO']=head_ll[1] environ['method']=head_ll[0] res = app(environ) conn.send(b'HTTP/1.1 200 OK\r\n') conn.send(b'Content-Type: text/html\r\n\r\n') conn.send(res) conn.close() def app(environ): # 处理业务逻辑 with open('timer.html', 'r', encoding='utf-8') as f: data = f.read() import time now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) data = data.replace('{{ time }}', now) # 字符串替换 return data.encode('utf-8') if __name__ == '__main__': make_server('127.0.0.1', 8008, app) # 在浏览器输入http://127.0.0.1:8008,每次刷新都会看到不一样的时间
综上案例咱们能够发现一个规律,在开发S端时,server
的功能是复杂且固定的(处理socket
消息的收发和http
协议的处理),而app
中的业务逻辑却各不相同(不一样的软件就应该有不一样的业务逻辑),重复开发复杂且固定的server
是毫无心义的,有一个wsgiref
模块帮咱们写好了server
的功能,这样咱们便只须要专一于app
功能的编写便可
wsgi
# wsgiref实现了server,即make_server from wsgiref.simple_server import make_server def app(environ, start_response): # 表明application # 一、返回http协议的响应首行和响应头信息 start_response('200 OK', [('Content-Type', 'text/html')]) # 二、处理业务逻辑:根据请求url的不一样返回不一样的页面内容 if environ.get('PATH_INFO') == '/index': with open('index.html','r', encoding='utf-8') as f: data=f.read() elif environ.get('PATH_INFO') == '/timer': with open('timer.html', 'r', encoding='utf-8') as f: data = f.read() import time now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) data = data.replace('{{ time }}', now) # 字符串替换 else: data='<h1>Hello, web!</h1>' # 三、返回http响应体信息,必须是bytes类型,必须放在列表中 return [data.encode('utf-8')] if __name__ == '__main__': # 当接收到请求时,wsgiref模块会对该请求加以处理,而后后调用app函数,自动传入两个参数: # 1 environ是一个字典,存放了http的请求信息 # 2 start_response是一个功能,用于返回http协议的响应首行和响应头信息 s = make_server('', 8011, app) # 表明server print('监听8011') s.serve_forever() # 在浏览器输入http://127.0.0.1:8011/index和http://127.0.0.1:8011/timer会看到不一样的页面内容
time.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>主页</h1> </body> </html>
上述案例中app
在处理业务逻辑时须要根据不一样的url
地址返回不一样的页面内容,当url
地址愈来愈多,须要写一堆if判断,代码不够清晰,耦合程度高,因此咱们作出如下优化
# 处理业务逻辑的函数 def index(environ): with open('index.html', 'r', encoding='utf-8') as f: data = f.read() return data.encode('utf-8') def timer(environ): import datetime now = datetime.datetime.now().strftime('%y-%m-%d %X') with open('timer.html', 'r', encoding='utf-8') as f: data = f.read() data = data.replace('{{ time }}', now) return data.encode('utf-8') # 路径跟函数的映射关系 url_patterns = [ ('/index', index), ('/timer', timer), ] from wsgiref.simple_server import make_server def app(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) # 拿到请求的url并根据映射关系url_patters执行相应的函数 reuqest_url = environ.get('PATH_INFO') for url in url_patterns: if url[0] == reuqest_url: data = url[1](environ) break else: data = b'404' return [data] if __name__ == '__main__': s = make_server('', 8011, app) print('监听8011') s.serve_forever()
随着业务逻辑复杂度的增长,处理业务逻辑的函数以及`url_patterns
中的映射关系都会不断地增多,此时仍然把全部代码都放到一个文件中,程序的可读性和可扩展性都会变得很是差,因此咱们应该将现有的代码拆分到不一样文件中
插图 :
view.py
内容
# 处理业务逻辑的函数 def index(environ): with open('templates/index.html', 'r',encoding='utf-8') as f: # 注意文件路径 data = f.read() return data.encode('utf-8') def timer(environ): import datetime now = datetime.datetime.now().strftime('%y-%m-%d %X') with open('templates/timer.html', 'r',encoding='utf-8') as f: # 注意文件路径 data = f.read() data=data.replace('{{ time }}',now) return data.encode('utf-8')
urls.py
内容
# 路径跟函数的映射关系 from app01.views import * # 须要导入views中的函数 url_patterns = [ ('/index', index), ('/timer', timer), ]
main.py
内容以下
from wsgiref.simple_server import make_server from mysite.urls import url_patterns # 须要导入urls中的url_patterns def app(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) # 拿到请求的url并根据映射关系url_patters执行相应的函数 reuqest_url = environ.get('PATH_INFO') for url in url_patterns: if url[0] == reuqest_url: data = url[1](environ) break else: data = b'404' return [data] if __name__ == '__main__': s = make_server('', 8011, app) print('监听8011') s.serve_forever()
至此,咱们就针对application
的开发自定义了一个框架,因此说框架的本质就是一系列功能的集合体、不一样的功能放到不一样的文件中。有了该框架,可让咱们专一于业务逻辑的编写,极大的提升了开发web
应用的效率(开发web
应用的框架能够简称为web
框架),好比咱们新增一个业务逻辑,要求为:浏览器输入http://127.0.0.1:8011/home 就能访问到home.html
页面,在框架的基础上具体开发步骤以下:
步骤一:在templates
文件夹下新增home.html
步骤二:在urls.py
的url_patterns
中新增一条映射关系
url_patterns = [ ('/index', index), ('/timer', timer), ('/home', home), # 新增的映射关系 ]
步骤三:在views.py
中新增一个名为home
的函数
def home(environ): with open('templates/home.html', 'r',encoding='utf-8') as f: data = f.read() return data.encode('utf-8')
咱们自定义的框架功能有限,在Python
中咱们可使用别人开发的、功能更强大的Django
框架
在使用Django
框架开发web
应用程序时,开发阶段一样依赖wsgiref
模块来实现Server
的功能,咱们使用Django
框架是为了快速地开发application
目前在企业开发中Django
框架使用的主流版本为1.11.x
版本,最新版本为2.x
,咱们主要讲解1.11
版本,同时会涉及2.x
的新特性,2019年11月中旬Django3.0 beta
已经到来
pip3 install django==1.11.18 # 在命令行执行该命令
若是使用的是咱们自定义的框架来开发web
应用,须要事先生成框架包含的一系列基础文件,而后在此基础上进行开发。
若是使用的是Django
框架来开发web
应用,一样须要事先生成Django
框架包含的一系列基础文件,而后在此基础上进行开发。
但Django
框架更为方便的地方在于它已经为咱们提供了一系列命令来帮咱们快速地生成这一系列基础文件
# 在命令行执行如下指令,会在当前目录生成一个名为mysite的文件夹,该文件夹中包含Django框架的一系列基础文件 django-admin startproject mysite
cd mysite # 切换到mysite目录下,执行如下命令 python manage.py startapp app01 # 建立功能模块app01,此处的startapp表明建立application下的一个功能模块。例如咱们要开发application是京东商城,京东商城这个大项目下有一个订单管理模块,咱们能够将其命名为app01
python manage.py runserver 8001 # 在浏览器输入:http://127.0.0.1:8001 会看到Django的欢迎页面。
截目录树的图(按照下述目录截图)
mysite # 文件夹 ├── app01 # 文件夹 │ └── migrations # 文件夹 │ └── admin.py │ └── apps.py │ └── models.py │ └── tests.py │ └── views.py ├── mysite # 文件夹 │ └── settings.py │ └── urls.py │ └── wsgi.py └── templates # 文件夹 ├── manage.py
关键文件介绍
-manage.py---项目入口,执行一些命令 -项目名 -settings.py 全局配置信息 -urls.py 总路由,请求地址跟视图函数的映射关系 -app名字 -migrations 数据库迁移的记录 -models.py 数据库表模型 -views.py 处理业务逻辑的函数,简称视图函数
Pycharm
建立Django
项目Django
实现的一个简单示例url.py
路由from django.contrib import admin from django.conf.urls import url #导入views模块 from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), # r'^index/$' 会正则匹配url地址的路径部分 url(r'^index/$',views.index), # 新增地址http://127.0.0.1:8001/index/与index函数的映射关系 ]
from django.shortcuts import render # 必须定义一个request形参,request至关于咱们自定义框架时的environ参数 def index(request): import datetime now=datetime.datetime.now() ctime=now.strftime("%Y-%m-%d %X") return render(request,"index.html",{"ctime":ctime}) # render会读取templates目录下的index.html文件的内容而且用字典中的ctime的值替换模版中的{{ ctime }}
在templates目录下新建文件index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h4>当前时间:{{ ctime }}</h4> </body> </html>
测试
python manage.py runserver 8001 # 在浏览器输入:http://127.0.0.1:8001/index/ 会看到当前时间。
Django
框架的分层与请求生命周期综上,咱们使用Django
框架就是为了开发application
,而application
的工做过程本质就是根据不一样的请求返回不一样的数据,Django
框架将这个工做过程细分为以下四层去实现
urls.py
)views.py
)models.py
)html
文件,详见templates
)django
请求生命周期
这体现了一种解耦合的思想,下面咱们开始深刻每一层