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()





目录
相关文章
|
3月前
|
开发框架 JavaScript 前端开发
震撼!破解 ASP.NET 服务器控件 Button 执行顺序之谜,颠覆你的开发认知!
【8月更文挑战第16天】在ASP.NET开发中,通过Button控件实现先执行JavaScript再触后台处理的需求十分常见。例如,在用户点击按钮前需前端验证或提示,确保操作无误后再传递数据至后台深度处理。此过程可通过设置Button的`OnClientClick`属性调用自定义JavaScript函数完成验证;若验证通过,则继续触发后台事件。此外,结合jQuery也能达到相同效果,利用`__doPostBack`手动触发服务器端事件。这种方式增强了应用的交互性和用户体验。
45 8
|
26天前
|
开发框架 .NET 测试技术
了解 .NET 9 中的新 Microsoft.AspNetCore.OpenApi 包,并将其与 NSwag 和 Swashbuckle.AspNetCore 进行比较。
本文介绍了 `.NET 9` 中新推出的 `Microsoft.AspNetCore.OpenApi` 包,该包旨在为 `ASP.NET Core` 应用程序生成 `OpenAPI` 文档。文章对比了 `NSwag` 和 `Swashbuckle.AspNetCore` 两大现有库,探讨了新包的优势和不足,特别是在性能和功能方面。尽管新包在某些方面尚不及成熟库完善,但其对原生 `AoT` 编译的支持和未来的扩展潜力使其成为一个值得考虑的选择。文章还提供了详细的性能测试数据和优化建议,适合对 `OpenAPI` 文档生成感兴趣的开发者阅读。
60 3
了解 .NET 9 中的新 Microsoft.AspNetCore.OpenApi 包,并将其与 NSwag 和 Swashbuckle.AspNetCore 进行比较。
|
29天前
|
监控 网络安全 调度
Quartz.Net整合NetCore3.1,部署到IIS服务器上后台定时Job不被调度的解决方案
解决Quartz.NET在.NET Core 3.1应用中部署到IIS服务器上不被调度的问题,通常需要综合考虑应用配置、IIS设置、日志分析等多个方面。采用上述策略,结合细致的测试和监控,可以有效地提高定时任务的稳定性和可靠性。在实施任何更改后,务必进行充分的测试,以验证问题是否得到解决,并监控生产环境的表现,确保长期稳定性。
46 1
|
1月前
|
网络协议 Unix Linux
一个.NET开源、快速、低延迟的异步套接字服务器和客户端库
一个.NET开源、快速、低延迟的异步套接字服务器和客户端库
|
1月前
|
网络协议 JavaScript 前端开发
【HTTP】HTTP报文格式和抓包
【HTTP】HTTP报文格式和抓包
40 0
http数据包抓包解析
http数据包抓包解析
http数据包抓包解析课程笔记
http数据包抓包解析课程笔记
|
2月前
|
开发框架 JavaScript 前端开发
|
3月前
|
机器学习/深度学习 Ubuntu Linux
在Linux中,如何按照该要求抓包:只过滤出访问http服务的,目标ip为192.168.0.111,一共抓1000个包,并且保存到1.cap文件中?
在Linux中,如何按照该要求抓包:只过滤出访问http服务的,目标ip为192.168.0.111,一共抓1000个包,并且保存到1.cap文件中?
|
3月前
|
缓存 监控 中间件
构建高效的Go语言Web服务器:基于Fiber框架的性能优化实践
在追求极致性能的Web开发领域,Go语言(Golang)凭借其高效的并发处理能力、垃圾回收机制及简洁的语法赢得了广泛的青睐。本文不同于传统的性能优化教程,将深入剖析如何在Go语言环境下,利用Fiber这一高性能Web框架,通过精细化配置、并发策略调整及代码层面的微优化,构建出既快速又稳定的Web服务器。通过实际案例与性能测试数据对比,揭示一系列非直觉但极为有效的优化技巧,助力开发者在快节奏的互联网环境中抢占先机。

热门文章

最新文章