用 Python 撸一个 Web 服务器-第5章:处理静态资源

简介: 用 Python 撸一个 Web 服务器-第5章:处理静态资源

处理静态文件

由于我们实现的模板引擎不支持直接将 CSS 嵌入在 HTML 中的写法,所以要将 CSS 独立出来。在 todo/ 目录下新建 static/ 目录,专门用来存储 CSS、JavaScript、图片等静态文件,在 static/ 目录下新建 css/ 目录用来存储 CSS 样式。我们把之前在 todo/templates/index.html HTML 页面中写的 CSS 移动到 todo/static/css/ 目录下的 style.css 文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* todo_list/todo/static/css/style.css */
* {
margin: 0;
padding: 0;
}
ul {
list-style: none;
}
a {
text-decoration: none;
outline: none;
color: #000000;
}
h1 {
margin: 20px auto;
}
.container {
display: flex;
justify-content: center;
align-items: center;
}
.containerul {
width: 100%;
max-width: 600px;
}
.containerulli {
height: 40px;
line-height: 40px;
margin-bottom: 4px;
padding: 06px;
display: flex;
justify-content: space-between;
background-color: #d2d2d2;
}

接下来在首页 HTML 中引入 style.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- todo_list/todo/templates/index.html -->
<!DOCTYPE html>
<html>
<head>
<metacharset="UTF-8">
<title>Todo List</title>
<!-- 引入外部 CSS 样式 -->
<linkrel="stylesheet"href="/static/css/style.css">
</head>
<body>
<h1class="container">Todo List</h1>
<divclass="container">
<ul>
        {% for todo in todo_list %}
<li>
<div>{{ todo.content }}</div>
</li>
        {% endfor %}
</ul>
</div>
</body>
</html>

浏览器在访问 Todo List 程序首页得到 index.html 源码后,发现 HTML 顶部的 link 标签,浏览器会自动再次发送一个请求到 link 标签 href 属性所指定的网址来获取 CSS 样式。所以我们还要编写一个视图函数来处理 /static/css/style.css 路径的请求,返回 style.css 内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# todo_list/todo/controllers.py
import os
from todo.config import BASE_DIR
from todo.utils import Response, render_template
# 此处省略了 index 视图函数
...
defstatic(request):
"""读取静态资源视图函数"""
# 能够处理的静态资源类型
    content_type = {
'css': 'text/css',
'.js': 'text/javascript',
'png': 'image/png',  # 文件需要返回二进制
'jpg': 'image/jpeg',
    }
# 请求路径格式:/static/css/style.css
# 根据请求路径中的文件名后缀判断静态文件类型,指定响应首部字段 Content-Type
    headers = {
'Content-Type': content_type.get(request.path[-3:], 'text/plain'),
    }
# 获取静态资源绝对路径
    path = request.path.lstrip('/')
    file_path = os.path.join(BASE_DIR, path)
# 读取静态资源内容,构造响应对象并返回
with open(file_path, 'r') as f:
        body = f.read()
return Response(body, headers=headers)
routes = {
    ...
'/static': (static, ['GET']),
}

编写 static 视图函数来专门处理静态文件,它不只能够处理 CSS 类型文件,还支持 JavaScript 文件和图片。实际上静态资源的访问路径被我故意设计为以 /static 开头加上静态文件类型 css 再加上文件名 style.css 的形式,这样只需要编写一个 static 视图函数就能够处理多种类型的静态文件,而不需给 CSS、JavaScript 等静态资源都单独编写一个视图函数。服务器在收到客户端发过来的请求时,判断请求路径以 /static 开头即可断定客户端需要请求静态资源,然后将请求交由 static 视图函数来处理。

假如程序需要用到 JavaScript,只需要在 todo/static/ 目录下新建 js/index.js 文件,当收到请求路径为 /static/js/index.js 的客户端请求时,static 视图函数就能够正确处理响应。

static 视图函数根据不同的资源类型,会指定对应的响应首部字段。浏览器根据响应首部字段 Content-Type 的值来判断响应报文类型,Content-Type: text/html 指明响应报文类型为 HTML,Content-Type: text/css 指明响应报文类型为 CSS。

由于需要指定响应首部字段,所以 static 视图函数返回的并不是响应体,而是直接构造了一个 Response 对象。因此之前编写的 make_response 函数逻辑相应的也需要做些修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# todo_list/server.py
defmake_response(request, headers=None):
"""构造响应报文"""
# 默认状态码为 200
    status = 200
