Go 使用标准库 net/http 包构建服务器

简介: Go 使用标准库 net/http 包构建服务器

01

概念


在 Go 语言中,使用标准库 net/http 可以很方便的构建服务器,只要调用 ListenAndServe 函数,并传入参数IP地址与端口组成的字符串和处理器(handler)即可。



func ListenAndServe(addr string, handler Handler) error


如果 IP 地址与端口组成的字符串参数为空字符串,那么服务器默认使用 80 端口进行网络连接,如果处理器(handler)参数为 nil,那么服务器将使用默认多路复用器 DefaultServeMux。


02

构建服务器


细心的读者可能会说,服务器配置信息除了 IP 地址和端口之外,还有很多其它配置信息,应该怎么配置给服务器呢?


Go 语言为我们提供了一个结构体 Server,其中包含了很多对服务器的其它配置,结构体 Server 的完整代码如下:


type Server struct {
    Addr              string
    Handler           Handler
    TLSConfig         *tls.Config
    ReadTimeout       time.Duration
    ReadHeaderTimeout time.Duration
    WriteTimeout      time.Duration
    IdleTimeout       time.Duration
    MaxHeaderBytes    int
    TLSNextProto      map[string]func(*Server, *tls.Conn, Handler)
    ConnState         func(net.Conn, ConnState)
    ErrorLog          *log.Logger
    BaseContext       func(net.Listener) context.Context
    ConnContext       func(ctx context.Context, c net.Conn) context.Context
    inShutdown        atomicBool
    disableKeepAlives int32
    nextProtoOnce     sync.Once
    nextProtoErr      error
    mu                sync.Mutex
    listeners         map[*net.Listener]struct{}
    activeConn        map[*conn]struct{}
    doneChan          chan struct{}
    onShutdown        []func()
}


使用结构体 Server 构建服务器:


server := http.Server{
    Addr: ":8080",
    Handler: nil,
}
server.ListenAndServe()


03

接收 HTTP 请求


在 Go 语言中,一个处理器就是一个拥有 ServeHTTP 方法的接口,这个 ServeHTTP 方法需要接收两个参数,第一个参数是一个 ResponseWriter 接口,第二个参数是一个指向 Request 结构的指针。

DefaultServeMux 默认多路复用器是多路复用器 ServeMux 结构的一个实例,ServeMux 也拥有 ServeHTTP 方法。

所以 DefaultServeMux 既是 ServeMux 结构的实例,也是处理器 Handler 结构的实例,因此 DefaultServeMux 不仅是一个多路复用器,还是一个处理器。但是 DefaultServeMux 是一个特殊的处理器,它唯一要做的就是根据请求的 URL 将请求重定向到不同的处理器。


自定义一个处理器,替代 DefaultServeMux。


type MyHandler struct {
}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "Hello World!")
}

使用自定义处理器,配置服务器。


type MyHandler struct {
}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "Hello World!")
}

细心的读者可能已经发现,使用自定义的处理器与服务器进行绑定,启动服务器,不管浏览器访问什么地址,服务器返回的都是同样的响应 Hello World!

这是因为使用自定义的处理器替代了默认多路复用器 DefaultServeMux,服务器不会再通过 URL 匹配来将请求路由至不同的处理器。


怎么解决这个问题呢?

使用多个处理器。使用 http 包的 Handle 函数绑定到 DefaultServeMux。


为了使用多个处理器去处理不同的 URL,我们不再在 Serve 结构

的 Handler 字段中指定处理器,而是让服务器使用默认多路复用器 DefaultServeMux,

然后通过 http.Handle 函数将处理器绑定到 DefaultServeMux。http 包的 Handle 函数实际上是 ServeMux 结构的方法,为了操作便利而创建的函数,调用它们等同于调用 DefaultServeMux 的某个方法。

例如,调用 http.Handle,实际上就是在调用 DefaultServeMux 的 Handle 方法。


示例代码:

编写多个处理器,处理请求


type FirstHandler struct {
}
func (f *FirstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "FirstHandler")
}
type SecondHandler struct {
}
func (s *SecondHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "SecondHandler")
}


构建服务器:


first := FirstHandler{}
second := SecondHandler{}
server := http.Server{
    Addr: "127.0.0.1:8080",
}
// http.Handle 函数
http.Handle("/first", &first)
http.Handle("/second", &second)
server.ListenAndServe()

以上我们通过使用 http.Handle 函数,将一个创建的处理器绑定到一个 URL 上,实现使用多个处理器处理不同的 URL。


