[7天实战入门Go语言后端] Day 2:用 Go 写一个 HTTP 服务——net/http 入门

本日关键词(实战):net/http、ListenAndServe、HandleFunc、路由、Request、ResponseWriter、JSON、编码解码、curl 调试

本日语法/概念(实战):

语法/概念 实战用途 本日示例
http.ListenAndServe(addr, nil) 启动 HTTP 服务,日常写 API 必用 server/main.go
http.HandleFunc(path, handler) 注册路由,一个路径一个处理函数 server/main.go
*http.Request 读 Method、URL、Path、Header、Body(如 JSON) 解析路径参数、请求体
http.ResponseWriter 写状态码、写响应头、写 Body、写 JSON 返回 JSON 响应
r.Header.Get / w.Header().Set 读请求头、写响应头(如 X-Trace-Id 串联调用与日志) server/main.go setTraceID
json.Marshal / json.Unmarshal 结构体 ↔ JSON,前后端联调必备 返回/解析 JSON

获取实战代码 :如需在本地跑通本文示例,请克隆仓库 WenSongWang/go-quickstart-7days,本文示例在 day2 目录,克隆后在项目根目录执行下文中的命令即可。


一、本篇目标

学完本文并跑通本目录下的示例,你将掌握:

模块 内容
服务 用标准库 net/http 起一个 HTTP 服务
路由 http.HandleFunc 或自建 mux 匹配路径
请求 Method、URL、Body、Header 的读取
响应 状态码、响应头(如 X-Trace-Id)、JSON、Write 的写法

二、前置要求

  • 已完成 Day 1 (至少会 go run、能看懂基本语法)。
  • 本系列命令均在项目根目录执行。

三、示例与知识点(先混个眼熟)

示例目录 主要知识点
server/ http.HandleFunc 注册路由、(ResponseWriter, *Request) 处理函数、读 Path/Method/Header、设 Content-Type 与 X-Trace-IdWriteHeader/Writejson.NewEncoder(w).Encode 写 JSON、路径参数用 TrimPrefix+Atoi 解析

四、核心概念与最小示例(不看代码也能懂)

下面用极短说明 + 一句话把写 HTTP 接口时最容易懵的点说清。

Handler 的签名为什么是 (w http.ResponseWriter, r *http.Request)

标准库规定:你注册的处理函数必须长这样,第一个参数写响应,第二个参数读请求w 用来设置状态码、响应头、响应体;r 用来读 Method、URL、Header、Body。名字可以改,类型不能改。

为什么结构体字段要写 `json:"id"`?

Go 里结构体字段默认转成 JSON 时用的是首字母大写的字段名 (如 ID),但前端通常习惯小写(id)。用 struct tag `json:"id"` 告诉编码器:转成 JSON 时用 "id" 这个键。这样 User{ID:1, Name:"张三"} 会变成 {"id":1,"name":"张三"},前后端联调更顺手。

为什么要在 http.Error 之前先 Set Content-Type?

http.Error 内部会调用 WriteHeader,而 响应头必须在第一次 WriteHeader 之前全部设好 ,之后就不能再改。所以若想返回 JSON 格式的错误体,必须先 w.Header().Set("Content-Type", "application/json"),再调用 http.Error,否则浏览器可能按纯文本解析。

Marshal / Encode 一句话

  • json.Marshal :把结构体转成 []byte,自己再 w.Write(data);适合要改一改再写出的场景。
  • json.NewEncoder(w).Encode(v) :把结构体直接编码并写入 w,一步到位,写 API 响应时常用。

线上项目更多用哪个:HandleFunc 还是 mux?为什么?

线上项目更多用「第三方路由库」(如 chi、gin、echo)或「自建 mux」 ,而不是只用 http.HandleFunc + 默认 mux。原因主要有:

需求 说明
路径参数 /api/users/:id,标准库要自己 TrimPrefix + 解析,第三方库直接提供 Param(r, "id")
中间件链 日志、鉴权、超时等需要一层层包装 Handler,第三方库有现成的 Use/Group 等模式(Day 5 会学)。
路由分组与前缀 大量接口时按模块分组、统一加前缀更清晰,标准库要自己拼路径。

本日用 HandleFunc 是为了先吃透标准库的 Request/Response 模型,后面再用框架会更容易理解它在帮你封装什么。

本系列约定 :7 天全程使用标准库 net/http ,不在 Day 2 也不在后续某天引入 chi/gin 等第三方路由框架。后面 Day 5、Day 7 会循序渐进地用 http.NewServeMux + 自建中间件(日志、鉴权),仍是标准库写法,便于理解「框架在帮你封装什么」后再按需选型。

和实战上线项目的区别实战上线项目里很多会用框架(chi、gin、echo 等)或公司自研路由层,因为路径参数、中间件、分组、性能与可维护性更省事。本系列故意不用框架,是为了先打好标准库基础,将来上手框架或做技术选型时心里有数;真正做生产项目时,可以按团队规范选用框架或继续用标准库(小服务、内部工具用标准库也常见)。


五、Day 2 示例代码全文与逐段解读

下面把 day2/server/main.go 的完整代码贴出,并分段做简短解读。读者无需打开项目,按顺序看下去即可把 Day 2 学完。


完整代码(即 day2/server/main.go,含注释)

go 复制代码
// Day2 示例:标准库 HTTP 服务 + 简单路由 + JSON + trace_id
package main

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"net/http"
	"strconv"
	"strings"
)

// setTraceID 从请求头读 X-Trace-Id,没有则生成,并写回响应头(便于调用方与日志串联)
func setTraceID(w http.ResponseWriter, r *http.Request) {
	id := r.Header.Get("X-Trace-Id")
	if id == "" {
		b := make([]byte, 8)
		rand.Read(b)
		id = hex.EncodeToString(b)
	}
	w.Header().Set("X-Trace-Id", id)
}

