整天写Crud没劲,写了个Web服务器

服务器
我们学知识大致都会经历三个阶段。第一阶段是what,了解一个东西是什么,第二阶段是how,了解一个东西如何使用。第三阶段是why,为什么要这么用。当你达到第三层境界才能说明你对这个东西理解透彻。

 大家好,我是军哥

今天分享的系列文章是如何用python徒手撸一个web服务器。

我们学知识大致都会经历三个阶段。第一阶段是what,了解一个东西是什么,第二阶段是how,了解一个东西如何使用。第三阶段是why,为什么要这么用。当你达到第三层境界才能说明你对这个东西理解透彻。

[[336693]]

同理,对于一个做web开发的程序员来说,没有什么比自己写一个web服务器或web框架更能了解web编程的本质了。

这个系列是我的一位读者朋友江湖十年写的,他过来问我可不可以投稿在公众号。看完之后就立马答应了。大家都喜欢看没营养的爽文,虽然这种干货型的技术文看的人少,但是能帮助对web开发感兴趣的人就值了。

系列文章工作分为8章,除了这篇,次条到第8条都是,你也不可能一天看到,先收藏着,看看你能不能一周也写一个web服务器出来。

教程简介

本教程使用 Python 语言实现了一个简易版的 Web 服务器,从 Web 开发基础开始讲解,不使用任何第三方库或框架,通过实现一个 Todo List 应用来还原 Web 开发的本质。

教程所需基础

Python、HTML、CSS 语法基础,对 Web 开发基本概念有所了解。

教程面向读者

本教程为入门级,主要适合准 Python Web 开发者或对 Web 开发理解不是很透彻的同学。

教程特点

本教程并不会使用如 Django、Flask 等常见 Python Web 开发框架,也不会使用任何其他第三方库,甚至会用文件来替代数据库存储数据。其目的是为了简化一些对初学者来说看似复杂的概念,使用更少的依赖从零开发一个 Python Web 服务器。以便读者能够更深刻的理解 Web 开发。

前言

Web 开发技术一直在高速发展,各种新奇概念与框架层出不穷,尤其在 Web 前端领域,几年前还是 jQuery 的天下,而如今在 Vue、React 等框架面前也显得廉颇老矣。

不过,虽然各种框架技术日新月异,但 Web 开发的核心概念与本质依旧不曾改变,本教程将通过一个 Todo List 应用带你探索 Web 开发基本原理,只有真正明白了 Web 开发的核心基础,才能更轻松的应对新框架与技术。

Web 开发简介

我们常见的软件种类有桌面软件、移动 APP以及网页应用等,Web 开发通常就是在开发网页应用。桌面软件、移动 APP 需要先安装在 Windows、Android 等宿主机才能使用,每个客户端每次升级更新软件时都需要重新下载并安装。而 Web 应用所依赖的客户端是浏览器,实际数据都存储在远程服务器端,如果应用需要升级,那么只需要升级服务器,所有用户通过浏览器打开网页时都将实时获取最新的数据,这也是 Web 开发能够流行起来的很重要的原因。

HTTP 简介

要学习 Web 开发,首先要明白什么是 HTTP 协议,因为 Web 开发就是建立在 HTTP 协议之上的。

在浏览器地址栏输入网址 https://www.jd.com/ 将得到京东商城首页。我们在浏览器页面中看到的所有数据都是服务器通过 HTTP 协议传输过来的。

 

HTTP 协议中文叫超文本传输协议,可以拆分成三部分理解:超文本、传输、协议。

所谓超文本就是 HTML、CSS、图片、视频等内容的集合。传输既超文本内容从浏览器(客户端)到服务器或从服务器到浏览器之间的传输过程。而协议是规范,大家约定俗成的规约既是协议。

HTTP 基于请求 —— 响应模型,浏览器向服务器的某个网址发起请求,服务器响应浏览器对应的资源。以下就是一个 HTTP 请求 —— 响应的模型图。

 

左侧是浏览器,右侧是服务器。浏览器和服务器之间通讯是通过文本传输来完成的。浏览器发起请求时发送的数据叫作请求报文,得到的服务端响应的数据叫作响应报文。下图示展示了请求报文和响应报文的格式。

 

根据图示可以看到,无论是请求报文还是响应报文基本都分为四个部分,每个部分之间以 \r\n 作为换行分隔符。

请求报文包含请求行、报文首部、空行、报文主体四个部分。

响应报文包含状态行、报文首部、空行、报文主体四个部分。

在请求报文中,请求行和报文首部可以看成一个整体叫作请求头,报文主体又叫请求体。