现在,可能有读者会说,创建多个处理器来处理多个请求,这也太不优雅了,有没有其它方式呢?


先告诉大家答案,有其它方式,使用处理器函数。


http.HandleFunc 函数将自定义函数转换成一个处理器 Handler,并将它与 DefaultServeMux 进行绑定,从而简化创建并绑定 Handler 的工作。


示例代码:

编写多个函数,处理请求


func first(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "func first")
}
func second(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "func second")
}


构建服务器:


server := http.Server{
    Addr: "127.0.0.1:8080",
}
// http.HandleFunc
http.HandleFunc("/first", first)
http.HandleFunc("/second", second)
server.ListenAndServe()


使用处理器函数和使用处理器,都可以实现根据请求的 URL 将请求重定向到不同的处理器,而且处理器函数比处理器的代码更为简洁。

但是也不是完全使用处理器函数代替处理器,因为如果代码已经包含了某个接口或某种类型,我们只需为它们添加 ServeHTTP 方法就可以将它们转变为处理器。


ServeMux 无法使用变量实现 URL 模式匹配,使用三方多路复用器 httprouter 包可以实现 URL 模式匹配。此外,还有一个非常优秀的三方多路复用器,gorilla/mux。篇幅限制,这里就不展开了。



04

处理 HTTP 请求


客户端和服务器端传递的消息,我们称之为 HTTP 报文,有两种类型,分别是 HTTP 请求和 HTTP 响应,并且这两种类型的结构相同。


  1. 请求行/响应行
  2. 零个/多个首部
  3. 一个空行
  4. 一个可选的报文主体


在 Go 语言中,标准库 net/http 提供了一系列用于表示 HTTP 报文的结构体。其中,Request 结构体代表 HTTP 请求报文。


Request 结构体的组成部分:

  • URL 字段
  • Header 字段
  • Body 字段
  • Form 字段、PostForm 字段和 MultipartForm 字段


请求/响应的首部都是使用 Header 类型描述,Header 类型使用一个 map 来表示 HTTP 首部中的多个键值对。Header 类型有 4 个基本方法,这些方法可以根据给定的键执行添加、删除、获取和设置等操作。


获取请求首部的示例代码:


func headers(w http.ResponseWriter, r *http.Request) {
  h := r.Header
  fmt.Fprintln(w, h)
  h2 := r.Header["User-Agent"]
  fmt.Fprintln(w, h2)
  h3 := r.Header.Get("User-Agent")
  fmt.Fprintln(w, h3)
}


构建服务器:


server := http.Server{
    Addr: "127.0.0.1:8080",
}
http.HandleFunc("/headers", headers)
server.ListenAndServe()


代码中,通过 r.Header 获取所有首部,通过 r.Header["key"] 获取指定的首部,直接引用 Header 获取的是一个字符串切片,如果我们需要获取字符串格式的首部值,可以使用 r.Header.Get("key") 方法。


请求/响应的主体都是用 Request 结构体的 Body 字段表示,这个字段是一个io.ReadCloser 接口,该接口即包含了 Reader 接口,也包含了 Closer 接口。其中 Reader 接口有 Read 方法,该方法接收一个字节切片参数,返回一个被读取内容的字节数和一个可选的错误。


获取请求主体中的数据的代码:


func body(w http.ResponseWriter, r *http.Request) {
  len := r.ContentLength
  body := make([]byte, len)
  r.Body.Read(body)
  fmt.Fprintln(w, string(body))
}


构建服务器:


server := http.Server{
    Addr: "127.0.0.1:8080",
}
http.HandleFunc("/body", body)
server.ListenAndServe()


代码中,通过 r.ContentLength 方法获取主体数据的字节长度,然后根据字节长度创建一个字节数组,然后调用 Read 方法将主体数据读取到字节数组中。


可能有的读者朋友们开始抱怨了,这也太麻烦了。别担心,Go 语言标准库net/http 提供了相关函数来满足用户对数据提取方面的需求,通过调用 Request 结构体提供的方法,可以将 URL、主体的数据提取到该结构体的 Form、PostForm 和 MultipartForm 等字段中。


示例代码:

