搭个 Web 服务器（一）
Web 服务器
![](/data/attachment/album/201608/08/205045iklb50bmkdkcm408.jpg)


一天，有一个正在散步的妇人恰好路过一个建筑工地，看到三个正在工作的工人。她问第一个人：“你在做什么？”第一个人没好气地喊道：“你没看到我在砌砖吗？”妇人对这个答案不满意，于是问第二个人：“你在做什么？”第二个人回答说：“我在建一堵砖墙。”说完，他转向第一个人，跟他说：“嗨，你把墙砌过头了。去把刚刚那块砖弄下来！”然而，妇人对这个答案依然不满意，于是又问了第三个人相同的问题。第三个人仰头看着天，对她说：“我在建造世界上最大的教堂。”当他回答时，第一个人和第二个人在为刚刚砌错的砖而争吵。他转向那两个人，说：“不用管那块砖了。这堵墙在室内，它会被水泥填平，没人会看见它的。去砌下一层吧。”


这个故事告诉我们：如果你能够理解整个系统的构造，了解系统的各个部件如何相互结合（如砖、墙还有整个教堂），你就能够更快地定位及修复问题（那块砌错的砖）。


如果你想从头开始创造一个 Web 服务器，那么你需要做些什么呢？


我相信，如果你想成为一个更好的开发者，你**必须**对日常使用的软件系统的内部结构有更深的理解，包括编程语言、编译器与解释器、数据库及操作系统、Web 服务器及 Web 框架。而且，为了更好更深入地理解这些系统，你**必须**从头开始，用一砖一瓦来重新构建这个系统。


荀子曾经用这几句话来表达这种思想：



> 
> “<ruby> 不闻不若闻之。 <rp>  （ </rp> <rt>  I hear and I forget. </rt> <rp>  ） </rp></ruby>”
> 
> 
> 


![](/data/attachment/album/201608/08/205108is5rhuarh3530luh.png)



> 
> “<ruby> 闻之不若见之。 <rp>  （ </rp> <rt>  I see and I remember. </rt> <rp>  ） </rp></ruby>”
> 
> 
> 


![](/data/attachment/album/201608/08/205110ndpjyv4vlvep5ova.png)



> 
> “<ruby> 知之不若行之。 <rp>  （ </rp> <rt>  I do and I understand. </rt> <rp>  ） </rp></ruby>”
> 
> 
> 


![](/data/attachment/album/201608/08/205110a41zcugc4zup4ege.png)


我希望你现在能够意识到，重新建造一个软件系统来了解它的工作方式是一个好主意。


在这个由三篇文章组成的系列中，我将会教你构建你自己的 Web 服务器。我们开始吧~


先说首要问题：Web 服务器是什么？


![](/data/attachment/album/201608/08/205112sxhej6turgrph11d.png)


简而言之，它是一个运行在一个物理服务器上的网络服务器（啊呀，服务器套服务器），等待客户端向其发送请求。当它接收请求后，会生成一个响应，并回送至客户端。客户端和服务端之间通过 HTTP 协议来实现相互交流。客户端可以是你的浏览器，也可以是使用 HTTP 协议的其它任何软件。


最简单的 Web 服务器实现应该是什么样的呢？这里我给出我的实现。这个例子由 Python 写成，即使你没听说过 Python（它是一门超级容易上手的语言，快去试试看！），你也应该能够从代码及注释中理解其中的理念：



```
import socket

HOST, PORT = '', 8888

listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listen_socket.bind((HOST, PORT))
listen_socket.listen(1)
print 'Serving HTTP on port %s ...' % PORT
while True:
    client_connection, client_address = listen_socket.accept()
    request = client_connection.recv(1024)
    print request

    http_response = """\
HTTP/1.1 200 OK

Hello, World!
"""
    client_connection.sendall(http_response)
    client_connection.close()

```

