老齐教室

剖析Web技术栈(一)

作者:Leonardo Giordani

翻译:老齐

与本文相关书籍推荐:《跟老齐学Python:Django实战》

引言

最近我与年轻的Web开发人员共事,他们第一次接触到用于生产的一些基础组件,为此出现了许多问题,这些组件都是“web服务”体系中常见的。通过这些问题,我看到了年轻人的困惑,虽然他们掌握了某种高级编程语言(如Node.js或Python),但不知晓浏览器和他们选择的框架之间所发生复杂事情,不清楚框架的作用和使用的时机。

如果我们只列出(按随机顺序)在讨论(Python)Web开发时使用的一些名词,对于一些读者来讲,理解起来可能有困难,比如:HTTP、cookies、Web server、Websockets、FTP、多线程、反向代理、Django、nginx、静态文件、POST、证书、框架、Flask、SSL、GET、WSGI、session、TLS、负载均衡、Apache等。

在这篇文章中,我想基于上面提到的所有单词汇(还有更多别的词),从头开始构建一个可用于生产的Web服务。希望这有助于年轻的开发人员了解全局,理解像我这样的高级开发者在日常对话中经常提到的那些“晦涩”名词(有时可以说是不恰当的)。

由于本文的重点是全局架构和选用特定组件的理由,因此,在Web开发的示例中,我将用HTML的网页,以Python语言为后端语言,并讨论所选用的对应框架。但,本文内容其实适用于任何编程语言或开发框架。

在本文的每个部分,都会先说明原理,然后介绍可能的解决方案。之后,我将指出缺失的部分或未解决的问题,并继续下一层次。在这个过程的最后,读者应该清楚地知道每个组件都被添加到系统中的原因。

完美的架构

对于系统架构,有一个最基本的认识:天才们不能设计出一个完美的解决方案,我们需要根据实际来选用系统架构。不幸的是,人们常常认为设计模式是“魔术般的解决方案”。然而,《设计模式》(Design Patterns)原著中指出

你的设计既要针对当前的问题,也应该足以用于解决未来的问题和需求。还要避免重构,或者尽可能减少重构。

之后

设计模式使人们更容易再次使用成功的设计和架构。……设计模式有利于你选择可以重复使用的系统架构。

本文讨论的是面向对象的编程,所以下面的内容适用于任何架构。如你所见,我们会遇到“手头的问题”和“设计备选方案”,这意味着最重要的是要了解需求,包括现在和将来的需求。只有得到明确的需求,才能有效地设计解决方案,并且可能会利用其他设计师已经设计的大量模式。

最后一句,Web栈非常复杂,由多个组件和软件包组成,这些组件和软件包是由不同的程序员为了不同的目标开发的。因此,这些东西的功能会有某种程度的重叠。虽然理论层之间的分界线通常非常清楚,但在实践中,分界线往往很模糊。有了这样的预期,你将永远不会迷失在Web栈中。

定义

让我们简单回顾一下Web栈中涉及的一些最重要的概念或协议。

TCP/IP

TCP/IP是一种网络协议,即两台计算机在通过物理网络连接以交换消息时必须遵循的一组既定规则。TCP/IP由两个不同的协议组成,涉及OSI协议栈的两个不同层,即传输层(TCP)和网络层(IP)。TCP/IP可以在任何物理接口(数据链路和物理OSI层)上实现,如以太网和无线网。TCP/IP网络中的参与者由套接字标识,套接字是由IP地址和端口号组成的元组。

正如我们所知,TCP/IP是一种可靠的协议,在电信领域,这意味着协议本身会在数据包丢失时进行处理或重传。换言之,虽然没有规定通信的速度,但我们可以确信,一旦发送消息,它将准确无误地到达目的地。

HTTP

TCP/IP可以保证一台计算机发送的字节到达其目的地,但这完全没有涉及如何发送有意义的信息的问题。特别是在1989年,Tim Barners-Lee想要解决这样一个问题:如何在网络中对超文本资源进行唯一命名以及如何访问它们。

HTTP是为解决这一问题而设计的协议,此后得到了极大的发展。在WebSocket等其他协议的帮助下,HTTP侵入了原本被认为不适合的通信领域,例如实时通信或游戏。

HTTP的核心是一个协议,它规定了文本请求的格式和可能的文本响应。1991年发布的0.9版定义了URL的概念,那时还只允许以GET方式请求特定资源。HTTP 1.0和1.1增加了一些重要的特性,如header、更多的方法和重要的性能优化。在撰写本文时,HTTP/2的采用率约为全球网站的45%,而HTTP/3仍然是一个草案。

