客户端/服务器
假设你开发了一个使用 Go 语言编写的程序来管理你的照片库,这个程序可以在你的电脑上处理你的照片。你可能想与其他家庭成员分享这些照片,为此,你可以通过电子邮件发送包含所有照片的附件。但如果你拍了一万张照片,这种解决方案可能是不可行的。
您可以将内容自动上传到您最喜爱的社交网络。如果您必须一次处理一张照片,该操作可能会变得非常耗时。另一种解决方案可能是将您的程序直接插入社交网络系统,以编程方式上传图片。
我们可以借助社交网络公开的 API 来做到这一点。您可以通过直接调用它们的 API 将图片推送到 for 循环中。
在这种情况下,您将使用一个 API。你是客户端。社交网络代表服务器。
调用 API 意味着按照精确的文档向 Web 服务器发出 HTTP(s) 请求。客户端和服务器这两个术语很重要,您必须记住它们。作为客户端,我们使用(或消费)API。服务器是一个计算机程序,旨在接受和响应客户端的 API 调用。
Go 语言在创建简单高效的 Web 服务器方面很有优势。Go 语言提供了内置的 HTTP 包,其中包含了快速创建 Web 或文件服务器所需的实用工具。这使得使用 Go 语言创建 Web 服务器和 Web 服务变得简单和高效。
创建 server.go 文件
在上一篇文章中,我们学习了 net/http 包中的 Request 和 Response ,所以这里我们可以直接使用:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello!")
})
fmt.Printf("Starting server at port 8088\n")
if err := http.ListenAndServe(":8088", nil); err != nil {
log.Fatal(err)
}
}
运行 go run server.go ,可以在终端上看到如下输出:
Starting server at port 8088
在此阶段,我们将创建一个实际在端口 8088 上提供服务并可以响应传入 GET 请求的 Web 服务器。让我们在端口 8088 启动 Web 服务器。ListenAndServe() 方法由我们在第一步中导入的 http 数据包导出。此方法允许我们启动 Web 服务器并指定端口以侦听传入请求。请注意,端口参数需要作为以冒号标点符号开头的字符串传递。第二个参数接受一个处理程序来为 HTTP/2 配置服务器,将 nil 作为第二个参数传递。
我们将使用 HandleFunc() 函数将路由处理程序添加到 Web 服务器。第一个参数接受它需要监听的路径 /hello。在这里,您告诉服务器监听对 http://localhost:8088/hello 的任何传入请求。第二个参数接受一个函数,该函数包含正确响应请求的业务逻辑。
默认情况下,此函数接受 ResponseWriter 以发回响应,并接受 Request 对象以提供有关请求本身的更多信息。例如,您可以访问有关已发送标头的信息,这对于验证请求很有用。
如您所见,处理程序发送了一个“Hello!”消息,因为我们将此响应传递给 ResponseWriter。
路由添加基本校验
不用说,安全很重要。让我们探索一些增强 Go Web 服务器安全性的基本策略。在我们这样做之前,我们应该花点时间来提高代码的可读性。让我们创建 helloHandler 函数,它包含与 /hello 请求相关的所有逻辑。
func helloHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/hello" {
http.Error(w, "404 not found.", http.StatusNotFound)
return
}
if r.Method != "GET" {
http.Error(w, "Method is not supported.", http.StatusNotFound)
return
}
fmt.Fprintf(w, "Hello!")
}
此处理程序使用 Request 对象检查请求的路径是否正确。这是一个非常基本的示例,说明如何使用 Request 对象。
如果路径不正确,服务器将向用户返回 StatusNotFound 错误。要向用户写入错误,您可以使用 http.Error 方法。请注意,StatusNotFound 代码对应于 404 错误。所有状态代码都可以在 Golang 文档中找到。
接下来,我们添加一个检查来验证请求的类型。如果该方法不对应于 GET,则服务器返回一个新错误。当两个检查都通过时,服务器返回其成功响应“Hello!”。
我们需要做的最后一件事是修改 main() 函数中的 handleFunc 函数以接受上面的 helloHandler 函数。
http.HandleFunc("/hello", helloHandler)
完整的 server.go 文件如下:
package main
import (
"fmt"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/hello" {
http.Error(w, "404 not found", http.StatusNotFound)
return
}
if r.Method != "GET" {
http.Error(w, "Method is not supported", http.StatusNotFound)
return
}
fmt.Fprintf(w, "Hello!")
}
func main() {
http.HandleFunc("/hello", helloHandler)
fmt.Printf("Starting server at port 8088\n")
if err := http.ListenAndServe(":8088", nil); err != nil {
log.Fatal(err)
}
}
接下来,我们将使用 go run server.go 启动 Go Web 服务器。您可以使用 Postman 或 cURL 等工具向 http://localhost:8088/hello 发送 POST 请求来测试您的安全性。我们将得到与上一次相同的结果。
启动静态 Web 服务器
在这一步中,我们将创建一个简单的文件服务器来托管静态文件。这将是对 Web 服务器的一个非常简单的添加。为确保我们有内容可在 Web 服务器上提供服务,让我们新建一个位于 static 文件夹中的 index.html 文件。为了简单起见,只需在文件中添加一个标题为“Go Server”的标题。如果您愿意,可以添加更多文件或样式文件以使您的 Web 服务器看起来更漂亮一些。
新建一个 index.html 文件:
<html>
<head>
<title>Learn Go</title>
</head>
<body>
<h2>Go Server</h2>
</body>
</html>
要为 static 文件夹提供服务,您必须向 server.go 添加两行代码。第一行代码使用 FileServer 函数创建文件服务器对象。此函数接受 http.Dir 类型的路径。因此,我们必须将字符串路径“./static”转换为 http.Dir 路径类型。
不要忘记指定 Handle 路由,它接受路径和文件服务器。此函数的作用与 HandleFunc 函数相同,但有一些细微差别。有关 FileServer 对象的更多信息,请查看文档。
func main() {
fileServer := http.FileServer(http.Dir("./static"))
http.Handle("/", fileServer)
http.HandleFunc("/hello", helloHandler)
fmt.Printf("Starting server at port 8088\n")
if err := http.ListenAndServe(":8088", nil); err != nil {
log.Fatal(err)
}
}
是时候尝试代码了。使用 go run server.go 启动服务器并访问 http://localhost:8088/。你应该看到:
接受表单提交 POST 请求
最后,Web 服务器必须响应表单提交。让我们向 static 文件夹中的 form.html 文件添加一些内容。请注意,表单操作已发送到 /form。这意味着来自表单的 POST 请求将被发送到 http://localhost:8088/form。表单本身要求输入两个变量:name 和 address。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div>
<form method="POST" actinotallow="/form">
<label>Name</label><input name="name" type="text" value="" />
<label>Address</label><input name="address" type="text" value="" />
<input type="submit" value="submit" />
</form>
</div>
</body>
</html>
下一步是创建处理程序来接受 /form 请求。 form.html 文件已经通过 FileServer 提供,可以通过 http://localhost:8088/form.html 访问。
首先,该函数必须调用 ParseForm() 来解析原始查询并更新 r.PostForm 和 r.Form。这将允许我们通过 r.FormValue 方法访问 name 和 address 值。
在函数的末尾,我们使用 fmt.Fprintf 将这两个值写入 ResponseWriter。
func formHandler(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
fmt.Fprintf(w, "ParseForm() err: %v", err)
return
}
fmt.Fprintf(w, "POST request successful")
name := r.FormValue("name")
address := r.FormValue("address")
fmt.Fprintf(w, "Name = %s\n", name)
fmt.Fprintf(w, "Address = %s\n", address)
}
不要忘记将新的表单处理程序路由添加到 main() 函数。
http.HandleFunc("/form", formHandler)
最后的 server.go 文件如下:
package main
import (
"fmt"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/hello" {
http.Error(w, "404 not found", http.StatusNotFound)
return
}
if r.Method != "GET" {
http.Error(w, "Method is not supported", http.StatusNotFound)
return
}
fmt.Fprintf(w, "Hello!")
}
func formHandler(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
fmt.Fprintf(w, "ParseForm() err: %v", err)
return
}
fmt.Fprintf(w, "POST request successful\n")
name := r.FormValue("name")
address := r.FormValue("address")
fmt.Fprintf(w, "Name = %s\n", name)
fmt.Fprintf(w, "Address = %s\n", address)
}
func main() {
// http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
// fmt.Fprintf(w, "Hello!")
// })
fileServer := http.FileServer(http.Dir("./static"))
http.Handle("/", fileServer)
http.HandleFunc("/hello", helloHandler)
http.HandleFunc("/form", formHandler)
fmt.Printf("Starting server at port 8088\n")
if err := http.ListenAndServe(":8088", nil); err != nil {
log.Fatal(err)
}
}
表单处理程序测试
我们可以通过使用 go run server.go 启动服务器来测试表单。当服务器启动时,访问 http://localhost:8088/form.html。您应该看到两个输入字段和一个提交按钮。
填写完表格后,点击提交按钮。服务器应处理您的 POST 请求并在 http://localhost:8088/form 响应页面上向您显示结果,例如以下响应:
如果你看到上面的结果,你已经成功地创建了你的第一个 Golang 网络和文件服务器。恭喜!如果您想进一步探索 Golang Web 服务器,Golang HTTP 包文档中有很多很好的例子。
官方文档:Writing Web Applications