第七篇:《初识 net/http:构建第一个 RESTful API》

发布时间:2026/7/23 11:09:32
第七篇:《初识 net/http:构建第一个 RESTful API》 掌握了 Go 的基础语法和并发模型后我们正式进入 Web 开发领域。Go 的标准库 net/http 已经足够强大无需第三方框架就能构建一个完整的 RESTful API 服务。本文从 http.Handler 接口和 http.ServeMux 路由器讲起深入讲解如何编写 Handler 处理 HTTP 请求、如何读取请求参数和请求体、如何返回 JSON 响应以及如何用闭包实现中间件。最后通过一个完整的“图书管理”CRUD API 实战帮你建立 Go Web 开发的基础认知。一、net/http 核心接口http.Handlernet/http 包的核心是 http.Handler 接口typeHandlerinterface{ServeHTTP(w http.ResponseWriter,r*http.Request)}任何实现了 ServeHTTP 方法的类型都可以作为 HTTP 处理器。1.1 函数式 Handlerhttp.HandlerFunchttp.HandlerFunc 是一个函数类型它实现了 http.Handler 接口允许将普通函数直接转换为 Handlerpackagemainimport(fmtnet/http)// 普通函数作为 HandlerfunchelloHandler(w http.ResponseWriter,r*http.Request){fmt.Fprintf(w,Hello, World!)}funcmain(){// http.HandlerFunc 将函数转换为 Handlerhttp.HandleFunc(/hello,helloHandler)// 启动服务器http.ListenAndServe(:8080,nil)}访问 http://localhost:8080/hello即可看到 Hello, World!。1.2 结构体 Handler面向对象风格typeGreetingHandlerstruct{Namestring}func(h*GreetingHandler)ServeHTTP(w http.ResponseWriter,r*http.Request){fmt.Fprintf(w,Greeting from %s,h.Name)}funcmain(){handler:GreetingHandler{Name:MyApp}http.Handle(/greeting,handler)http.ListenAndServe(:8080,nil)}二、处理 HTTP 请求2.1 读取 URL 参数Query String使用 r.URL.Query() 读取查询参数funcgetUserHandler(w http.ResponseWriter,r*http.Request){// 获取查询参数query:r.URL.Query()name:query.Get(name)// /user?nameAliceage:query.Get(age)// /user?nameAliceage30fmt.Fprintf(w,name: %s, age: %s,name,age)}2.2 读取路径参数Path ParametersGo 标准库不直接支持路由参数如 /user/123需要自行解析路径或使用 http.ServeMux 的路径匹配模式。更常用的是直接使用 r.URL.Path 解析或者后续章节引入 Gin 框架来处理更复杂的路由。funcuserHandler(w http.ResponseWriter,r*http.Request){// 解析路径/users/123path:r.URL.Path// 简单解析parts:strings.Split(path,/)iflen(parts)3{id:parts[2]// 123fmt.Fprintf(w,User ID: %s,id)}}2.3 读取请求体POST / PUT对于 POST/PUT 请求需要从请求体中读取数据import(encoding/jsonionet/http)typeUserstruct{Namestringjson:nameAgeintjson:age}funccreateUserHandler(w http.ResponseWriter,r*http.Request){// 只允许 POST 方法ifr.Method!http.MethodPost{http.Error(w,Method not allowed,http.StatusMethodNotAllowed)return}// 读取请求体body,err:io.ReadAll(r.Body)iferr!nil{http.Error(w,读取请求体失败,http.StatusBadRequest)return}deferr.Body.Close()// 解析 JSONvaruser Useriferr:json.Unmarshal(body,user);err!nil{http.Error(w,JSON 解析失败,http.StatusBadRequest)return}fmt.Fprintf(w,接收到用户: %s, 年龄: %d,user.Name,user.Age)}三、返回 HTTP 响应3.1 返回 JSON 响应使用 json.NewEncoder 可以方便地返回 JSON 格式的响应typeResponsestruct{Codeintjson:codeMessagestringjson:messageDatainterface{}json:data,omitempty}funcapiHandler(w http.ResponseWriter,r*http.Request){// 设置响应头w.Header().Set(Content-Type,application/json)// 构造响应数据resp:Response{Code:200,Message:success,Data:map[string]string{status:ok},}// 返回 JSONw.WriteHeader(http.StatusOK)json.NewEncoder(w).Encode(resp)}3.2 设置状态码和响应头funchandler(w http.ResponseWriter,r*http.Request){// 设置响应头w.Header().Set(X-Custom-Header,my-value)w.Header().Set(Content-Type,application/json)// 设置状态码w.WriteHeader(http.StatusCreated)// 201// 写入响应体w.Write([]byte({status:ok}))}四、中间件Middleware中间件是一个高阶函数它接收一个 http.Handler 并返回一个新的 http.Handler可以在请求处理前后执行逻辑。net/http 标准库中没有直接定义中间件模式但通过闭包可以非常方便地实现。// 日志中间件funcloggerMiddleware(next http.Handler)http.Handler{returnhttp.HandlerFunc(func(w http.ResponseWriter,r*http.Request){fmt.Printf([%s] %s %s\n,time.Now().Format(2006-01-02 15:04:05),r.Method,r.URL.Path)next.ServeHTTP(w,r)// 调用下一个处理器})}// Recovery 中间件捕获 panicfuncrecoveryMiddleware(next http.Handler)http.Handler{returnhttp.HandlerFunc(func(w http.ResponseWriter,r*http.Request){deferfunc(){iferr:recover();err!nil{log.Printf(panic 恢复: %v,err)http.Error(w,服务器内部错误,http.StatusInternalServerError)}}()next.ServeHTTP(w,r)})}// 应用中间件funcmain(){mux:http.NewServeMux()mux.HandleFunc(/,helloHandler)// 中间件链handler:recoveryMiddleware(loggerMiddleware(mux))http.ListenAndServe(:8080,handler)}五、实战图书管理 CRUD API纯标准库下面是一个完整的图书管理 API包含创建、查询、更新、删除操作全部基于标准库实现packagemainimport(encoding/jsonfmtnet/httpsync)// 图书模型typeBookstruct{IDstringjson:idTitlestringjson:titleAuthorstringjson:authorPriceintjson:price}// 图书仓库typeBookStorestruct{mu sync.RWMutex booksmap[string]Book}funcNewBookStore()*BookStore{returnBookStore{books:make(map[string]Book),}}func(s*BookStore)Create(book Book){s.mu.Lock()defers.mu.Unlock()s.books[book.ID]book}func(s*BookStore)Get(idstring)(Book,bool){s.mu.RLock()defers.mu.RUnlock()book,ok:s.books[id]returnbook,ok}func(s*BookStore)GetAll()[]Book{s.mu.RLock()defers.mu.RUnlock()books:make([]Book,0,len(s.books))for_,book:ranges.books{booksappend(books,book)}returnbooks}func(s*BookStore)Update(idstring,book Book)bool{s.mu.Lock()defers.mu.Unlock()if_,ok:s.books[id];!ok{returnfalse}book.IDid s.books[id]bookreturntrue}func(s*BookStore)Delete(idstring)bool{s.mu.Lock()defers.mu.Unlock()if_,ok:s.books[id];!ok{returnfalse}delete(s.books,id)returntrue}// ---- HTTP Handlers ----varstoreNewBookStore()funcrespondJSON(w http.ResponseWriter,statusint,datainterface{}){w.Header().Set(Content-Type,application/json)w.WriteHeader(status)json.NewEncoder(w).Encode(data)}funcgetAllBooks(w http.ResponseWriter,r*http.Request){books:store.GetAll()respondJSON(w,http.StatusOK,books)}funcgetBookByID(w http.ResponseWriter,r*http.Request){// 简单路径解析/books/123id:r.URL.Path[len(/books/):]ifid{http.Error(w,id 不能为空,http.StatusBadRequest)return}book,ok:store.Get(id)if!ok{http.Error(w,图书不存在,http.StatusNotFound)return}respondJSON(w,http.StatusOK,book)}funccreateBook(w http.ResponseWriter,r*http.Request){ifr.Method!http.MethodPost{http.Error(w,Method not allowed,http.StatusMethodNotAllowed)return}varbook Bookiferr:json.NewDecoder(r.Body).Decode(book);err!nil{http.Error(w,JSON 解析失败,http.StatusBadRequest)return}deferr.Body.Close()ifbook.ID{http.Error(w,id 不能为空,http.StatusBadRequest)return}store.Create(book)respondJSON(w,http.StatusCreated,book)}funcupdateBook(w http.ResponseWriter,r*http.Request){ifr.Method!http.MethodPut{http.Error(w,Method not allowed,http.StatusMethodNotAllowed)return}id:r.URL.Path[len(/books/):]ifid{http.Error(w,id 不能为空,http.StatusBadRequest)return}varbook Bookiferr:json.NewDecoder(r.Body).Decode(book);err!nil{http.Error(w,JSON 解析失败,http.StatusBadRequest)return}deferr.Body.Close()if!store.Update(id,book){http.Error(w,图书不存在,http.StatusNotFound)return}respondJSON(w,http.StatusOK,book)}funcdeleteBook(w http.ResponseWriter,r*http.Request){ifr.Method!http.MethodDelete{http.Error(w,Method not allowed,http.StatusMethodNotAllowed)return}id:r.URL.Path[len(/books/):]ifid{http.Error(w,id 不能为空,http.StatusBadRequest)return}if!store.Delete(id){http.Error(w,图书不存在,http.StatusNotFound)return}w.WriteHeader(http.StatusNoContent)}funcmain(){// 初始化一些示例数据store.Create(Book{ID:1,Title:Go 入门,Author:张三,Price:59})store.Create(Book{ID:2,Title:微服务架构,Author:李四,Price:89})// 路由http.HandleFunc(/books,func(w http.ResponseWriter,r*http.Request){switchr.Method{casehttp.MethodGet:getAllBooks(w,r)casehttp.MethodPost:createBook(w,r)default:http.Error(w,Method not allowed,http.StatusMethodNotAllowed)}})http.HandleFunc(/books/,func(w http.ResponseWriter,r*http.Request){switchr.Method{casehttp.MethodGet:getBookByID(w,r)casehttp.MethodPut:updateBook(w,r)casehttp.MethodDelete:deleteBook(w,r)default:http.Error(w,Method not allowed,http.StatusMethodNotAllowed)}})fmt.Println(服务器启动在 :8080)http.ListenAndServe(:8080,nil)}测试 API# 获取所有图书curlhttp://localhost:8080/books# 创建图书curl-XPOST http://localhost:8080/books-HContent-Type: application/json-d{id:3,title:Go 并发, author:王五, price:69}# 获取单本图书curlhttp://localhost:8080/books/1# 更新图书curl-XPUT http://localhost:8080/books/1-HContent-Type: application/json-d{title:Go 入门第二版, author:张三, price:79}# 删除图书curl-XDELETE http://localhost:8080/books/1六、小结http.Handler核心接口ServeHTTP(w http.ResponseWriter, r *http.Request) 是 Go Web 的起点。路由http.ServeMux 是标准库的路由器支持路径匹配。请求处理r.URL.Query() 读取查询参数io.ReadAll(r.Body) 读取请求体json.Unmarshal 解析 JSON。响应返回w.Header().Set() 设置响应头w.WriteHeader() 设置状态码json.NewEncoder(w).Encode() 返回 JSON。中间件使用闭包实现接收 http.Handler 返回 http.Handler可以实现日志、鉴权、恢复等功能。