一 Web应用的组成

接下来我们学习的目的是为了开发一个Web应用程序,而Web应用程序是基于B/S架构的,其中B指的是浏览器,负责向S端发送请求信息,而S端会根据接收到的请求信息返回相应的数据给浏览器,需要强调的一点是:S端由server和application两大部分构成,如图所示:

上图:Web应用组成

二 开发一个Web应用

我们无需开发浏览器(本质即套接字客户端),只需要开发S端即可,S端的本质就是用套接字实现的,如下

# 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() # 1、接收浏览器发来的请求信息
recv_data = conn.recv(1024)
# print(recv_data.decode('utf-8')) # 2、将请求信息直接转交给application
res = app(recv_data) # 3、向浏览器返回消息(此处并没有按照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 会报错(注意:请使用谷歌浏览器)

目前S端已经可以正常接收浏览器发来的请求消息了,但是浏览器在接收到S端回复的响应消息b'hello world'时却无法正常解析 ,因为浏览器与S端之间收发消息默认使用的应用层协议是HTTP,浏览器默认会按照HTTP协议规定的格式发消息,而S端也必须按照HTTP协议的格式回消息才行,所以接下来我们详细介绍HTTP协议

HTTP协议详解链接地址:http://www.cnblogs.com/linhaifeng/articles/8243379.html

S端修订版本:处理HTTP协议的请求消息,并按照HTTP协议的格式回复消息

# 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() # 1、接收并处理浏览器发来的请求信息
# 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标签加以渲染

# S端
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)

更进一步我们还可以返回一个文件,例如timer.html,内容如下

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>{{ time }}</h2>
</body>
</html>

S端程序如下

# 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)

上述S端为浏览器返回的都是静态页面(内容都固定的),我们还可以返回动态页面(内容是变化的)

# 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,每次刷新都会看到不同的时间

三 Web框架的由来

综上案例我们可以发现一个规律,在开发S端时,server的功能是复杂且固定的(处理socket消息的收发和http协议的处理),而app中的业务逻辑却各不相同(不同的软件就应该有不同的业务逻辑),重复开发复杂且固定的server是毫无意义的,有一个wsgiref模块帮我们写好了server的功能,这样我们便只需要专注于app功能的编写即可

# wsgiref实现了server,即make_server
from wsgiref.simple_server import make_server def app(environ, start_response): # 代表application
# 1、返回http协议的响应首行和响应头信息
start_response('200 OK', [('Content-Type', 'text/html')]) # 2、处理业务逻辑:根据请求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>' # 3、返回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会看到不同的页面内容

timer.html已经存在了,新增的index.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中的映射关系都会不断地增多,此时仍然把所有代码都放到一个文件中,程序的可读性和可扩展性都会变得非常差,所以我们应该将现有的代码拆分到不同文件中

插图:

mysite # 文件夹
├── app01 # 文件夹
│ └── views.py
├── mysite # 文件夹
│ └── urls.py
└── templates # 文件夹
│ ├── index.html
│ └── timer.html
├── main.py

views.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框架的安装与使用

在使用Django框架开发web应用程序时,开发阶段同样依赖wsgiref模块来实现Server的功能,我们使用Django框架是为了快速地开发application

4.1 安装

目前在企业开发中Django框架使用的主流版本为1.11.x版本,最新版本为2.x,我们主要讲解1.11版本,同时会涉及2.x的新特性

pip3 install django==1.11.18 # 在命令行执行该命令

4.2 使用

4.2.1 快速创建并启动Django项目

如果使用的是我们自定义的框架来开发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的欢迎页面。

4.2.2 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 处理业务逻辑的函数,简称视图函数

4.2.3 基于Pycharm创建Django项目

4.2.4 基于Django实现的一个简单示例

(1)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函数的映射关系
]
(2)视图
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 }}
(3)模版

在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/ 会看到当前时间。

4.2.5 Django框架的分层与请求生命周期

综上,我们使用Django框架就是为了开发application,而application的工作过程本质就是根据不同的请求返回不同的数据,Django框架将这个工作过程细分为如下四层去实现

1、路由层(根据不同的地址执行不同的视图函数,详见urls.py)

2、视图层(定义处理业务逻辑的视图函数,详见views.py)

3、模型层 (跟数据库打交道的,详解models.py)

4、模板层(待返回给浏览器的html文件,详见templates)

django请求生命周期

这体现了一种解耦合的思想,下面我们开始详细介绍每一层

 
 
 