在响应报文中,状态行和报文首部可以看成一个整体叫作响应头,报文主体又叫响应体。

如何在 Chrome 浏览器中查看请求与响应的报文信息呢?浏览器页面任意位置点击鼠标右键 —— 选择检查,就能够在页面底部显示 Chrome 开发者工具。点击 Elements选项卡,你就能够看到网页的源代码,也就是服务器的响应数据。

 

点击 Network 选项卡,就能够看到所有请求、响应记录。

 

每一行记录即为一个请求,为什么只是在地址栏里输入了 https://www.jd.com/ 会出现这么多请求呢?我们知道,HTML 中不止可以写简单的文本标签,还可以写 img、video 等多媒体标签加载图片或视频,以及 link、script 等标签来加载 CSS 样式 和 JavaScript 脚本。浏览器在得到服务器的第一个响应时,会检查服务器返回的 HTML 页面,如果页面源码中包含这些特殊的标签,浏览器就会针对每一个标签专门发起一次请求。

点击第一个请求,在右侧将会显示该请求及响应信息。找到 Request Headers 点击右侧的 view source,将会看到此次请求的请求头信息。

 

第一行 GET / HTTP/1.1 即为请求行。它由三个部分组成:请求方法、请求地址、HTTP 协议版本号,分别对应 GET、/、HTTP/1.1。每个部分之间通过一个空格隔开。

HTTP 请求方法有很多,不过最常见的只有四种:POST、DELETE、PUT、GET 分别对应增、删、改、查四个操作。由于我们只是想查看京东首页,所以这里发送的是 GET 请求。

请求地址 / 代表首页,你可能看过如 /index、/index.html 等类似的地址,其实这只是一个约定俗成的做法,它们都代表首页。

现在最常用的 HTTP 版本即为 1.1。虽然 HTTP 2.0 早已发布,不过目前来说普及率依旧不是很高。所以现在大可不必纠结版本的问题。

从第二行开始,所有内容都是诸如 Key: Value 这种键值对的形式组合成的,我们管每一组键值对都叫作请求首部字段,这些首部字段加在一起就是请求的报文首部,一般会直接叫请求头。

其中最重要的请求首部字段就是 Host: www.jd.com 这一行,因为只有这一行是 HTTP 协议明确规定为必传的首部字段,其他请求首部字段都是非必传字段。Host: www.jd.com 的作用是当服务器上部署了多个网站,那么服务器就会根据这一行的信息来确定浏览器到底想访问哪个网站。

Connection: keep-alive 代表长连接,User-Agent 标识了浏览器信息,Accept 代表浏览器能够接收的报文格式,其他的请求首部字段可以等我们用到了再做说明。

在 HTTP 请求报文中,还缺少一个空行和请求体是我们目前没有看到的。实际上虽然我们是点击浏览器的 view source 功能来查看请求报文信息,但它仍然是浏览器处理过的数据,并不是原始数据,所以空行是看不到的。而此次请求为 GET 请求,通常 GET请求是没有请求体的,所以请求体被省略了。

一个 HTTP GET 请求大概长这样:

  1. GET / HTTP/1.1 
  2. Host: www.jd.com 
  3. Connection: keep-alive 
  4. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 
  5. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 

一个 HTTP POST 请求大概长这样:

  1. POST /login HTTP/1.1 
  2. Host: www.jd.com 
  3. Connection: keep-alive 
  4. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 
  5. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 
  6.  
  7. username=test&password=pass 

上面 POST 请求示例中 username=test&password=pass 即为请求体。

需要注意,以上请求报文中每一个换行都是用 \r\n 来标识的,这一点在后面实际开发的章节中就能体会到。

分析完请求报文,接下来分析下响应报文。找到 Response Headers 同样点击右侧的 view source,将会看到此次请求的响应头信息。

 

第一行 HTTP/1.1 200 OK 即为状态行。它同样由三部分组成:HTTP协议版本号、状态码、当前状态码对应的原因短语,分别对应 HTTP/1.1、200、OK。每个部分之间仍然通过一个空格隔开。

其中状态码用来告知服务器返回的响应状态,为一个数字。状态码大概分为四类:2XX、3XX、4XX、5XX,分别对应成功、重定向、客户端错误、服务端错误。所以状态码为 200 表示请求成功。

原因短语 OK 其实并不是很重要,它主要是给客户端返回一个人类可读的短语,来标识请求结果,对浏览器来说没什么作用。

