From 2e6eaf43b974af8e65aaf0a61c780de3e9cf7d93 Mon Sep 17 00:00:00 2001 From: tangwei <409766394@qq.com> Date: Sat, 12 Jun 2021 13:12:05 +0800 Subject: [PATCH] chap26 --- .../chapter-26-Basic HTTP Server.md | 458 ++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 chapter 26 Basic HTTP Server/chapter-26-Basic HTTP Server.md diff --git a/chapter 26 Basic HTTP Server/chapter-26-Basic HTTP Server.md b/chapter 26 Basic HTTP Server/chapter-26-Basic HTTP Server.md new file mode 100644 index 0000000..b99d8d8 --- /dev/null +++ b/chapter 26 Basic HTTP Server/chapter-26-Basic HTTP Server.md @@ -0,0 +1,458 @@ +# 基本的HTTP服务器 + +## 1 你在本章节可以学到什么? + +- 什么是 HTTP 服务端? +- 什么是 HTTP 客户端? +- 什么是 HTML?如何创建一个 HTML 文件? +- 什么是 HTTP 和 IP ? +- HTTP 请求是怎么组成的? +- HTTP 响应是怎么组成的? +- 如何搭建一个基本的 Web 服务器? + +## 2 覆盖知识点 + +- HTTP +- HTML +- 请求和响应 +- 客户端和服务端 +- DNS +- Handler +- 接口实现 + +## 3 一个 Web 页面背后有什么? + +如图1,你可以看到一个非常简单的 Web 页面 + +在这个 Web 页面后面有什么?Web 浏览器会加载源码并把源码转换成我们在 Web 页面上所看到的内容。下面的代码会生成图1中的页面。 + +```html + + + + Title of page + + +

Title

+

Welcome to my website

+ + + +``` + +这就是 HTML。HTML 即超文本标记语言(Hypertext Markup Language)。它不是一种程序语言,而是一种标记语言。有了 HTML,你可以让 Web 浏览器使你的网页内容显示成指定的样式。 + +## 4 HTML基础知识 + +让我们仔细分析下第一个例子中的代码(文档) + +```html + + + + +``` + +- 首先,你可以注意到该文档的第一行是``。这一行会告诉Web浏览器接下来的内容是用 HTML 语言来书写的。 +- 文档是由元素组成的。每个元素有个起始标签和结束标签。 +- 元素可以被嵌套。可以在一个元素中嵌套另一个元素。 +- 第一个元素以``开头并且以``结尾。它表示在开始标记和结束标记之间还有其他 HTML 元素。 +- 在第一个元素中,我们可以有其他的元素: + +```html + + Title of page + +``` + +- 上面例子中起始标签是``,结束标签是``,这是文档的头部部分,可以在该标签内找到文档的元数据。元数据是描述了文档的相关信息。 + - 在标签内,还有另一个元素。它表示页面的标题。页面的标题将显示在浏览器的选项卡间。 +- 下一个元素是 body,它表示文档的内容,即显示在浏览器上的内容。 + +```html + +

Title

+

Welcome to my website

+ + +``` + +- 在 body 标签中,存在两个元素和一个注释 + - 第一个元素是1号标题:`

Title

`。 + - 一个段落元素:`

Welcome to my website