使用 Request 结构体提供的方法提取数据(enctype 属性的值为application/x-www-form-urlencoded


func getVal(w http.ResponseWriter, r *http.Request) {
  // 语法分析
  r.ParseForm()
  fmt.Fprintln(w, r.Form)
  fmt.Fprintln(w, r.PostForm)
  fmt.Fprintln(w, r.FormValue("username")) // 只获取第一个值
  fmt.Fprintln(w, r.PostFormValue("username")) // 只获取 form 表单值
}


使用 Request 结构体提供的方法提取数据(enctype 属性的值为multipart/form-data


func getMultipart(w http.ResponseWriter, r *http.Request) {
  // 语法分析
  r.ParseMultipartForm(1024)
  fmt.Fprintln(w, r.Form)
  fmt.Fprintln(w, r.PostForm) // 只取表单值,不取 URL 值
  fmt.Fprintln(w, r.MultipartForm)
  fmt.Fprintln(w, r.FormValue("username")) // 只取 URL 值
  fmt.Fprintln(w, r.PostFormValue("username")) // 只取 form 表单值
}


构建服务器:


server := http.Server{
    Addr: "127.0.0.1:8080",
}
http.HandleFunc("/getVal", getVal)
http.HandleFunc("/getMultipart", getMultipart)
server.ListenAndServe()


使用 Request 结构的方法获取表单数据:

1. 调用 ParseForm 方法或者 ParseMultipartForm 方法,对请求进行语法分析。

2. 取值:

r.Form,map 类型,键是字符串,值是字符串切片。

如果键同时存在表单和 URL,值包含表单值和 URL 值,并且表单值排在前面。

r.PostForm,如果键同时存在表单和 URL,只取要表单的值。

只支持 application/x-www-form-urlencoded 编码。

r.MultipartForm,支持 multipart/form-data 编码。

只取表单的值,不取 URL 的值。


上面提到的几个方法,可能有些读者朋友感觉比较繁琐,别担心,Request 结构体还提供了另外一些方法,FormValue 和 PostFormValue,它们可以让用户更容易地获取表单中的键值对。


FormValue 方法直接获取指定键的值,不需要在之前调用语法分析的方法。

如果键同时存在表单和 URL,只取表单的值。

PostFormValue 方法只会取表单的值,不取 URL 的值。


05

给客户端发送响应


处理器通过 ResponseWriter 接口创建 HTTP 响应。ResponseWriter 接口有以下 3 个方法:

  • Writer
  • WriterHeader
  • Header


示例代码:

写主体:


func setVal(w http.ResponseWriter, r *http.Request) {
  str := "Hello World!"
  w.WriteHeader(501) // 设置响应返回的状态码,必须在 Write 方法之前调用。
  w.Write([]byte(str)) // 写入响应主体
}


写首部:

func setHeader(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Location", "https://www.baidu.com")
  w.WriteHeader(302)
}


构建服务器:

server := http.Server{
    Addr: "127.0.0.1:8080",
}
http.HandleFunc("/setVal", setVal)
http.HandleFunc("/setHeader", setHeader)
server.ListenAndServe()


需要注意的是,WriteHeader 方法执行完,不能再对首部写入,所以要提前对首部写入。


06

Cookie


关于 Cookie 本身的内容,可以阅读我们之前的一篇文章Gin 学习之 cookie 读写


本篇文章,我们只演示一些如何使用标准库 net/http 操作 cookie,包括写 cookie、读 cookie 和删 cookie。


在 Go 语言中,使用 Cookie 结构体表示 cookie。Cookie 结构体完整字段:


type Cookie struct {
    Name       string
    Value      string
    Path       string
    Domain     string
    Expires    time.Time
    RawExpires string
    MaxAge     int
    Secure     bool
    HttpOnly   bool
    SameSite   SameSite
    Raw        string
    Unparsed   []string
}


通过代码,我们演示如何使用标准库 net/http 操作 cookie。

示例代码:

操作 cookie:


// 写 Cooie
func setCookie(w http.ResponseWriter, r *http.Request) {
  c1 := http.Cookie{
    Name:  "c1",
    Value: "val1",
  }
  c2 := http.Cookie{
    Name:  "c2",
    Value: "val2",
  }
  c3 := http.Cookie{
    Name:  "c3",
    Value: "val3",
  }
  w.Header().Set("Set-Cookie", c1.String())
  w.Header().Add("Set-Cookie", c2.String())
  http.SetCookie(w, &c3) // 指针类型
}
// 读 Cookie
func getCookie(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintln(w, r.Header["Cookie"])
  c1, _ := r.Cookie("c1")
  fmt.Fprintln(w, c1)
  fmt.Fprintln(w, r.Cookies())
}
// 删 Cookie
func delCookie(w http.ResponseWriter, r *http.Request) {
  c2 := http.Cookie{
    Name:    "c2",
    MaxAge:  -1,
    Expires: time.Unix(1, 0),
  }
  http.SetCookie(w, &c2)
}


构建服务器:


server := http.Server{
    Addr: "127.0.0.1:8080",
}
http.HandleFunc("/setCookie", setCookie)
http.HandleFunc("/getCookie", getCookie)
http.HandleFunc("/delCookie", delCookie)
server.ListenAndServe()





目录
相关文章
|
9月前
|
人工智能 JavaScript API
零基础构建MCP服务器:TypeScript/Python双语言实战指南
作为一名深耕技术领域多年的博主摘星,我深刻感受到了MCP(Model Context Protocol)协议在AI生态系统中的革命性意义。MCP作为Anthropic推出的开放标准,正在重新定义AI应用与外部系统的交互方式,它不仅解决了传统API集成的复杂性问题,更为开发者提供了一个统一、安全、高效的连接框架。在过去几个月的实践中,我发现许多开发者对MCP的概念理解透彻,但在实际动手构建MCP服务器时却遇到了各种技术壁垒。从环境配置的细节问题到SDK API的深度理解,从第一个Hello World程序的调试到生产环境的部署优化,每一个环节都可能成为初学者的绊脚石。因此,我决定撰写这篇全面的实
2038 67
零基础构建MCP服务器:TypeScript/Python双语言实战指南
|
7月前
|
Cloud Native 算法 区块链
站在巨人的肩膀上:gRPC通过HTTP/2构建云原生时代的通信标准
gRPC是云原生时代高效通信标准,基于HTTP/2实现,支持四种服务方法。通过.proto文件定义接口,生成多语言Stub,实现跨语言调用。其请求响应结构清晰,结合Headers、Data帧与Trailers,保障高性能与可扩展性,广泛应用于微服务架构中。
325 0
|
8月前
|
人工智能 自然语言处理 安全
Python构建MCP服务器:从工具封装到AI集成的全流程实践
MCP协议为AI提供标准化工具调用接口,助力模型高效操作现实世界。
1393 1
|
9月前
|
监控 安全 Go
使用Go语言构建网络IP层安全防护
在Go语言中构建网络IP层安全防护是一项需求明确的任务,考虑到高性能、并发和跨平台的优势,Go是构建此类安全系统的合适选择。通过紧密遵循上述步骤并结合最佳实践,可以构建一个强大的网络防护系统,以保障数字环境的安全完整。
192 12
|
10月前
|
NoSQL Go API
MCP 官方开源 Registry 注册服务:基于 Go 和 MongoDB 构建
作为 `registry` 项目的贡献者,我很高兴能参与这个社区驱动的开源项目,也期待它不断发展壮大。本文将对 `registry` 服务进行介绍,为项目的推广尽一份绵薄之力。
319 3
MCP 官方开源 Registry 注册服务:基于 Go 和 MongoDB 构建
|
9月前
|
存储 中间件 网络安全
在Go中构建应用级IP防火墙机制
使用Go构建应用级别的IP防火墙机制不仅能够为你的应用程序增加一层额外的安全性,还能够通过自定义中间件的方式让你有更多控制力,来决定哪些客户端可以或不可以访问你的服务。
261 8
|
9月前
|
Java Shell Maven
【Azure Container App】构建Java应用镜像时候遇无法编译错误:ERROR [build 10/10] RUN ./mvnw.cmd dependency:go-offline -B -Dproduction package
在部署Java应用到Azure Container App时,构建镜像过程中出现错误:“./mvnw.cmd: No such file or directory”。尽管项目根目录包含mvnw和mvnw.cmd文件,但依然报错。问题出现在Dockerfile构建阶段执行`./mvnw dependency:go-offline`命令时,系统提示找不到可执行文件。经过排查,确认是mvnw文件内容异常所致。最终通过重新生成mvnw文件解决该问题,镜像成功构建。
468 1
|
9月前
|
人工智能 负载均衡 监控
使用 Go 和 Gin 实现高可用负载均衡代理服务器
本文基于Go语言和Gin框架,实现了一个企业级负载均衡代理服务器,支持动态路由、健康检查、会话保持等功能。具备高可用性与高性能,单节点支持100k+ QPS,延迟达亚毫秒级,并提供完整的压力测试方案与优化建议。
279 7
|
8月前
|
人工智能 JavaScript 前端开发
用 Go 语言轻松构建 MCP 服务器
本文介绍了使用 Go 语言构建 MCP 服务器的完整过程,涵盖创建服务器实例、注册工具、资源和提示词,以及通过 stdio 和 sse 模式启动服务的方法,帮助开发者快速集成 LLM 应用与外部系统。
|
10月前
|
JSON 编解码 API
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。