响应首部字段同样是 Key: Value 这种键值对形式,并且有些是和请求首部字段一样的,称为通用首部字段,如 Connection: keep-alive 同样存在于响应首部字段。还有些字段是和请求首部字段搭配着使用的,如你所见 Content-Type 字段就是搭配请求首部字段中的 Accept 来使用的,Accept 是浏览器用来告诉服务器它能够接收的数据格式,而 Content-Type: text/html 标识了服务器返回的数据格式为 html。其他的响应首部字段可以等我们用到了再做说明。

同样的,响应报文中的空行并不会在浏览器中展现出来,不过响应体是可以在浏览器中看到的。

 

当前请求的响应体实际上就是服务器返回的 HTML 源码。

一个 HTTP 响应大概长这样:

  1. HTTP/1.1 200 OK 
  2. Connection: keep-alive 
  3. Content-Type: text/html 
  4.  
  5. <html>XXX</html> 

URL 简介

介绍完 HTTP 协议基础,我们再来简单介绍下 URL。

京东首页的网址是 https://www.jd.com/,这就是一个 URL。URL 中文叫作统一资源定位符。URL 的作用是在网络中唯一的标记一个资源。

你可能听说过 URI,实际上 URL 是 URI 的一个子集。在基础学习阶段,我们并不需要弄清楚它们之间到底有什么差别,只需要把我们常见的网址统称为 URL 即可。

一个 URL 的完整格式如下:

  1. scheme://user:passwd@host:port/path?query#fragment 

每一个部分代表含义如下:

scheme:协议名,表示资源使用哪种协议,http 就代表了 HTTP 协议,file 代表文件协议。

://:这个是固定写法,记住就好,没必要深究。

user:passwd@:身份信息,表示访问主机时需要提供的用户名和密码,但这种将隐私信息完全暴露在外部的作法已经几乎没人使用了。

host:port:表示资源所在的主机名和端口号。主机名为 IP 地址或域名,必须要有,HTTP 协议默认端口号是 80,可以省略,其他端口号则不可省略。

path:标识资源所在位置。它采用类似 Unix 系统的目录风格,必须以 / 开头,如果是多级路径,可以写成 /a/b/c 这种形式。

query:查询参数,在路径之后第一个 ? 开始(不包含?)到 # 之前结束,所有的内容都是查询参数,一个参数的格式为 key=value,如果存在多个参数,中间用 & 隔开。假如我们需要对数据做分页处理就可以使用这个参数,如 page=1&offset=20 表示需要获取第一页的数据,每页数据 20 条。这时候服务器端就可以解析到这个参数并返回对应的数据。

fragment:最后一部分为片段标识符。也可以将其叫做锚点,从 # 开始,浏览器可以根据锚点跳转到页面指定位置,但这个参数是不会被发送到服务器端的,所以开发服务器端通常不太需要关心。

下面通过对一个实际的 URL 地址进行分析来加强理解。

以下图示为在京东首页搜索 Python书籍后浏览器地址栏 URL 截图。为了能够更清晰的说明问题 URL 部分略有删减。

 

我们可以将 URL 复制出来:

  1. https://search.jd.com/Search?keyword=python%E4%B9%A6%E7%B1%8D&enc=utf-8 

以上 URL 按标准格式拆分如下:

  1. scheme: https 
  2. host:port: search.jd.com 
  3. path: /Search 
  4. query: keyword=python%E4%B9%A6%E7%B1%8D&enc=utf-8 

你应该已经发现上面复制出来的 URL 和截图中的有些不同。

首先我们复制出来的 URL 是以 https 开头,而浏览器中却是以 search 开头,其实这是浏览器为了让 URL 更加可读,而将 https:// 隐藏了,实际上它仍然是存在的。

你可能有个疑惑,我们上面一直在介绍 HTTP 协议,一个完整的 URL 是以 http 开头的。但实际上京东网站的 URL 都是以 https 开头的。其实 https 是基于 http的,只是在 http 的基础上又加了一个安全套接层,默认端口 443,HTTP 协议传输数据都是明文传输,HTTPS 协议传输数据是经过加密的,所以使得数据的传输更加安全,仅此而已。本教程不会使用 HTTPS 协议,故此不做过多讲解。

还有一个较大的差异就是在浏览器地址栏中看到的查询参数 Python书籍 复制出来以后就变成了 python%E4%B9%A6%E7%B1%8D。这是 URL 编码造成的,通常在 URL 中只能使用 ASCII 码,并且 URL 本身会使用如 ?、& 等符号作为分隔符。这就导致了很多特殊符号或者 ASCII 以外的字符(如中文)不能够直接使用,所以就规定了一种转码规范,将不能直接用于 URL 的字符通过转义操作使其变为可用字符。这就是 书籍 两个字符变成了 %E4%B9%A6%E7%B1%8D 的原因。在浏览器地址栏中能够正常显示同样是浏览器为了让 URL 更加可读做的特殊处理。URL 转义具体规则可以查看相关文档进行学习。

