📚 原创系列: “Go语言学习系列”
🔄 转载说明: 本文最初发布于"Gopher部落"微信公众号,经原作者授权转载。
🔗 关注原创: 欢迎扫描文末二维码,关注"Gopher部落"微信公众号获取第一手Go技术文章。
📑 Go语言学习系列导航
🚀 第二阶段:基础巩固篇本文是【Go语言学习系列】的第19篇,当前位于第二阶段(基础巩固篇)
- 13-包管理深入理解
- 14-标准库探索(一):io与文件操作
- 15-标准库探索(二):字符串处理
- 16-标准库探索(三):时间与日期
- 17-标准库探索(四):JSON处理
- 18-标准库探索(五):HTTP客户端
- 19-标准库探索(六):HTTP服务器 👈 当前位置
- 20-单元测试基础
- 21-基准测试与性能剖析入门
- 22-反射机制基础
- 23-Go中的面向对象编程
- 24-函数式编程在Go中的应用
- 25-context包详解
- 26-依赖注入与控制反转
- 27-第二阶段项目实战:RESTful API服务
📖 文章导读
在本文中,您将了解:
- Go语言HTTP服务器的核心概念和设计理念
- 处理HTTP请求和响应的基础知识
- 路由系统和中间件的实现与使用
- 静态文件服务和模板渲染
- HTTP服务器的性能优化和安全配置
Go语言的net/http包提供了功能强大且性能卓越的HTTP服务器实现,使我们能够轻松构建从简单API到复杂Web应用的各种服务。本文将深入探讨这些功能,帮助您掌握使用Go开发高性能HTTP服务器的关键技巧。
1. HTTP服务器基础
Go语言的net/http包是构建HTTP服务的核心,它提供了简洁而强大的API,使我们能够快速构建高性能的HTTP服务器。
1.1 创建最小HTTP服务器
创建一个HTTP服务器在Go中非常简单,只需几行代码:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
// 注册处理函数
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "你好,Go HTTP服务器!")
})
// 启动服务器
log.Println("服务器启动在 :8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
运行这段代码,然后在浏览器中访问http://localhost:8080,你会看到"你好,Go HTTP服务器!"的消息。
http.ListenAndServe函数是启动HTTP服务器的主要方式,它接收两个参数:
- 监听的地址(如":8080"表示所有网络接口的8080端口)
- 请求处理器(传递
nil表示使用默认处理器)
1.2 处理器函数与处理器接口
Go的HTTP服务器架构围绕两个核心概念构建:
- 处理器函数(HandlerFunc):一个签名为
func(http.ResponseWriter, *http.Request)的函数 - 处理器(Handler):任何实现了
ServeHTTP(http.ResponseWriter, *http.Request)方法的类型
以下是两种方式的示例:
package main
import (
"fmt"
"log"
"net/http"
)
// 处理器函数
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "你好,世界!")
}
// 处理器类型
type CounterHandler struct {
counter int
}
// 实现ServeHTTP方法
func (h *CounterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.counter++
fmt.Fprintf(w, "你是第 %d 位访问者!", h.counter)
}
func main() {
// 注册处理器函数
http.HandleFunc("/hello", helloHandler)
// 注册处理器
counter := &CounterHandler{
}
http.Handle("/counter", counter)
log.Println("服务器启动在 :8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
处理器对象的优势在于可以保持状态(如上例中的counter)和封装行为,而处理器函数则更简洁直观。
1.3 请求处理详解
当HTTP请求到达服务器时,http.Request结构包含了所有请求信息。以下是如何访问这些信息:
func requestInfoHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "===== 请求信息 =====")
fmt.Fprintf(w, "方法: %s\n", r.Method)
fmt.Fprintf(w, "URL路径: %s\n", r.URL.Path)
fmt.Fprintf(w, "协议版本: %s\n", r.Proto)
// 查询参数
fmt.Fprintln(w, "\n----- 查询参数 -----")
for key, values := range r.URL.Query() {
for _, value := range values {
fmt.Fprintf(w, "%s: %s\n", key, value)
}
}
// 请求头
fmt.Fprintln(w, "\n----- 请求头 -----")
for key, values := range r.Header {
for _, value := range values {
fmt.Fprintf(w, "%s: %s\n", key, value)
}
}
// 远程地址
fmt.Fprintf(w, "\n远程地址: %s\n", r.RemoteAddr)
}
请求体读取
对于POST、PUT等包含请求体的请求,可以通过以下方式读取:
func bodyHandler(w http.ResponseWriter, r *http.Request) {
// 限制请求体大小,防止恶意请求
r.Body = http.MaxBytesReader(w, r.Body, 1024*1024) // 限制为1MB
// 读取请求体
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "读取请求体失败", http.StatusBadRequest)
return
}
defer r.Body.Close()
fmt.Fprintf(w, "收到请求体:\n%s", body)
}
解析表单数据
对于表单提交,Go提供了便捷的解析方法:
func formHandler(w http.ResponseWriter, r *http.Request) {
// 解析表单数据(包括URL查询参数和表单字段)
if err := r.ParseForm(); err != nil {
http.Error(w, "解析表单失败", http.StatusBadRequest)
return
}
// 访问表单数据
fmt.Fprintln(w, "表单数据:")
for key, values := range r.Form {
fmt.Fprintf(w, "%s: %s\n", key, strings.Join(values, ", "))
}
// 仅访问表单字段(不包括URL查询参数)
fmt.Fprintln(w, "\n仅表单字段:")
for key, values := range r.PostForm {
fmt.Fprintf(w, "%s: %s\n", key, strings.Join(values, ", "))
}
}
解析多部分表单(文件上传)
文件上传需要特殊处理:
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// 设置文件上传最大尺寸为10MB
r.ParseMultipartForm(10 << 20)
// 获取上传的文件
file, handler, err := r.FormFile("file")
if err != nil {
http.Error(w, "获取上传文件失败", http.StatusBadRequest)
return
}
defer file.Close()
fmt.Fprintf(w, "上传的文件信息:\n")
fmt.Fprintf(w, "文件名: %s\n", handler.Filename)
fmt.Fprintf(w, "文件大小: %d字节\n", handler.Size)
fmt.Fprintf(w, "MIME类型: %s\n", handler.Header.Get("Content-Type"))
// 获取其他表单字段
if description := r.FormValue("description"); description != "" {
fmt.Fprintf(w, "文件描述: %s\n", description)
}
// 保存文件(在实际应用中,应检查文件类型和进行安全验证)
tempFile, err := os.CreateTemp("", handler.Filename)
if err != nil {
http.Error(w, "创建临时文件失败", http.StatusInternalServerError)
return
}
defer tempFile.Close()
_, err = io.Copy(tempFile, file)
if err != nil {
http.Error(w, "保存文件失败", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "文件已保存为: %s\n", tempFile.Name())
}
1.4 响应写入
HTTP响应通过http.ResponseWriter接口构建。以下是常见的响应操作:
func responseHandler(w http.ResponseWriter, r *http.Request) {
// 设置状态码
w.WriteHeader(http.StatusOK) // 200 OK
// 设置响应头
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("X-Custom-Header", "自定义值")
// 写入响应体
fmt.Fprintln(w, "<h1>响应示例</h1>")
fmt.Fprintln(w, "<p>这是一个HTTP响应示例。</p>")
}
注意:
- 必须在调用
WriteHeader或写入响应体前设置响应头 - 如果不显式调用
WriteHeader,第一次写入响应体时会隐式发送状态码200
不同类型的响应
// 返回JSON响应
func jsonResponse(w http.ResponseWriter, r *http.Request) {
// 创建响应数据
data := struct {
Message string `json:"message"`
Time string `json:"time"`
Status int `json:"status"`
}{
Message: "操作成功",
Time: time.Now().Format(time.RFC3339),
Status: 1,
}
// 转换为JSON
jsonData, err := json.Marshal(data)
if err != nil {
http.Error(w, "JSON编码失败", http.StatusInternalServerError)
return
}
// 设置头部和状态码
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
// 写入响应
w.Write(jsonData)
}
// 返回文件下载
func fileDownloadHandler(w http.ResponseWriter, r *http.Request) {
file, err := os.Open("example.pdf")
if err != nil {
http.Error(w, "文件不存在", http.StatusNotFound)
return
}
defer file.Close()
// 获取文件信息
fileInfo, err := file.Stat()
if err != nil {
http.Error(w, "无法获取文件信息", http.StatusInternalServerError)
return
}
// 设置响应头
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileInfo.Name()))
w.Header().Set("Content-Type", "application/pdf")
w.Header().Set("Content-Length", fmt.Sprintf("%d", fileInfo.Size()))
// 复制文件内容到响应
_, err = io.Copy(w, file)
if err != nil {
http.Error(w, "文件传输失败", http.StatusInternalServerError)
return
}
}
// 重定向响应
func redirectHandler(w http.ResponseWriter, r *http.Request) {
target := "/new-location"
if r.URL.Query().Get("type") == "temporary" {
// 临时重定向 (HTTP 302)
http.Redirect(w, r, target, http.StatusFound)
} else {
// 永久重定向 (HTTP 301)
http.Redirect(w, r, target, http.StatusMovedPermanently)
}
}
1.5 服务器生命周期
理解HTTP服务器的启动、运行和关闭过程对于构建可靠的应用至关重要:
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"time"
)
func main() {
// 创建路由
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "服务器运行中...")
})
// 配置服务器
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
// 在goroutine中启动服务器
go func() {
log.Println("服务器启动在 :8080...")
if err := server.ListenAndServe()

最低0.47元/天 解锁文章
221

被折叠的 条评论
为什么被折叠?