将以上代码保存为 webserver1.py，或者直接从 [GitHub](https://github.com/rspivak/lsbaws/blob/master/part1/webserver1.py) 上下载这个文件。然后，在命令行中运行这个程序。像这样：



```
$ python webserver1.py
Serving HTTP on port 8888 …

```

现在，在你的网页浏览器的地址栏中输入 URL：http://localhost:8888/hello ，敲一下回车，然后来见证奇迹。你应该看到“Hello, World!”显示在你的浏览器中，就像下图那样：


![](/data/attachment/album/201608/08/205113s5r558rw5wt1raxx.png)


说真的，快去试一试。你做实验的时候，我会等着你的。


完成了？不错！现在我们来讨论一下它实际上是怎么工作的。


首先我们从你刚刚输入的 Web 地址开始。它叫 [URL](http://en.wikipedia.org/wiki/Uniform_resource_locator)，这是它的基本结构：


![](/data/attachment/album/201608/08/205114fpqashpps0awl2hh.png)


URL 是一个 Web 服务器的地址，浏览器用这个地址来寻找并连接 Web 服务器，并将上面的内容返回给你。在你的浏览器能够发送 HTTP 请求之前，它需要与 Web 服务器建立一个 TCP 连接。然后会在 TCP 连接中发送 HTTP 请求，并等待服务器返回 HTTP 响应。当你的浏览器收到响应后，就会显示其内容，在上面的例子中，它显示了“Hello, World!”。


我们来进一步探索在发送 HTTP 请求之前，客户端与服务器建立 TCP 连接的过程。为了建立链接，它们使用了所谓“<ruby> 套接字 <rp>  （ </rp> <rt>  socket </rt> <rp>  ） </rp></ruby>”。我们现在不直接使用浏览器发送请求，而在命令行中使用 `telnet` 来人工模拟这个过程。


在你运行 Web 服务器的电脑上，在命令行中建立一个 telnet 会话，指定一个本地域名，使用端口 8888，然后按下回车：



```
$ telnet localhost 8888
Trying 127.0.0.1 …
Connected to localhost.

```

这个时候，你已经与运行在你本地主机的服务器建立了一个 TCP 连接。在下图中，你可以看到一个服务器从头开始，到能够建立 TCP 连接的基本过程。


![](/data/attachment/album/201608/08/205116fzqeqae4ee7dduhh.png)


在同一个 telnet 会话中，输入 `GET /hello HTTP/1.1`，然后输入回车：



```
$ telnet localhost 8888
Trying 127.0.0.1 …
Connected to localhost.
GET /hello HTTP/1.1

HTTP/1.1 200 OK
Hello, World!

```

你刚刚手动模拟了你的浏览器（的工作）！你发送了 HTTP 请求，并且收到了一个 HTTP 应答。下面是一个 HTTP 请求的基本结构：


![](/data/attachment/album/201608/08/205117illrhlrzrc53qskl.png)


HTTP 请求的第一行由三部分组成：HTTP 方法（`GET`，因为我们想让我们的服务器返回一些内容），以及标明所需页面的路径 `/hello`，还有协议版本。


为了简单一些，我们刚刚构建的 Web 服务器完全忽略了上面的请求内容。你也可以试着输入一些无用内容而不是“GET /hello HTTP/1.1”，但你仍然会收到一个“Hello, World!”响应。


一旦你输入了请求行并敲了回车，客户端就会将请求发送至服务器；服务器读取请求行，就会返回相应的 HTTP 响应。


下面是服务器返回客户端（在上面的例子里是 telnet）的响应内容：


![](/data/attachment/album/201608/08/205118dlbjjl7dfrblq9f4.png)


我们来解析它。这个响应由三部分组成：一个状态行 `HTTP/1.1 200 OK`，后面跟着一个空行，再下面是响应正文。


HTTP 响应的状态行 HTTP/1.1 200 OK 包含了 HTTP 版本号，HTTP 状态码以及 HTTP 状态短语“OK”。当浏览器收到响应后，它会将响应正文显示出来，这也就是为什么你会在浏览器中看到“Hello, World!”。


以上就是 Web 服务器的基本工作模型。总结一下：Web 服务器创建一个处于监听状态的套接字，循环接收新的连接。客户端建立 TCP 连接成功后，会向服务器发送 HTTP 请求，然后服务器会以一个 HTTP 响应做应答，客户端会将 HTTP 的响应内容显示给用户。为了建立 TCP 连接，客户端和服务端均会使用套接字。


现在，你应该了解了 Web 服务器的基本工作方式，你可以使用浏览器或其它 HTTP 客户端进行试验。如果你尝试过、观察过，你应该也能够使用 telnet，人工编写 HTTP 请求，成为一个“人形” HTTP 客户端。


现在留一个小问题：“你要如何在不对程序做任何改动的情况下，在你刚刚搭建起来的 Web 服务器上适配 Django, Flask 或 Pyramid 应用呢？”


我会在本系列的第二部分中来详细讲解。敬请期待。


顺便，我在撰写一本名为《搭个 Web 服务器：从头开始》的书。这本书讲解了如何从头开始编写一个基本的 Web 服务器，里面包含本文中没有的更多细节。订阅邮件列表，你就可以获取到这本书的最新进展，以及发布日期。




---


via: <https://ruslanspivak.com/lsbaws-part1/>


作者：[Ruslan](https://linkedin.com/in/ruslanspivak/) 译者：[StdioA](https://github.com/StdioA) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
Out for a walk one day, a woman came across a construction site and saw three men working. She asked the first man, “What are you doing?” Annoyed by the question, the first man barked, “Can’t you see that I’m laying bricks?” Not satisfied with the answer, she asked the second man what he was doing. The second man answered, “I’m building a brick wall.” Then, turning his attention to the first man, he said, “Hey, you just passed the end of the wall. You need to take off that last brick.” Again not satisfied with the answer, she asked the third man what he was doing. And the man said to her while looking up in the sky, “I am building the biggest cathedral this world has ever known.” While he was standing there and looking up in the sky the other two men started arguing about the errant brick. The man turned to the first two men and said, “Hey guys, don’t worry about that brick. It’s an inside wall, it will get plastered over and no one will ever see that brick. Just move on to another layer.”

The moral of the story is that when you know the whole system and understand how different pieces fit together (bricks, walls, cathedral), you can identify and fix problems faster (errant brick).

What does it have to do with creating your own Web server from scratch?

**I believe to become a better developer you MUST get a better understanding of the underlying software systems you use on a daily basis and that includes programming languages, compilers and interpreters, databases and operating systems, web servers and web frameworks. And, to get a better and deeper understanding of those systems you MUST re-build them from scratch, brick by brick, wall by wall.**

Confucius put it this way:


“I hear and I forget.”


“I see and I remember.”


“I do and I understand.”

I hope at this point you’re convinced that it’s a good idea to start re-building different software systems to learn how they work.

In this three-part series I will show you how to build your own basic Web server. Let’s get started.

First things first, what is a Web server?

In a nutshell it’s a networking server that sits on a physical server (oops, a server on a server) and waits for a client to send a request. When it receives a request, it generates a response and sends it back to the client. The communication between a client and a server happens using HTTP protocol. A client can be your browser or any other software that speaks HTTP.

What would a very simple implementation of a Web server look like? Here is my take on it. The example is in Python (tested on Python3.7+) but even if you don’t know Python (it’s a very easy language to pick up, try it!) you still should be able to understand concepts from the code and explanations below:

```
# Python3.7+
import socket
HOST, PORT = '', 8888
listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listen_socket.bind((HOST, PORT))
listen_socket.listen(1)
print(f'Serving HTTP on port {PORT} ...')
while True:
client_connection, client_address = listen_socket.accept()
request_data = client_connection.recv(1024)
print(request_data.decode('utf-8'))
http_response = b"""\
HTTP/1.1 200 OK
Hello, World!
"""
client_connection.sendall(http_response)
client_connection.close()
```

Save the above code as *webserver1.py* or download it directly from [GitHub](https://github.com/rspivak/lsbaws/blob/master/part1/webserver1.py) and run it on the command line like this

```
$ python webserver1.py
Serving HTTP on port 8888 …
```

Now type in the following URL in your Web browser’s address bar [http://localhost:8888/hello](http://localhost:8888/hello), hit Enter, and see magic in action. You should see *“Hello, World!”* displayed in your browser like this:

Just do it, seriously. I will wait for you while you’re testing it.

Done? Great. Now let’s discuss how it all actually works.

First let’s start with the Web address you’ve entered. It’s called an [URL](http://en.wikipedia.org/wiki/Uniform_resource_locator) and here is its basic structure:

This is how you tell your browser the address of the Web server it needs to find and connect to and the page (path) on the server to fetch for you. Before your browser can send a HTTP request though, it first needs to establish a TCP connection with the Web server. Then it sends an HTTP request over the TCP connection to the server and waits for the server to send an HTTP response back. And when your browser receives the response it displays it, in this case it displays “Hello, World!”

Let’s explore in more detail how the client and the server establish a TCP connection before sending HTTP requests and responses. To do that they both use so-called *sockets*. Instead of using a browser directly you are going to simulate your browser manually by using *telnet* on the command line.

On the same computer you’re running the Web server fire up a telnet session on the command line specifying a host to connect to *localhost* and the port to connect to *8888* and then press Enter:

```
$ telnet localhost 8888
Trying 127.0.0.1 …
Connected to localhost.
```

At this point you’ve established a TCP connection with the server running on your local host and ready to send and receive HTTP messages. In the picture below you can see a standard procedure a server has to go through to be able to accept new TCP connections.

In the same telnet session type ** GET /hello HTTP/1.1** and hit Enter:

```
$ telnet localhost 8888
Trying 127.0.0.1 …
Connected to localhost.
GET /hello HTTP/1.1
HTTP/1.1 200 OK
Hello, World!
```

You’ve just manually simulated your browser! You sent an HTTP request and got an HTTP response back. This is the basic structure of an HTTP request:

The HTTP request consists of the line indicating the HTTP method (** GET**, because we are asking our server to return us something), the path

*/hello*that indicates a

*“page”*on the server we want and the protocol version.

For simplicity’s sake our Web server at this point completely ignores the above request line. You could just as well type in any garbage instead of *“GET /hello HTTP/1.1”* and you would still get back a *“Hello, World!”* response.

Once you’ve typed the request line and hit Enter the client sends the request to the server, the server reads the request line, prints it and returns the proper HTTP response.

Here is the HTTP response that the server sends back to your client (*telnet* in this case):

Let’s dissect it. The response consists of a status line *HTTP/1.1 200 OK*, followed by a required empty line, and then the HTTP response body.

The response status line *HTTP/1.1 200 OK* consists of the *HTTP Version*, the *HTTP status code* and the *HTTP status code reason* phrase *OK*. When the browser gets the response, it displays the body of the response and that’s why you see *“Hello, World!”* in your browser.

And that’s the basic model of how a Web server works. To sum it up: The Web server creates a listening socket and starts accepting new connections in a loop. The client initiates a TCP connection and, after successfully establishing it, the client sends an HTTP request to the server and the server responds with an HTTP response that gets displayed to the user. To establish a TCP connection both clients and servers use *sockets*.

Now you have a very basic working Web server that you can test with your browser or some other HTTP client. As you’ve seen and hopefully tried, you can also be a human HTTP client too, by using *telnet* and typing HTTP requests manually.

Here’s a question for you: “How do you run a Django application, Flask application, and Pyramid application under your freshly minted Web server without making a single change to the server to accommodate all those different Web frameworks?”

I will show you exactly how in [Part 2](https://ruslanspivak.com/lsbaws-part2/) of the series. Stay tuned.


*Resources used in preparation for this article (links are affiliate links):*

-
[Unix Network Programming, Volume 1: The Sockets Networking API (3rd Edition)](http://www.amazon.com/gp/product/0131411551/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0131411551&linkCode=as2&tag=russblo0b-20&linkId=2F4NYRBND566JJQL) -
[The Linux Programming Interface: A Linux and UNIX System Programming Handbook](http://www.amazon.com/gp/product/1593272200/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1593272200&linkCode=as2&tag=russblo0b-20&linkId=CHFOMNYXN35I2MON)


UPDATE: Sat, July 13, 2019

Updated the server code to run under Python 3.7+

Added resources used in preparation for the article




Get the edge and stay sharp. Subscribe to Beyond Basics for free and get new posts without missing a beat!


**All articles in this series:**

## Comments

comments powered by Disqus