// User 返回给前端的结构体
type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

// handleHello 处理 /hello
func handleHello(w http.ResponseWriter, r *http.Request) {
	setTraceID(w, r)
	if r.URL.Path != "/hello" {
		http.NotFound(w, r)
		return
	}
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("Hello, Go HTTP!"))
}

// handleGetUser 处理 /api/users/1 这种路径
func handleGetUser(w http.ResponseWriter, r *http.Request) {
	setTraceID(w, r)
	if r.Method != http.MethodGet {
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
		return
	}
	// 从 /api/users/1 里取出 "1",再转成数字
	path := strings.TrimPrefix(r.URL.Path, "/api/users/")
	id, err := strconv.Atoi(path)
	if err != nil || id <= 0 {
		w.Header().Set("Content-Type", "application/json") // 须在 WriteHeader 前设置头
		http.Error(w, `{"error":"invalid id"}`, http.StatusBadRequest)
		return
	}
	user := User{ID: id, Name: "用户" + strconv.Itoa(id)}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(user) // 把结构体编码成 JSON 写到 w
}

func main() {
	// 注册路径与处理函数的对应关系:访问 /hello 调 handleHello,访问 /api/users/xxx 调 handleGetUser
	http.HandleFunc("/hello", handleHello)
	http.HandleFunc("/api/users/", handleGetUser)
	// 监听 8080 端口,nil 表示用默认的路由(就是上面 HandleFunc 注册的)
	http.ListenAndServe(":8080", nil)
}

逐段解读

  • setTraceID :从请求头读 X-Trace-Id,没有则用 crypto/rand 生成一个并写回响应头。这样调用方(或网关)可以把同一次请求的 trace_id 串联起来,日志里也方便按 trace_id 查一整条链路;实战里通常会在中间件里统一做(Day 5/7 会见到)。

  • User 与 json tagUser 和 Day 1 的 basics 里结构体概念一致,这里多了 `json:"id"`、`json:"name"`,表示序列化成 JSON 时键名为 idname,方便前端或 curl 直接使用。

  • handleHello :先调用 setTraceID(w, r) 再处理业务,保证响应头里都带 trace_id。再判断 r.URL.Path != "/hello" 则返回 404;否则设置 Content-Type 为纯文本、状态码 200,再 Write 一段字节。顺序不能反 :先 Header().Set,再 WriteHeader,再 Write

  • handleGetUser :同样先 setTraceID(w, r),再只接受 GET;用 strings.TrimPrefix(r.URL.Path, "/api/users/") 得到路径后缀(如 "1"),再用 strconv.Atoi 转成数字。若转换失败或 id≤0,先设 Content-Type 再 http.Error 返回 400 和 JSON 错误体;成功则构造 User,设 JSON 头、200,用 json.NewEncoder(w).Encode(user) 写回 JSON。易踩坑 :若先调用 http.ErrorSet("Content-Type", "application/json"),响应头已发出无法修改,浏览器会按纯文本解析;解法即本段写法:先设头再写 body。

  • mainHandleFunc 把路径和函数绑在一起:/hello → handleHello,/api/users/ 注意带末尾斜杠,这样 /api/users/1 会匹配到 handleGetUser。易踩坑 :误以为注册 /api/users/ 只能匹配该精确路径;解法 :标准库规定末尾有 / 为前缀匹配,无 / 为精确匹配,所以 /api/users/1 会进同一 handler。ListenAndServe(":8080", nil) 表示监听 8080,nil 表示使用默认的 DefaultServeMux(即刚才注册的路由)。


六、运行当天代码

在项目根目录执行:

bash 复制代码
go run ./day2/server

启动后:

  • 浏览器或 curl 访问:http://localhost:8080/hello
  • 访问:http://localhost:8080/api/users/1 看 JSON 响应

示例里会演示:注册路由、读请求、写 JSON 等,可直接对照源码学习。

七、学习建议

  1. 先跑起来 :确保 8080 端口没被占用,跑通后再看 day2/server 下的 main.go
  2. 动手改 :改路径(如 /hello/hi)、改返回的 JSON 字段,再请求一次观察变化。
  3. 理解「一个 Handler 对应一个路径」和「如何从 *http.Request 里拿参数、Body」。

八、小结

Day 2 用标准库就能写出可用的 HTTP 服务,不依赖框架。后面 Day 3 做配置、Day 5 加中间件、Day 7 做完整 API,都会基于同样的 net/http 模型。

相关推荐
圣殿骑士-Khtangc14 小时前
Go错误处理最佳实践进阶从error到panic的完整指南
golang
pcplayer15 小时前
Delphi WebService 使用 HTTP Header 管理 Session
http·webservice·soap·web server·http header
运维开发笔记15 小时前
3.6 Go defer 语句学习笔记
golang
存在morning16 小时前
【Python 开发实践 一】Python vs Go vs Java 三门语言对比
java·python·golang
小新讲网安17 小时前
HTTP请求走私攻击实战:CL.TE与TE.CL绕过前端服务器全解析
服务器·前端·网络·web安全·http·架构·漏洞
圣殿骑士-Khtangc1 天前
Go字符串高效拼接性能对比与底层原理分析
服务器·前端·golang
xzlAwin1 天前
Win10安装Go语言多版本管理器g工具
开发语言·golang
wear工程师1 天前
接口超时别只调大 timeout:Java HTTP 客户端其实有 3 段等待
java·http
蜡台1 天前
本机 局域网 搭建本地 HTTPS 域名完整方案
网络协议·http·https·openssl
IS6832 天前
视频文案提取技术全景:ASR vs OCR,哪种方案更适合你?
ide·macos·xcode