# 处理静态资源请求
if request.path.startswith('/static'):
        route, methods = routes.get('/static')
else:
        route, methods = routes.get(request.path)
# 如果请求方法不被允许返回 405 状态码
if request.method notin methods:
        status = 405
        data = 'Method Not Allowed'
else:
# 请求首页时 route 实际上就是我们在 controllers.py 中定义的 index 视图函数
        data = route(request)
# 如果返回结果为 Response 对象,直接获取响应报文
if isinstance(data, Response):
        response_bytes = bytes(data)
else:
# 返回结果为字符串,需要先构造 Response 对象,然后再获取响应报文
        response = Response(data, headers=headers, status=status)
        response_bytes = bytes(response)
    print(f'response_bytes: {response_bytes}')
return response_bytes

修改后的 make_response 函数会判断请求路径如果以是 /static 开头则将请求交给 static 视图函数来处理。同时还修改了视图函数的调用接口 data = route(request),每次调用视图函数时需要将 request 对象传递进去,所以记得修改 index 视图函数让其能够接收 request 参数。 make_response 函数最后还对视图函数返回结果 data 做了类型检查,如果返回的是 Response 对象,则可以直接获取响应报文,否则需要先构造 Response 对象,然后再获取响应报文。

终端中进入项目根目录 todo_list/ 下,使用 Python 运行 server.py 文件,就能看到带有 CSS 样式的 Todo List 程序首页了:

Todo List 首页

现在 Todo List 程序已经有了处理静态资源的能力,接下来再给 Todo List 程序添加一个网页图标。

网页图标格式通常为 .ico 文件,将其放到 todo/static/ 目录下。然后在控制器中编写一个 favicon 视图函数用来处理 ICO 图标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# todo_list/todo/controllers.py
...
deffavicon(request):
"""读取网页 ICO 图标"""
    headers = {
'Content-Type': 'image/x-icon',
    }
    path = f'static/{request.path.lstrip("/")}'
    file_path = os.path.join(BASE_DIR, path)
# ICO 图标文件需要读取为二进制
with open(file_path, 'rb') as f:
        body = f.read()
return Response(body, headers=headers)
routes = {
    ...
'/favicon.ico': (favicon, ['GET']),
}

当请求路径为 /favicon.ico 时匹配到 favicon 视图函数进行处理。因为 ICO 图标文件需要以二进制格式来读取,并且不能够直接转换为字符串类型,所以在构造 Response 对象时传入的响应体不再是 str 类型,而是 bytes 类型。因此还要修改下 Response 类,使其能够接收 bytes 类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# todo_list/todo/utils.py
classResponse(object):
"""响应类"""
    ...
def__bytes__(self):
"""构造响应报文"""
# 状态行 'HTTP/1.1 200 OK\r\n'
        header = f'HTTP/1.1 {self.status}{self.reason_phrase.get(self.status, "")}\r\n'
# 响应首部
        header += ''.join(f'{k}: {v}\r\n'for k, v in self.headers.items())
# 空行
        blank_line = '\r\n'
# 响应体
        body = self.body
# body 支持 str 或 bytes 类型
if isinstance(body, str):
            body = body.encode('utf-8')
        response_message = (header + blank_line).encode('utf-8') + body
return response_message

只需要修改 Response 类的 __bytes__ 方法即可,根据 body 类型来决定如何构造响应报文。

现在重启 Todo List 程序,浏览器中刷新首页,你会看到浏览器标签页已经显示出 ICO 图标了:

Todo List 图标

本章源码:chapter5