TCP/IP 简介

多个计算机之间通讯靠的是网络协议,最早的计算机厂商生产的计算机只能跟自家的计算机通讯,为让所有计算机之间都能够通讯就有了 TCP/IP 协议。这就比如几个人对话,有的说汉语、有的说英语、有的说日语,大家谁也听不懂其他人说话,所以最后大家都规定用一种语言来交流一样。

TCP/IP 协议并不是单个的协议,它是一个协议族,包含了很多种网络通讯协议,HTTP 协议也在其中。在网络中两台计算机之间如果需要通讯,就要知道对方在哪,所以就有了 IP 地址。就像我们要给别人发快递,那么就要知道对方的家庭住址一样。现在常用的 IP 地址为 IPv4 版本,格式如 192.168.3.14,最新版本是 IPv6,格式如 ABCD:EF01:2345:6789:ABCD:EF01:2345:6789。

有了 IP 地址还不够,就像你把快递邮寄到一个家庭住址,但家里有五口人,你还要确定这个快递是寄给谁的。端口的作用就是干这个的。计算机通过端口号区分收到的信息该转发给哪个软件。

IP 地址加上端口号就是上面介绍的 URL 中 host:port 部分,如 192.168.3.14:80。但 IP 地址是一串数字,不容易记住,所以就有了域名。www.jd.com 就是京东的域名。我们在浏览器中输入这个域名时,计算机会自动将其转换成 IP 地址加端口号的形式。这个转换过程是通过一个叫 DNS 解析的技术。它能够将域名和 IP 之间的关系做绑定,知道了域名它就能查出其对应的 IP 地址是多少。

HTTP 协议是建立在 TCP/IP 协议之上的,HTTP 的数据传输依靠的是 TCP 协议。总结起来就是 TCP 用来传输数据,HTTP 用来规范数据传输格式。

扩展

从浏览器地址栏输入网址 https://www.jd.com/ 到看见京东网站首页经历了什么?

浏览器从地址栏获取 URL

浏览器或操作系统通过 DNS 解析将 URL 中域名和端口号解析成对应的 IP 地址和端口号

浏览器通过 TCP 与服务器建立连接

浏览器向服务器发送请求报文

服务器收到请求报文后做对应的处理,将处理结果组装成响应报文返回给浏览器

浏览器解析响应报文,渲染页面

以上就是从浏览器地址栏输入网址 https://www.jd.com/ 到看见京东网站首页所经历的过程概述。

如果你对 Web 开发完全零基础,看了以上我对 HTTP 协议的简介,感觉不是很明白的话,那么我推荐你可以看一本叫作《图解HTTP》的书,看完后再来学习本教程能够达到事半功倍的效果。

开发环境

本教程项目开发环境如下:

  • Python:Python3.7
  • 浏览器:Chrome83
  • 开发工具:PyCharm 2020.1
  • 操作系统:macOS 10.15

有 Python 基础的同学想必搭建开发环境肯定不在话下,所以具体搭建过程这里不再做过多介绍,需要强调的一点是浏览器我只推荐 Chrome 或者 Firefox,这样能够得到更好的开发体验。

本文转载自微信公众号「 Python之禅」,可以通过以下二维码关注。转载本文请联系 Python之禅公众号。

责任编辑:武晓燕 来源: Python之禅
相关推荐

2020-10-29 16:00:03

Node.jsweb前端

2017-11-10 08:58:49

Web服务器应用程序

2019-02-26 16:20:52

FTP服务器

2022-02-22 11:57:32

BOAWeb服务器

2010-05-13 18:32:52

2010-03-12 10:48:03

2010-12-28 15:39:38

2019-05-14 09:39:07

Web服务器Web容器应用程序服务器

2018-08-07 08:54:18

2019-04-24 15:13:16

Web服务器应用服务器Web容器

2018-05-02 09:00:26

开源 Web 服务器

2009-07-09 10:25:05

Servlet的Web

2009-12-30 10:09:08

Ubuntu Apac

2019-05-08 14:54:16

PythonWeb服务器

2017-12-29 09:47:11

Web服务器万维网

2016-08-10 16:28:00

WebURLHTTP

2011-07-07 15:43:51

服务器安装

2016-08-22 20:37:10

PythonWeb服务器

2022-01-12 08:49:33

CaddyWeb服务器Go语言

2012-09-19 09:47:58

PHPWeb服务器
点赞
收藏

51CTO技术栈公众号