`。 + - 一个注释 `` +- 在渲染好的 Web 页面中,你不会看到这些标签。Web 浏览器使用标签来渲染页面。 + +#### 4.0.0.1 总结: + +- 有了 HTML,你可以构建 Web 页面 +- 元素包括一个起始标签和结束标签 +- 标签用来表示元素并且可以被浏览器解析 +- 元素可以嵌套 +- 下面是一个简单 Web 页面的框架: + +```html + + + + Title of page + + + + + +``` + +## 5 什么是HTTP + +HTTP 指超文本传输协议(Hypertext Transfer Protocol)。HTTP 是一种通信协议。它是一个用来描述双方如何通信的一组规范。 + +在我们每天的生活中,我们会使用通信协议来交流:当你与另一个人交谈时,你需要遵循一些潜在的规则。当你们见面,你们会互相问好。接着开始交流。对话是的连续的,第一个人说话,然后另一个人说话,最后一段对话经常以“再见”作为结束。 + +HTTP是一个用来使机器互相传递信息的协议。HTTP主要存在两个版本,HTTP/1.1 和 HTTP/2。两个版本都内容繁多,我们接下来将会重点介绍必须了解的一些关键要素。 + +## 6 HTTP请求之旅 + +当你在你的浏览器中输入一个URL并按下enter键时将会向网络中发送一个HTTP请求 + +HTTP 请求是由发送方(客户端)发送给接收方(服务器)的消息。 + +服务器在网络上可以通过唯一的名称进行标识,即域名。你还可以通过提供IP地址向服务器发送请求。当你发出HTTP请求时,你需要提供 IP地址或者域名,来将你的请求发送到对应的服务器上。 + +[图片] + +让我们来定义一些重要的术语: + +- IP:网际协议(Internet Protocol)。这是网络上的计算机之间以及不同网络之间的通信协议。你在浏览网页时会使用此协议。 +- IP 地址是由点(.)或冒号(:)分隔的数字组成的字符串。它应用于使用 Internet 协议进行通信的每台设备1。127.0.0.1 是一个 IP 地址。::1也是个 IP 地址。注意这两个地址的不同。第一个是 IPv4 地址,而第二个是 IPv6 地址。 +- 域名是用于标识网络上一个服务器(或多个服务器)的一个字符串。域名由一个复杂但精妙的域名系统(DNS)来管理。 + +HTTP 服务器是一个基于HTTP协议连接到网络中的一个计算机。这个计算机会运行一个程序来处理客户端发送给它的HTTP请求。服务器很少会关闭。下面我们将要搭建一个服务器 + +## 7 HTTP 请求的剖析 + +下面是一个 HTTP 请求(HTTP/1.1)。一个 HTTP 请求即发送到网络中的一系列行。 + +```http +GET /pub/WWW/TheProject.html HTTP/1.1 +Host: www.w3.org +User-Agent: curl/7.54.0 +Accept: */* +``` + +在图3中你可以看到一个请求的详细架构: + +[图片] + +#### 7.0.0.1 请求的组成 + +请求主要由三部分组成 + +1. 请求行 +2. 请求头 +3. 请求体 + +#### 7.0.0.2 概念 + +- URI:统一资源标识符(Unified Ressource Identifier)。如`/pub/WWW/TheProject.html` + - 这是我们要检索的资源(这里是 HTML 页面)的标识 +- URL:统一资源定位符(Uniform Ressource Locato)。如`http://www.w3.org/pub/WWW/TheProject.html` + - 它用来标识资源 + - 并指明如何检索资源(向主机 www.w3.org 发出 HTTP 请求) +- 每个HTTP请求都有个请求方法。可选值为:`OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT`。 + - 加载新页面时,Web 浏览器将会发送 HTTP GET 请求 +- 客户端可能会向请求中添加请求头。请求头是客户端将有关请求及其自身的信息传递给服务器的一种方式2。 + - 例子:`Host: www.w3.org`:该请求头标识所请求资源的网络位置 + - 例子:`Accept: */*`:客户端告诉服务端它可以接受的消息类型,如 JSON、XML... + - 例子:`User-Agent: curl/7.54.0`:有关发出请求的程序的信息 +- 请求体是请求的最后一部分。在前面的例子中,请求体是空的。它是用来传递信息。 + +## 8 HTTP响应的剖析 + +下面是一个HTTP响应 + +```http +HTTP/1.1 404 Not Found +Content-Type: text/plain; charset=utf-8 +Date: Fri, 03 Apr 2020 06:26:25 GMT +Content-Length: 19 + +404 page not found +``` + +在图4中我们可以看到一个响应的结构 + +[图片] + +#### 8.0.0.1 响应的组成 + +响应由三部分组成: + +1. 状态行 +2. 响应头 +3. 响应体 + +#### 8.0.0.2 概念 + +- 状态行是响应的第一行 + - 它指明了协议所用的版本 + - 由状态码和解释状态的短语表示的请求状态 +- 响应头用来“允许服务器传递有关无法放置在状态行中的响应的附加信息”3 + - Content-Type 用来通知客户端内容的媒体类型以及最终的字符集 + - Content-Length 用来通知客户端响应体的大小 +- 响应体将包含请求的信息 + - 对于网站,响应体将包含一个 HTML 页面 + - 如果是HTML页面,Coutent-Type 值将等于 text/html + +## 9 HTTP响应的状态码 + +一个HTTP响应包含了状态码。状态码是一个整数。你需要了解最常用的一些状态码: + +- 状态码 200 OK:请求被服务器成功处理 +- 状态码 400 Bad Request:客户端发送的请求错误(请求格式错误、语法错误...) +- 状态码 404 Not Found:服务端没有找到客户端请求的资源 +- 状态码 500 Internal Server Error:“服务端遭遇某种意外情况而无法处理请求”4 + +其他状态码也可以使用。你可以参考https://tools.ietf.org/html/rfc7231#section-6.1 + +要记住一件事是状态码的第一个数字代表状态代码累。存在五个类(见图): + +[图片] + +## 10 如何搭建一个简单的HTTP服务器 + +你可以在标准库中找到构建Web服务器所需的一切。这些函数存在于net/http包中: + +```go +// basic-http-server/first/main.go +package main + +import ( + "log" + "net/http" + "time" +) + +func main() { + // create a server + myServer := &http.Server{ + // set the server address + Addr: "127.0.0.1:8080", + // define some specific configuration + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + } + // launch the server + log.Fatal(myServer.ListenAndServe()) +} +``` + +#### 10.0.0.1 代码说明 + +我们首先创建一个变量 myServer 指向一个新的结构体 http.Server 的指针。结构体 http.Server 允许你为服务端定义一个特定的配置:Addr:服务器用于监听请求的地址。在我的例子中,我把它设置为 127.0.0.1:8000,这意味着客户端能够发送请求到127.0.0.1:8080。 + +- 注意地址由冒号分隔的两部分组成:127.0.0.1和8080。第一部分是IP地址(IPv4),第二部分是端口号。 + - 端口号参与构成完整的IP地址。它是一个无符号整数(16位)。Go 程序背后的操作系统会将接收到的数据包发送到监听这些特定端口的 Go 程序。 在一台机器上,可以运行多个服务器,具有相同的地址IP和不同的端口(`127.0.0.1:8081`, `127.0.0.1:8083`, `127.0.0.1:8084`)。 + +ReadTimeout:Web 服务器读取客户端请求的最长时间 + +WriteTimeout:允许服务器处理程序写入响应的最长时间。程序的最后一行需要一些解释 + +```go +log.Fatal(myServer.ListenAndServe()) +``` + +myServer.ListenAndServe()是一个方法调用,它将会启动服务器。该方法如果运行出错将会返回一个 error。例如,如果端口已经被占用,你将会收到一个 error。只要服务器运行,该方法也会运行。当服务器不再运行,该方法会返回一个 error 并将 error 传递给log.Fatal。 + +#### 10.0.0.2 构建并运行 + +让我们构建并运行我们全新的服务器: + +```shell +$ go build main.go +$ ./main +``` + +该程序将会运行直到发生一个错误。服务器可以运行数月甚至数年而无需重新启动。 + +让我们测试下我们的服务器。我们有两个方法: + +1. 使用Web浏览器(Chrome) +2. 使用命令行工具cURL + +## 11 如何使用 Chrome 测试一个本地服务器 + +第一个方法很简单。打开我们的浏览器并输入 127.0.0.1:8000 + +[图片] + +我们的服务器返回了“404 page not found“!它回答了一些事情。 + +你可以使用 Chrome 检查发送到服务器的请求。想要实现这个功能,打开开发者面板(见图5) + +[图片] + +一个窗口出现了(见图6)。然后你需要点击”Network“选项。 + +[图片] + +network选项将记录任务网络的使用情况。目前,它是空的,因为没有网络请求。重新加载页面,你将会看到 Chrome 发出的请求(见图7)。 + +你能够看到 + +- 请求的名称 +- 请求的方法 +- 请求头和响应头 +- 状态(这里是404 Not Found) +- 请求的类型、发起者、大小、耗时 + +通过点击请求,你可以获取更多关于我们服务的请求和响应的信息。 + +[图片] + +”Header“选项卡(见图8)包含了请求和响应的头部信息 + +[图片] + +”Response“选项卡会服务器的响应。这是从服务器接收到的原始响应。 + +## 12 如何使用cURL测试本地Web服务器 + +cURL 是一个用 C 语言写的程序,发布与1997年。该软件由两个不同部分组成:库(libcurl)和命令行界面(curl)。我们可以在C语言程序或者任何提供绑定的程序中使用该库。 + +我们将会使用命令行工具来测试我们的服务器。 + +下面的命令将会发起一个 HTTP GET 请求到服务器地址http://127.0.0.1:8080中: + +```shel +$ curl http://127.0.0.1:8080 --verbose +``` + +[图片] + +在图10中,你可以看到命令行的示例输出。我添加了详细标志以获取有关请求和响应的更多信息。 我们可以看到 : + +- 与服务器的连接 +- 发送的请求 +- 从服务器接收到的响应 +- 连接的最终状态 + +## 13 如何使用 Go 服务器来生成一个 HTML 页面 + +我们构建的一个服务器什么也没做:它对每一个请求返回来了一个状态码为 404(Not Found)的 HTTP 响应。我们接下来将在响应体中发送一个 HTML 文档。 + +#### 13.0.0.1 带有 HTTP 内容的 HTTP 响应 + +我们的目标是发送下面这个 HTML 文档给客户端 + +```html + + + Hello + +``` + +我们需要发送下面这个 HTTP 请求给服务端 + +```html +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +Date: Fri, 03 Apr 2020 06:26:25 GMT +Content-Length: 45 + + + + Hello + +``` + +#### 13.0.0.2 Server 类型结构体 + +包 http 中的 Server 结构体有一个名为 Handler 的字段。下面从标准库文件net/http/server.go文件中摘录的代码。 + +```go +package http +//.. +type Server struct { + Addr string // TCP address to listen on, ":http" if empty + Handler Handler // handler to invoke, http.DefaultServeMux if nil + //... +} +``` + +`Handler`(来自包 `http` )是一个接口: + +```go +package http +//... +type Handler interface { + ServeHTTP(ResponseWriter, *Request) +} +``` + +#### 13.0.0.3 创建你自己的 http.Hanlder 实现 + +我们应该创建我们自己的类型来实现这个接口并定义一个 HTTP Handler: + +```go +type myHandler struct { +} + +func (h *myHandler)ServeHTTP(w http.ResponseWriter, r *http.Request) { + // TODO +} +``` + +这里我创建了一个结构体叫 myHander。在这个类型中,我定义了一个方法叫 ServeHTTP。这个方法需要两个参数 w 和 r。w 的类型是http.ResponseWriter,而r的类型是 *http.Request。 + +这些是什么类型?让我们看看文档。 + +#### 13.0.0.4 http.ResponseWriter + +“HTTP Handler 使用 ResponseWriter 接口来构造 HTTP 响应”。我们可以看到这是一个接口类型: + +```go +package http +//... +type ResponseWriter interface { + // Header returns the header map that will be sent by + // WriteHeader. The Header map also is the mechanism with which + // Handlers can set HTTP trailers. + //... + Header() Header + + // Write writes the data to the connection as part of an HTTP reply. + //... + Write([]byte) (int, error) + + // WriteHeader sends an HTTP response header with the provided + // status code. + //... + WriteHeader(statusCode int) +} +``` + +当服务器收到请求时,它会将这个接口的实现传递给我们的方法。这意味着我们能够使用下面三种方法: + +```go +Header +``` + +- 此方法的返回类型是 http.Header,它实际是一个 map,用来存放响应头数据 + +```go +Write +``` + +- 通过该方法,我们能够向响应体中写入内容 + +```go +WriteHeader +``` + +- 通过该方法,我们能够发送指定的状态码 + +#### 13.0.0.5 http.Request + + + -- Gitee