相关文章
|
9天前
|
SQL 安全 JavaScript
告别Web安全小白!Python实战指南:抵御SQL注入、XSS、CSRF的秘密武器!
【9月更文挑战第12天】在Web开发中,安全漏洞如同暗礁,尤其对初学者而言,SQL注入、跨站脚本(XSS)和跨站请求伪造(CSRF)是常见挑战。本文通过实战案例,展示如何利用Python应对这些威胁。首先,通过参数化查询防止SQL注入;其次,借助Jinja2模板引擎自动转义机制抵御XSS攻击;最后,使用Flask-WTF库生成和验证CSRF令牌,确保转账功能安全。掌握这些技巧,助你构建更安全的Web应用。
14 5
|
11天前
|
安全 Python
使用Python实现简单的Web服务器
使用Python实现简单的Web服务器
19 6
|
20天前
|
API C# 开发框架
WPF与Web服务集成大揭秘:手把手教你调用RESTful API,客户端与服务器端优劣对比全解析!
【8月更文挑战第31天】在现代软件开发中,WPF 和 Web 服务各具特色。WPF 以其出色的界面展示能力受到欢迎,而 Web 服务则凭借跨平台和易维护性在互联网应用中占有一席之地。本文探讨了 WPF 如何通过 HttpClient 类调用 RESTful API,并展示了基于 ASP.NET Core 的 Web 服务如何实现同样的功能。通过对比分析,揭示了两者各自的优缺点:WPF 客户端直接处理数据,减轻服务器负担,但需处理网络异常;Web 服务则能利用服务器端功能如缓存和权限验证,但可能增加服务器负载。希望本文能帮助开发者根据具体需求选择合适的技术方案。
56 0
|
20天前
|
Rust 安全 开发者
惊爆!Xamarin 携手机器学习,开启智能应用新纪元,个性化体验与跨平台优势完美融合大揭秘!
【8月更文挑战第31天】随着互联网的发展,Web应用对性能和安全性要求不断提高。Rust凭借卓越的性能、内存安全及丰富生态,成为构建高性能Web服务器的理想选择。本文通过一个简单示例,展示如何使用Rust和Actix-web框架搭建基本Web服务器,从创建项目到运行服务器全程指导,帮助读者领略Rust在Web后端开发中的强大能力。通过实践,读者可以体验到Rust在性能和安全性方面的优势,以及其在Web开发领域的巨大潜力。
29 0
|
20天前
|
存储 运维 监控
自动化运维:使用Python脚本进行服务器监控
【8月更文挑战第31天】在数字化时代,服务器的稳定运行对于企业至关重要。本文将介绍如何使用Python编写一个简单的服务器监控脚本,帮助运维人员及时发现并解决潜在问题。我们将从基础的服务器资源监控开始,逐步深入到日志分析与报警机制的实现。通过实际代码示例和操作步骤,使读者能够快速掌握自动化监控的技能,提升工作效率。
|
20天前
|
Java 数据库 API
JSF与JPA的史诗级联盟:如何编织数据持久化的华丽织锦,重塑Web应用的荣耀
【8月更文挑战第31天】JavaServer Faces (JSF) 和 Java Persistence API (JPA) 分别是构建Java Web应用的用户界面组件框架和持久化标准。结合使用JSF与JPA,能够打造强大的数据驱动Web应用。首先,通过定义实体类(如`User`)和配置`persistence.xml`来设置JPA环境。然后,在JSF中利用Managed Bean(如`UserBean`)管理业务逻辑,通过`EntityManager`执行数据持久化操作。
32 0
|
21天前
|
数据采集 存储 数据挖掘
构建高效Web爬虫:Python与BeautifulSoup实战指南
【8月更文挑战第31天】本文将引导读者步入Web爬虫的世界,通过Python编程语言和BeautifulSoup库的强强联手,解锁数据抓取的艺术。文章不仅提供代码示例,还将深入探讨如何设计高效、可维护且符合伦理的爬虫程序。
|
5天前
|
数据采集 机器学习/深度学习 人工智能
Python编程入门:从零基础到实战应用
【9月更文挑战第15天】本文将引导读者从零开始学习Python编程,通过简单易懂的语言和实例,帮助初学者掌握Python的基本语法和常用库,最终实现一个简单的实战项目。文章结构清晰,分为基础知识、进阶技巧和实战应用三个部分,逐步深入,让读者在学习过程中不断积累经验,提高编程能力。
|
1天前
|
数据可视化 Python
Python编程中的数据可视化技术
【9月更文挑战第19天】在数据驱动的时代,将复杂的数据集转化为直观易懂的视觉表达至关重要。本文将深入探索Python中的数据可视化库,如Matplotlib和Seaborn,并指导读者如何运用这些工具来揭示数据背后的模式和趋势。文章不仅会介绍基础图表的绘制方法,还将讨论高级技巧以提升图表的信息丰富度和吸引力。
|
2天前
|
人工智能 数据挖掘 开发者
Python编程入门:从基础到实战
【9月更文挑战第18天】本文将带你走进Python的世界,从最基本的语法开始,逐步深入到实际的项目应用。无论你是编程新手,还是有一定基础的开发者,都能在这篇文章中找到你需要的内容。我们将通过详细的代码示例和清晰的解释,让你轻松掌握Python编程。
15 5