作为开发人员,我们需要记住:HTTP最重要的特点:它是一个无状态协议。这意味着协议不需要服务器跟踪请求之间的通信状态,基本上将会话(sessoin)管理留给服务本身的开发人员。

会话管理现在非常重要,因为你通常希望在服务前面有一个身份验证层。在该层中,用户提供凭据并访问一些私有数据。但是,在其他情境中也很有用,例如记录用户的偏好等。解决HTTP中的session管理问题,最典型方案是使用cookies或会话tokens。

HTTPS

近年来,安全已成为一个非常重要的词,这是有原因的。我们在互联网上交流或存储在数字设备上的敏感数据量正呈指数级增长,但不幸的是,恶意攻击者的数量以及他们的行为可能造成的损害程度也呈指数级增长。

HTTP本质上是不安全的。它是两台服务器之间的纯文本通信,这种通信通常发生在完全不稳定的网络(如Internet)上。虽然在最初构想该协议时,安全并不是一个问题,但现在是一个极其重要的问题了,因为我们交换的私人信息通常对人们的安全或企业至关重要,需要确保我们正在向正确的服务器发送信息,并且发送的数据不能被拦截。

HTTPS解决了篡改和窃听的问题,使用传输层安全(Transport Layer Security,TLS)协议对HTTP进行加密,该协议还强制使用由可信机构颁发的数字证书。在撰写本文时,Firefox加载的大约80%的网站默认使用HTTPS。当服务器接收到一个HTTPS连接并将其转换为HTTP连接时,通常说它终止了TLS(或TLS的旧称:SSL)。

WebSocket

HTTP的一个很大的缺点是,通信总是由客户端发起的,服务器只能在显式请求时发送数据。虽然可以通过轮询解决服务器主动响应问题,但它不能保证正确的全双工通信性能,即服务器和客户端之间的信道保持打开,并且两者都可以在不被请求的情况下发送数据。这种信道由WebSocket协议提供。

WebSocket是一项杀手级的技术,它可以应用于在线游戏、实时消息推送(如金融行情或体育新闻)、多媒体通信(如会议或远程教育)等领域。

重要的是要理解WebSocket不是HTTP,并且可以在没有HTTP的情况下存在。同样,这个新协议被设计用于现有的HTTP连接之上,因此WebSocket通信经常出现在Web页面的某些部分中,最初是使用HTTP检索的。

通过HTTP实现服务

我们终于开始讨论比特和字节了。我们旅程的起点是HTTP上的服务,这意味着存在HTTP请求——响应交换。例如,一个GET请求,这是最简单的HTTP方法。

1
2
3
4
GET / HTTP/1.1
Host: localhost
User-Agent: curl/7.65.3
Accept: */*

如你所见,客户端正在向服务器发送纯文本消息,格式由HTTP协议指定。第一行包含方法名(GET)、URL(/)和我们正在使用的协议,包括它的版本(HTTP/1.1)。其余的行称为头信息,包含可以帮助服务器管理请求的元数据。在本例中,Host的完整值是localhost:80,但是由于HTTP服务的标准端口是80,所以不需要指定它。

如果服务器localhost在端口80上提供HTTP服务(即运行一些理解HTTP的程序),那么我们可能得到类似于下面的响应

1
2
3
4
5
6
7
8
9
10
HTTP/1.0 200 OK
Date: Mon, 10 Feb 2020 08:41:33 GMT
Content-type: text/html
Content-Length: 26889
Last-Modified: Mon, 10 Feb 2020 08:41:27 GMT

<!DOCTYPE HTML>
<html>
...
</html>

对于请求所作的响应是一条文本消息,这条消息根据标准进行了格式化。第一行是协议和响应请求的状态(在本例中是200,这意味着成功),而下面几行,都是headers中的信息,最后,在空行之后,是客户端请求的资源,在本例中是对应于URL的网页源代码,由于此HTML页面可能包含对其他资源(如CSS、JS、图像等)的引用,浏览器将发送其他几个请求来收集它需要的所有数据,以便向用户显示正确的页面。

因此,我们面临的第一个问题是要有一个服务器。服务器理解这一协议,并在接收到HTTP请求时发送适当的响应。我们应该尝试加载请求的资源,如果可以找到它,则返回success(HTTP 200),如果找不到,则返回failure(HTTP 404)。

(未完,待续)

搜索技术问答的公众号:老齐教室

在公众号中回复:老齐,可查看所有文章、书籍、课程。

觉得好看,就点这里👇👇👇

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

关注微信公众号,读文章、听课程,提升技能