第一个 HTTP 服务
这是 Go 语言入门实践的第一个项目,我们将从零开始搭建一个简单的 HTTP 服务器。
项目结构
hello-server/
├── go.mod
├── go.sum
├── main.go
└── README.md
步骤 1:初始化项目
# 创建项目目录
mkdir hello-server
cd hello-server
# 初始化 Go Module
go mod init hello-server步骤 2:编写最简单的 HTTP 服务器
// main.go
package main
import (
"fmt"
"net/http"
)
// 处理函数:处理 HTTP 请求
func helloHandler(w http.ResponseWriter, r *http.Request) {
// w: 用于写入响应
// r: 包含请求信息
// 设置响应头
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
// 写入响应体
fmt.Fprintf(w, "Hello, World!")
}
func main() {
// 注册路由:将路径 "/" 映射到处理函数
http.HandleFunc("/", helloHandler)
// 启动服务器,监听 8080 端口
fmt.Println("服务器启动在 http://localhost:8080")
err := http.ListenAndServe(":8080", nil)
// 如果启动失败,打印错误
if err != nil {
fmt.Printf("服务器启动失败: %v\n", err)
}
}步骤 3:运行服务器
# 运行程序
go run main.go
# 或者先编译再运行
go build -o server main.go
./server步骤 4:测试服务器
# 在另一个终端测试
curl http://localhost:8080
# 或者在浏览器访问
# http://localhost:8080完整示例:多路由服务器
package main
import (
"fmt"
"net/http"
"time"
)
// 首页处理函数
func homeHandler(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<html>
<head>
<title>Go HTTP 服务器</title>
</head>
<body>
<h1>欢迎使用 Go HTTP 服务器</h1>
<p>当前时间: %s</p>
<ul>
<li><a href="/hello">Hello</a></li>
<li><a href="/about">About</a></li>
<li><a href="/api/info">API Info</a></li>
</ul>
</body>
</html>
`
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, html, time.Now().Format("2006-01-02 15:04:05"))
}
// Hello 处理函数
func helloHandler(w http.ResponseWriter, r *http.Request) {
// 获取查询参数
name := r.URL.Query().Get("name")
if name == "" {
name = "World"
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "Hello, %s!", name)
}
// About 处理函数
func aboutHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "这是一个用 Go 语言编写的简单 HTTP 服务器")
}
// API Info 处理函数(返回 JSON)
func apiInfoHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json := `{
"name": "Go HTTP Server",
"version": "1.0.0",
"status": "running"
}`
fmt.Fprintf(w, json)
}
func main() {
// 注册多个路由
http.HandleFunc("/", homeHandler)
http.HandleFunc("/hello", helloHandler)
http.HandleFunc("/about", aboutHandler)
http.HandleFunc("/api/info", apiInfoHandler)
// 启动服务器
port := ":8080"
fmt.Printf("服务器启动在 http://localhost%s\n", port)
fmt.Println("可用路由:")
fmt.Println(" - http://localhost:8080/")
fmt.Println(" - http://localhost:8080/hello?name=Go")
fmt.Println(" - http://localhost:8080/about")
fmt.Println(" - http://localhost:8080/api/info")
err := http.ListenAndServe(port, nil)
if err != nil {
fmt.Printf("服务器启动失败: %v\n", err)
}
}使用自定义 Server
package main
import (
"fmt"
"net/http"
"time"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
// 创建自定义 Server
server := &http.Server{
Addr: ":8080", // 监听地址
Handler: nil, // 使用默认的 DefaultServeMux
ReadTimeout: 10 * time.Second, // 读取超时
WriteTimeout: 10 * time.Second, // 写入超时
IdleTimeout: 120 * time.Second, // 空闲超时
}
// 注册路由
http.HandleFunc("/", helloHandler)
fmt.Println("服务器启动在 http://localhost:8080")
// 启动服务器
err := server.ListenAndServe()
if err != nil {
fmt.Printf("服务器启动失败: %v\n", err)
}
}处理不同的 HTTP 方法
package main
import (
"fmt"
"net/http"
)
func userHandler(w http.ResponseWriter, r *http.Request) {
// 根据 HTTP 方法处理不同的逻辑
switch r.Method {
case http.MethodGet:
// GET 请求:获取用户信息
id := r.URL.Query().Get("id")
fmt.Fprintf(w, "获取用户 ID: %s", id)
case http.MethodPost:
// POST 请求:创建用户
// 读取请求体
name := r.FormValue("name")
email := r.FormValue("email")
fmt.Fprintf(w, "创建用户: %s (%s)", name, email)
case http.MethodPut:
// PUT 请求:更新用户
id := r.URL.Query().Get("id")
name := r.FormValue("name")
fmt.Fprintf(w, "更新用户 ID: %s, 新名称: %s", id, name)
case http.MethodDelete:
// DELETE 请求:删除用户
id := r.URL.Query().Get("id")
fmt.Fprintf(w, "删除用户 ID: %s", id)
default:
// 不支持的 HTTP 方法
http.Error(w, "不支持的 HTTP 方法", http.StatusMethodNotAllowed)
}
}
func main() {
http.HandleFunc("/user", userHandler)
fmt.Println("服务器启动在 http://localhost:8080")
http.ListenAndServe(":8080", nil)
}读取请求数据
package main
import (
"fmt"
"io"
"net/http"
)
func dataHandler(w http.ResponseWriter, r *http.Request) {
// 1. 读取查询参数
name := r.URL.Query().Get("name")
age := r.URL.Query().Get("age")
fmt.Printf("查询参数: name=%s, age=%s\n", name, age)
// 2. 读取表单数据(application/x-www-form-urlencoded)
if r.Method == http.MethodPost {
// 解析表单
r.ParseForm()
// 获取表单字段
username := r.Form.Get("username")
password := r.Form.Get("password")
fmt.Printf("表单数据: username=%s, password=%s\n", username, password)
// 或者直接获取
username2 := r.FormValue("username")
fmt.Printf("直接获取: %s\n", username2)
}
// 3. 读取 JSON 请求体
if r.Header.Get("Content-Type") == "application/json" {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "读取请求体失败", http.StatusBadRequest)
return
}
defer r.Body.Close()
fmt.Printf("JSON 数据: %s\n", string(body))
}
// 4. 读取请求头
contentType := r.Header.Get("Content-Type")
userAgent := r.Header.Get("User-Agent")
fmt.Printf("Content-Type: %s\n", contentType)
fmt.Printf("User-Agent: %s\n", userAgent)
// 响应
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "数据接收成功")
}
func main() {
http.HandleFunc("/data", dataHandler)
fmt.Println("服务器启动在 http://localhost:8080")
http.ListenAndServe(":8080", nil)
}返回 JSON 响应
package main
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
// 定义响应结构
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
Time string `json:"time"`
}
// 用户结构
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func userHandler(w http.ResponseWriter, r *http.Request) {
// 创建用户数据
user := User{
ID: 1,
Name: "Alice",
Email: "alice@example.com",
}
// 创建响应
response := Response{
Code: 200,
Message: "成功",
Data: user,
Time: time.Now().Format("2006-01-02 15:04:05"),
}
// 设置响应头
w.Header().Set("Content-Type", "application/json; charset=utf-8")
// 编码 JSON
jsonData, err := json.Marshal(response)
if err != nil {
http.Error(w, "JSON 编码失败", http.StatusInternalServerError)
return
}
// 写入响应
w.Write(jsonData)
}
func main() {
http.HandleFunc("/user", userHandler)
fmt.Println("服务器启动在 http://localhost:8080")
http.ListenAndServe(":8080", nil)
}静态文件服务
package main
import (
"fmt"
"net/http"
)
func main() {
// 提供静态文件服务
// 将 /static/ 路径映射到 ./static/ 目录
fs := http.FileServer(http.Dir("./static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
// 首页
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<html>
<head>
<title>静态文件服务</title>
</head>
<body>
<h1>静态文件服务示例</h1>
<p>访问 <a href="/static/test.txt">/static/test.txt</a></p>
</body>
</html>
`
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, html)
})
fmt.Println("服务器启动在 http://localhost:8080")
fmt.Println("静态文件目录: ./static/")
http.ListenAndServe(":8080", nil)
}错误处理
package main
import (
"fmt"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
// 检查请求方法
if r.Method != http.MethodGet {
http.Error(w, "只支持 GET 方法", http.StatusMethodNotAllowed)
return
}
// 处理逻辑
fmt.Fprintf(w, "Hello, World!")
}
func main() {
http.HandleFunc("/", helloHandler)
port := ":8080"
fmt.Printf("服务器启动在 http://localhost%s\n", port)
// 使用 log.Fatal 自动处理错误
// 如果启动失败,会自动退出程序
log.Fatal(http.ListenAndServe(port, nil))
}完整示例:带日志的服务器
package main
import (
"fmt"
"log"
"net/http"
"time"
)
// 日志中间件
func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 调用下一个处理函数
next(w, r)
// 记录日志
log.Printf(
"%s %s %s %v",
r.Method,
r.RequestURI,
r.RemoteAddr,
time.Since(start),
)
}
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
// 使用中间件包装处理函数
http.HandleFunc("/", loggingMiddleware(helloHandler))
port := ":8080"
fmt.Printf("服务器启动在 http://localhost%s\n", port)
log.Fatal(http.ListenAndServe(port, nil))
}测试服务器
使用 curl 测试
# GET 请求
curl http://localhost:8080/
# GET 请求带参数
curl "http://localhost:8080/hello?name=Go"
# POST 请求
curl -X POST http://localhost:8080/user \
-d "name=Alice&email=alice@example.com"
# POST JSON 请求
curl -X POST http://localhost:8080/api/user \
-H "Content-Type: application/json" \
-d '{"name":"Alice","email":"alice@example.com"}'使用浏览器测试
直接在浏览器访问:
常见问题
1. 端口被占用
// 错误:listen tcp :8080: bind: address already in use
// 解决:更换端口或关闭占用端口的程序
port := ":8081" // 使用其他端口2. 跨域问题
// 添加 CORS 头
func corsMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next(w, r)
}
}3. 优雅关闭
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
server := &http.Server{
Addr: ":8080",
Handler: nil,
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
})
// 在 goroutine 中启动服务器
go func() {
fmt.Println("服务器启动在 http://localhost:8080")
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Printf("服务器错误: %v\n", err)
}
}()
// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
fmt.Println("正在关闭服务器...")
// 优雅关闭:等待 5 秒让现有请求完成
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
fmt.Printf("服务器关闭错误: %v\n", err)
}
fmt.Println("服务器已关闭")
}总结
通过这个项目,我们学会了:
- 创建 HTTP 服务器:使用
http.ListenAndServe - 注册路由:使用
http.HandleFunc - 处理请求:编写处理函数
- 读取请求数据:查询参数、表单、JSON
- 返回响应:文本、HTML、JSON
- 错误处理:使用
http.Error - 中间件:包装处理函数
- 优雅关闭:使用
Shutdown
这是 Go Web 开发的基础,接下来可以学习使用 Web 框架(如 Gin、Echo)来简化开发!