01-01 Web应用的更多相关文章

  1. (五)u-boot2013.01.01 for TQ210:《移植前的准备及u-boot初编译》

    移植前的准备 移植前,要做的事情是搭建开发环境以及对U-boot源码的获取.首先说一下开发环境: 1.此次U-boot移植的硬件平台是天嵌的TQ210开发板: CPU:板载核心是S5PV210(Cor ...

  2. (二)u-boot2013.01.01 for TQ210:《Makefile分析》

           当时写的时候看的是2012-10版本的,但是略对比了一遍和2013.01.01没什么改动,所以这不影响对2013.01.01版本的makefile的理解.本文比较侧重于语法句意的分析,框 ...

  3. (一)u-boot2013.01.01 for TQ210:《Uboot简介》

    一直想写一个s5pv210硬件平台的u-boot的移植文档,但一直都忙着没时间写.先写一些u-boot的脚本分析吧,包括makefile,mkconfig,config.mk,主要侧重于语法句意的分析 ...

  4. org.codehaus.jackson.map.JsonMappingException: Can not construct instance of java.util.Date from String value '2012-12-12 12:01:01': not a valid representation (error: Can not parse date "2012-12-

    Jackson对于date的反序列化只支持几种,如果不符合默认格式则会报一下错误 org.codehaus.jackson.map.JsonMappingException: Can not cons ...

  5. 2015.12.25-2016.01.01 大论文迭代B

    大论文B轮迭代,稍重前端 12.25 周五,完善摘要 12.26 周六,完善第一章 12.27 周天,完善第二章 12.28 周一,完善第三章 12.29 周二,完善第四章 12.30 周三,完善第五 ...

  6. Java开发工程师(Web方向) - 01.Java Web开发入门 - 第6章.蜂巢

    第6章--蜂巢 蜂巢简介 网站开发完,就需要测试.部署.在服务器上运行. 网易蜂巢: 采用Docker容器化技术的云计算平台 https://c.163.com 容器管理:容器可被视作为云主机的服务器 ...

  7. Java开发工程师(Web方向) - 01.Java Web开发入门 - 第4章.Maven

    第4章--Maven Maven实战 Java Web应用的部署: 手动式: 编译:javac -cp $CATALINA_HOME/lib/servlet-api.jar web-inf/class ...

  8. Java开发工程师(Web方向) - 01.Java Web开发入门 - 第3章.Tomcat

    第3章--Tomcat Tomcat安装与运行 Tomcat:目前最常用的基于java的web应用服务器 本课程中所有的Java代码最终都需要部署到Tomcat中运行 Tomcat的配置文件是XML的 ...

  9. Java开发工程师(Web方向) - 01.Java Web开发入门 - 第1章.Web应用开发概述

    第1章--Web应用开发概述 Web应用开发概述 浏览器-服务器架构(BS-architecture) browser/ App    ---- request ---->    server ...

  10. html 01前沿-web介绍

    1. 认识网页 网页主要由文字.图像和超链接等元素构成.当然,除了这些元素,网页中还可以包含音频.视频以及Flash等. 2. 浏览器(显示代码) 浏览器是网页显示.运行的平台,常用的浏览器有IE.火 ...

随机推荐

  1. 【AMAD】django-filer -- 一个管理文件和图片的django app

    动机 简介 个人评分 动机 django-filer1可以让你像一些云存储一样使用WEB UI控制你的文件. 简介 下面是前端图片:   个人评分 类型 评分 实用性 ⭐️⭐️⭐️⭐️ 易用性 ⭐ ...

  2. 【AMAD】python-magic -- libmagic的python封装

    简介 动机 作用 用法 个人评分 简介 libmagic的python封装 动机 封装libmagic,使用python代码获取文件类型. 作用 libmagic通过文件头部,来确定文件的类型. 用法 ...

  3. 关于虚拟机docker 启动mysql 启动成功但未挂载到端口

    首先排查了防火墙和其他权限相关问题 然后检查了mysql 用户权限问题 docker logs 查看日志 正常应该是到3306 问题是我的mysql my.cnf 文件是挂在在本地.当第二次启动容器时 ...

  4. 记录Linq中lambda动态表达式的使用方式

    项目中有的时候我们会用到动态表达式的方式去查询数据,这里简单记录下个人的使用方式,方便使用↓ //构建参数表达式 ParameterExpression parameter = Expression. ...

  5. 再谈循环&迭代&回溯&递归&递推这些基本概念

    循环:不断重复进行某一运算.操作. 迭代:不断对前一旧值运算得到新值直到达到精度.一般用于得到近似目标值,反复循环同一运算式(函数),并且总是把前一 次运算结果反代会运算式进行下一次运算 递推:从初值 ...

  6. [转帖]全方位掌握OpenStack技术知识

    全方位掌握OpenStack技术知识 http://www.itpub.net/2019/06/17/2206/ 架构师技术联盟的文章 相当好呢. 大家好,我是小枣君.最近几年,OpenStack这个 ...

  7. ssl安全验证

    #ssl验证 r=requests.get('https://www.12306.cn',verify=False) print(r.content.decode('utf-8')) 结果:

  8. std::tr1::function和bind组件

    C++中std::tr1::function和bind 组件的使用 在C++的TR1中(Technology Report)中包含一个function模板类和bind模板函数,使用它们可以实现类似函数 ...

  9. 基于VS搭建OpenCV环境

    OpenCV OpenCV的全称是Open Source Computer Vision Library,是一个跨平台的计算机视觉库.OpenCV是由英特尔公司发起并参与开发,以BSD许可证授权发行, ...

  10. O020、理解 Glance

    参考https://www.cnblogs.com/CloudMan6/p/5384923.html   OpenStack 由 Glance 提供 Image 服务.   理解 Glance    ...