11.3 包net/http
在Go语言中,包net/http提供了创建 Web 服务器和客户端的功能,支持 HTTP 和 HTTPS 协议。
11.3.1 包net/http的功能和内置成员
在Go语言中,包net/http的主要功能有:创建 HTTP 服务器和客户端、处理 HTTP 请求和响应、操作 HTTP 头信息、实现 HTTP 接口和HTTPS操作。以下是包net/http中的主要内置成员:
- 常量http.MethodGet、http.MethodPost、http.MethodPut、http.MethodDelete:表示 HTTP 请求的方法。
- 常量http.StatusContinue、http.StatusOK、http.StatusNotFound、http.StatusInternalServerError:表示 HTTP 响应状态码。
- 类型http.DefaultClient:表示一个默认的 HTTP 客户端,可以直接使用该客户端发送请求。
- 类型http.HandlerFunc:表示一个 HTTP 处理函数,可以将普通函数转换为 HTTP 处理器。
- 方法http.FileServer():创建一个静态文件服务器,并返回一个实现了 http.Handler 接口的对象,用于处理静态资源的请求。
- 类型http.Cookie:表示一个 HTTP Cookie,包括 Cookie 名称、值、过期时间等属性。
总之,包net/http是 Go 语言中用于实现 HTTP 协议的标准库,提供了丰富的功能和内置成员,可以方便地创建和处理 HTTP 请求、响应以及头信息等,同时也支持 HTTPS,是开发基于 HTTP 协议的 Web 应用程序的必要工具。
11.3.2 类型http.DefaultClient
http.DefaultClient是Go语言包net/http中的一个默认 HTTP 客户端实例,它提供了发送 HTTP 请求的方法,并支持连接池等高级特性。具体来说,http.DefaultClient 的主要功能如下:
- 发送 HTTP 请求:http.DefaultClient.Do() 方法可以发送一个 HTTP 请求,并返回一个 http.Response 对象。
- 支持超时和重试:http.Client.Timeout 属性表示客户端发送请求的超时时间;httpretry 包可以实现 HTTP 请求的重试逻辑。
- 支持连接池:http.Transport 类型是 http.DefaultClient 内部使用的 HTTP 连接池,可以复用 TCP 连接以提高效率,同时也支持最大空闲连接数、最大空闲时间等配置。
- 支持代理服务器:http.ProxyFromEnvironment() 方法可以根据环境变量自动设置代理服务器。
类型http.DefaultClient中的常用内置成员如下:
- http.Client.Timeout:表示客户端发送请求的超时时间;默认为无穷大。
- http.Client.Jar:表示客户端的 Cookie 存储器,用于管理 HTTP 请求和响应中的 Cookie。
- http.Client.CheckRedirect 属性:表示是否允许 HTTP 重定向,默认允许重定向。
- http.Client.Transport:表示客户端内部使用的 HTTP 连接池。
本实例实现了一个简单的命令行天气查询工具,用户可以通过命令行输入城市名称,程序会向 OpenWeatherMap 发送 HTTP 请求并获取该城市的天气预报信息。在开发本程序之前,需要先在OpenWeatherMap获取一个密钥,注册申请“API密钥”的地址是:
https://home.openweathermap.org/users/sign_up
实例11-5:天气预报程序(源码路径:Go-codes\11\tianqi.go)
实例文件tianqi.go的具体实现代码如下所示。
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
)
const APIKey = "your_api_key_here" // 请替换为你的 OpenWeatherMap API Key
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: weather <city>")
return
}
city := strings.Join(os.Args[1:], " ")
url := fmt.Sprintf("https://api.openweathermap.org/data/2.5/weather?q=%s&units=metric&appid=%s", city, APIKey)
resp, err := http.Get(url)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
var data map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&data)
if err != nil {
fmt.Println(err)
return
}
if data["cod"].(float64) == 200 {
name := data["name"].(string)
temp := data["main"].(map[string]interface{})["temp"].(float64)
desc := data["weather"].([]interface{})[0].(map[string]interface{})["description"].(string)
fmt.Printf("%s: %.1f℃ %s\n", name, temp, desc)
} else {
fmt.Println(data["message"])
}
}
对上述代码的具体说明如下:
- 首先使用方法http.DefaultClient.Get()发送一个 HTTP GET 请求,并在 URL 中指定了城市名称和 OpenWeatherMap 的 API Key。
- 然后,使用方法json.NewDecoder()解析HTTP 响应,并提取出其中的城市名称、气温和天气描述等信息。
- 最后,将这些信息输出到命令行中。
执行后如果输入的城市名称是有效的,程序会输出该城市的名称、气温和天气描述等信息,例如:
go run weather.go Beijing
Beijing: 27.2℃ scattered clouds
如果输入的城市名称无效或发生其他错误,程序会输出相应的错误信息,例如:
go run weather.go invalid_city_name
city not found
11.3.3 类型http.HandlerFunc
http.HandlerFunc是Go语言包net/http中的一个类型,能够将普通函数转换成 http.Handler 接口。通过使用 http.HandlerFunc,可以方便地定义 HTTP 请求处理器,并且可以像普通函数一样传递参数和返回值。以下是 http.HandlerFunc 的主要功能:
- 实现 HTTP 处理器:http.HandlerFunc 实现了 http.Handler 接口,可以作为 HTTP 处理器使用,当有请求到达时会自动调用其 ServeHTTP() 方法。
- 支持传参和返回值:http.HandlerFunc 能够接收任意类型的函数,并将其转换为 http.Handler 接口,从而支持传参和返回值。
在类型http.HandlerFunc中主要包含如下所示的内置成员:
- http.HandlerFunc.ServeHTTP():表示 HTTP 请求处理器的核心方法,它接收一个 http.ResponseWriter 对象和一个 *http.Request 对象,并根据请求信息生成响应结果。
- http.HandlerFunc(func(w http.ResponseWriter, r *http.Request)) :表示用于创建 http.HandlerFunc 对象的构造函数。
请看下面的实例,功能是使用 http.HandlerFunc实现了一个简单的猜数字游戏。用户可以在浏览器中访问该服务,并通过输入框输入一个数字,程序会判断该数字是否等于随机生成的一个数字,然后给出相应的提示。
实例11-6:猜数字游戏(源码路径:Go-codes\11\guss.go)
实例文件guss.go的具体实现代码如下所示。
import (
"crypto/rand"
"fmt"
"math/big"
"net/http"
"strconv"
"strings"
)
func main() {
http.HandleFunc("/", guessHandler)
fmt.Println("Guess game is running on http://localhost:8089")
err := http.ListenAndServe(":8089", nil)
if err != nil {
fmt.Println(err)
}
}
func guessHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
w.Write([]byte(`
<html>
<head><title>Guess Game</title></head>
<body>
<h1>Welcome to Guess Game!</h1>
<p>I'm thinking of a number between 1 and 100. Can you guess it?</p>
<form method="POST">
<input type="text" name="guess" placeholder="Enter your guess">
<input type="submit" value="Submit">
</form>
</body>
</html>
`))
} else if r.Method == "POST" {
guessStr := strings.TrimSpace(r.FormValue("guess"))
guess, err := strconv.Atoi(guessStr)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Invalid input"))
return
}
target, err := rand.Int(rand.Reader, big.NewInt(100))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
if guess == int(target.Int64())+1 {
w.Write([]byte("Congratulations! You got it!"))
} else if guess < int(target.Int64())+1 {
w.Write([]byte("Too low! Try again."))
} else {
w.Write([]byte("Too high! Try again."))
}
} else {
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte("Method not allowed"))
}
}
对上述代码的具体说明如下:
- 首先定义了函数guessHandler(),并将其转换为 http.HandlerFunc 类型;
- 使用包crypto/rand中提供的方法Int()生成了一个大数(在本例中为 100),然后将其转换为一个整型数作为目标数字。这种方法比简单地使用随机种子更加安全和可靠。
- 当浏览器发送 GET 请求时,该函数会返回一个包含输入框的 HTML 页面;
- 当浏览器发送 POST 请求时,该函数会读取用户输入的数字并与随机生成的数字进行比较,然后给出相应的提示。
执行成功后在浏览器中访问http://localhost:8089,当在输入框输入猜测的数字时,程序会判断该数字是否等于随机生成的一个数字,然后给出相应的提示。如果你输入的数字与程序随机生成的数字相等,则会输出以下消息:
Congratulations! You got it!
如果你输入的数字小于程序随机生成的数字,则会输出以下消息:
Too low! Try again.
如果你输入的数字大于程序随机生成的数字,则会输出以下消息:
Too high! Try again.
如果你输入的不是数字,则会输出以下消息:
Invalid input
如果程序在内部遇到了错误,则会输出以下消息:
Internal server error
如果你使用了除 GET 和 POST 以外的 HTTP 方法,则会输出以下消息:
Method not allowed
最后,程序会一直运行在 http://localhost:8080 地址上,直到你按下 Ctrl+C 停止它。
11.3.4 类型http.Cookie
http.Cookie是Go语言包net/http中的一个类型,表示一个 HTTP Cookie。Cookie 是一种用于在客户端和服务器之间传递信息的机制,通常用于存储用户会话信息、记录用户偏好设置等。在类型http.Cookie中主要包含如下所示的内置成员:
- http.Cookie.Name:表示 Cookie 的名称。
- http.Cookie.Value:表示 Cookie 的值。
- http.Cookie.Path:表示 Cookie 的作用路径,默认值为 "/"。
- http.Cookie.Domain:表示 Cookie 的作用域名,默认值为当前访问的域名。
- http.Cookie.Expires:表示 Cookie 的过期时间。
- http.Cookie.MaxAge:表示 Cookie 的最大存活时间,以秒为单位。
- http.Cookie.Secure:表示 Cookie 只能通过 HTTPS 连接传输。
- http.Cookie.HttpOnly:表示 Cookie 不能被 JavaScript 访问。
下面是一个使用 http.Cookie的例子,实现了一个简单的登录功能。用户可以在浏览器中输入用户名和密码,并选择是否记住登录状态。如果勾选了“记住我”选项,程序会将用户登录信息保存到 Cookie 中,以便在下次访问时自动登录。
实例11-7:用户登录验证系统(源码路径:Go-codes\11\login.go)
实例文件login.go的具体实现代码如下所示。
import (
"fmt"
"html/template"
"net/http"
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("username")
// 如果存在 Cookie,则直接登录
if err == nil && cookie.Value != "" {
http.Redirect(w, r, "/welcome", http.StatusFound)
return
}
// 否则显示登录表单
tmpl := template.Must(template.ParseFiles("index.html"))
tmpl.Execute(w, nil)
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
username := r.PostFormValue("username")
password := r.PostFormValue("password")
if username == "admin" && password == "admin123" {
http.SetCookie(w, &http.Cookie{
Name: "username",
Value: username,
})
http.Redirect(w, r, "/welcome", http.StatusFound)
} else {
tmpl := template.Must(template.ParseFiles("login.html"))
err := tmpl.Execute(w, "Invalid username or password")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
}
func welcomeHandler(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("username")
if err != nil {
http.Redirect(w, r, "/", http.StatusFound)
return
}
tmpl := template.Must(template.ParseFiles("welcome.html"))
err = tmpl.Execute(w, []string{cookie.Value})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func main() {
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
http.HandleFunc("/", indexHandler)
http.HandleFunc("/login", loginHandler)
http.HandleFunc("/welcome", welcomeHandler)
fmt.Println("Server starting...")
http.ListenAndServe(":8089", nil)
}
在上述实例代码中定义了三个 HTTP 请求处理器函数:indexHandler()、loginHandler() 和 welcomeHandler(),具体说明如下:
- indexHandler():显示包含用户名和密码输入框的登录表单;
- loginHandler():处理用户提交的表单,判断用户是否输入正确的用户名和密码,并根据用户是否勾选“记住我”选项设置 Cookie;
- welcomeHandler():显示欢迎页面,如果存在有效的 Cookie则显示 Cookie中存储的用户名。
执行后,当我们访问 http://localhost:8089/login 时,会直接显示 login.html文件的登录表单内容,如图11-1所示。在该页面上可以输入用户名和密码,并选择是否记住登录状态。如果勾选了“记住我”选项,则下次访问时将自动登录。如果输入的用户名和密码正确,则会跳转到欢迎页面,并输出欢迎消息,如图11-2所示。