许多开发人员至少会花一些时间创建服务器,以便在互联网上分发内容。HTTP (Hypertext Transfer Protocol,超文本传输协议)提供了大部分这些内容,无论是请求一张猫的图片还是请求加载你正在阅读的教程。Go标准库为创建HTTP服务器以提供web内容或向这些服务器发出HTTP请求提供内置支持。
在本教程中,您将使用Go的标准库创建一个HTTP服务器,然后扩展服务器以从请求的查询字符串、请求体和表单数据中读取数据。你还需要更新程序,用自己的HTTP头和状态码响应请求。
在Go中,标准库中的net/http
包提供了大多数HTTP功能,而其余的网络通信由net
包提供。net/http
包不仅包含发出HTTP请求的能力,还提供了一个HTTP服务器,您可以使用它来处理这些请求。
在本节中,您将创建一个程序,它使用’ HTTP . listenandserve '函数来启动一个HTTP服务器,以响应请求路径/
和/hello
。然后,扩展该程序,在同一个程序中运行多个HTTP服务器。
不过,在编写代码之前,需要先创建程序的目录。许多开发人员将他们的项目放在一个目录中,以保持项目的组织。在本教程中,你将使用一个名为projects
的目录。
首先,创建projects
目录并导航到它:
mkdir projects
cd projects
接下来,创建项目目录并导航到该目录。在这种情况下,使用httpserver
目录:
mkdir httpserver
cd httpserver
现在你已经创建了你的程序目录,并且你在httpserver
目录下,你可以开始实现你的HTTP服务器了。
一个Go HTTP服务器包括两个主要组件:侦听来自HTTP客户端的请求的服务器和一个或多个将响应这些请求的请求处理程序。在本节中,你将从使用http.HandleFunc
函数来告诉服务器调用哪个函数来处理对服务器的请求开始。然后,你将使用http.ListenAndServe
函数来启动服务器,并告诉它监听新的HTTP请求,然后使用你设置的处理程序函数来处理它们。
现在,在你创建的httpserver
目录中,使用nano
或者你最喜欢的编辑器,打开main.go
文件:
nano main.go
在main.go
文件中,你将创建两个函数,getRoot
和getHello
,作为你的处理函数。然后,你将创建一个main
函数,并使用http.HandleFunc
函数来设置你的请求处理程序,方法是将/
路径传递给getRoot
函数,将/hello
路径传递给getHello
函数。一旦你设置了你的处理程序,调用http.ListenAndServe
函数来启动服务器并监听请求。
将以下代码添加到该文件中,启动程序并设置处理程序:
main.go
package main
import (
"errors"
"fmt"
"io"
"net/http"
"os"
)
func getRoot(w http.ResponseWriter, r *http.Request) {
fmt.Printf("got / request\n")
io.WriteString(w, "This is my website!\n")
}
func getHello(w http.ResponseWriter, r *http.Request) {
fmt.Printf("got /hello request\n")
io.WriteString(w, "Hello, HTTP!\n")
}
在这第一段代码中,你为Go程序设置了package
, import
程序所需的包,并创建了两个函数:getRoot
函数和getHello
函数。这两个函数都有相同的函数签名,它们接受相同的参数:http.ResponseWriter
值和*http.Request
值。这个函数签名用于HTTP处理程序函数,定义为’ http.HandlerFunc '。当向服务器发出请求时,它使用正在发出的请求的信息设置这两个值,然后使用这些值调用处理程序函数。
在http.HandlerFunc
中,http.ResponseWriter
值(在您的处理程序中名为w
)用于控制将响应信息写回发出请求的客户端,例如响应体或状态码。然后,*http.Request
值(在处理程序中名为r
)用于获取进入服务器的请求的信息,例如在POST
请求情况下发送的请求体或有关发出请求的客户端的信息。
现在,在你的两个HTTP处理程序中,你使用fmt.Printf
来打印处理程序函数的请求,然后使用http.ResponseWriter
向响应体发送一些文本。http.ResponseWriter
是一个io.Writer
,这意味着你可以使用任何能够写入该接口的东西来写入响应主体。在本例中,你使用io.WriteString
函数将响应写入body。
现在,通过启动main
函数来继续创建程序:
main.go
...
func main() {
http.HandleFunc("/", getRoot)
http.HandleFunc("/hello", getHello)
err := http.ListenAndServe(":3333", nil)
...
在main
函数中,你有两次对http.HandleFunc
函数的调用。对该函数的每次调用都会为默认的服务器多路复用器中的特定请求路径设置一个处理程序函数。服务器多路复用器是一个http.Handler
,它能够查看请求路径并调用与该路径相关联的给定处理程序函数。所以,在你的程序中,你告诉默认的服务器多路复用器,当有人请求/
路径时调用getRoot
函数,当有人请求/hello
路径时调用getHello
函数。
一旦处理程序设置好了,你就可以调用http.ListenAndServe
函数,它会告诉全局HTTP服务器使用可选的http.Handler
在特定端口上监听传入的请求。在你的程序中,你告诉服务器监听":3333"
。通过冒号前不指定IP地址,服务器将监听与计算机关联的每个IP地址,并且监听端口为3333
。[网络端口](https://en.wikipedia.org/wiki/Port_(computer_networking),如这里的’ 3333 ',是一台计算机同时有许多程序相互通信的一种方式。每个程序都使用自己的端口,因此当客户端连接到特定的端口时,计算机就知道要把它发送到哪个程序。如果你只想允许连接到localhost
,即IP地址为127.0.0.1
的主机名,你可以改为127.0.0.1:3333
。
你的http.ListenAndServe
函数也为http.Handler
参数传递了一个nil
值。这告诉ListenAndServe
函数,你想使用默认的服务器多路复用器,而不是你设置的那个。
ListenAndServe
是一个阻塞调用,这意味着你的程序在ListenAndServe
结束运行之前不会继续运行。然而,ListenAndServe
在你的程序结束运行或者HTTP服务器被告知关闭之前不会结束运行。即使ListenAndServe
阻塞了,而且你的程序没有包含关闭服务器的方法,但包含错误处理仍然很重要,因为调用ListenAndServe
可能会失败。因此,将错误处理添加到main
函数的ListenAndServe
中,如下所示:
main.go
...
func main() {
...
err := http.ListenAndServe(":3333", nil)
if errors.Is(err, http.ErrServerClosed) {
fmt.Printf("server closed\n")
} else if err != nil {
fmt.Printf("error starting server: %s\n", err)
os.Exit(1)
<^>}
}
你要检查的第一个错误是’ http.ErrServerClosed ',它会在服务器被告知关闭或关闭时返回。这通常是一个意料之中的错误,因为你需要自己关闭服务器,但它也可以用来在输出中显示服务器停止的原因。在第二个错误检查中,检查是否有其他错误。如果发生这种情况,它会将错误打印到屏幕上,然后使用os.Exit
函数退出程序,错误代码为1
。
在运行程序时,你可能会看到的一个错误是address already in use
错误。当ListenAndServe
无法监听你提供的地址或端口时,这个错误可能会返回,因为另一个程序已经在使用它。有时,如果端口是常用的,而你计算机上的另一个程序正在使用它,就会发生这种情况,但如果你多次运行自己程序的多个副本,也会发生这种情况。如果你在学习本教程时看到这个错误,请确保在再次运行程序之前已经停止了上一步的程序。
**注意:**如果你看到address already in use
错误,并且你没有运行你的程序的另一个副本,这可能意味着其他程序正在使用它。如果发生这种情况,无论你在哪里看到本教程中提到的3333
,将其更改为高于1024且低于65535的另一个数字,例如3334
,然后重试。如果仍然看到错误,你可能需要继续寻找没有被使用的端口。一旦你找到一个可以工作的端口,就使用它来执行本教程中的所有命令。
现在你的代码已经准备好了,保存你的main.go
文件并使用go run
运行你的程序。与您可能编写的其他Go程序不同,此程序不会立即自己退出。运行程序后,继续执行下面的命令:
go run main.go
由于程序仍在终端中运行,因此需要打开第二个终端与服务器交互。当你看到命令或输出与下面的命令相同的颜色时,意味着要在第二个终端中运行它。
在第二个终端中,使用curl程序向HTTP服务器发出HTTP请求。curl
是一个默认安装在许多系统上的实用工具,它可以向各种类型的服务器发出请求。在本教程中,你将使用它来发起HTTP请求。你的服务器在你计算机的3333
端口上监听连接,所以你需要在同一个端口上向localhost
发起请求:
curl http://localhost:3333
OutputThis is my website!
在输出中,你会看到This is my website!
,因为你访问了HTTP服务器上的/
路径。
现在,在相同的终端中,向相同的主机和端口发出请求,但在curl
命令的末尾添加/hello
路径:
curl http://localhost:3333/hello
OutputHello, HTTP!
这次你会看到Hello, HTTP!
返回getHello
函数的响应。
如果回到运行HTTP服务器函数的终端,现在会看到服务器输出的两行内容。一个用于/
请求,另一个用于/hello
请求:
Outputgot / request
got /hello request
因为