[译] Go中的HTTP请求处理概述

栏目: Go · 发布时间: 5年前

内容简介:使用 Go 处理 HTTP 请求主要涉及两件事:ServeMuxes 和 Handlers。ServeMux本质上是一个 HTTP 请求路由器(或多路复用器)。它将传入的请求与预定义的 URL 路径列表进行比较,并在找到匹配时调用路径的关联 handler。handler 负责写入响应头和响应体。几乎任何对象都可以是 handler,只要它满足http.Handler接口即可。在非专业术语中,这仅仅意味着它必须是一个拥有以下签名的

使用 Go 处理 HTTP 请求主要涉及两件事:ServeMuxes 和 Handlers。

ServeMux本质上是一个 HTTP 请求路由器(或多路复用器)。它将传入的请求与预定义的 URL 路径列表进行比较,并在找到匹配时调用路径的关联 handler。

handler 负责写入响应头和响应体。几乎任何对象都可以是 handler,只要它满足http.Handler接口即可。在非专业术语中,这仅仅意味着它必须是一个拥有以下签名的 ServeHTTP 方法:

ServeHTTP(http.ResponseWriter, *http.Request)

Go 的 HTTP 包附带了一些函数来生成常用的 handler,例如FileServer, NotFoundHandler 和RedirectHandler。让我们从一个简单的例子开始:

$ mkdir handler-example
$ cd handler-example
$ touch main.go
复制代码
File: main.go
package main

import (
  "log"
  "net/http"
)

func main() {
  mux := http.NewServeMux()

  rh := http.RedirectHandler("http://example.org", 307)
  mux.Handle("/foo", rh)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}
复制代码

让我们快速介绍一下:

  • main 函数中,我们使用http.NewServeMux函数创建了一个空的ServeMux。
  • 然后我们使用http.RedirectHandler函数创建一个新的handler。该handler将其接收的所有请求307重定向到http://example.org。
  • 接下来我们使用mux.Handle函数向我们的新ServeMux注册它,因此它充当URL路径 /foo 的所有传入请求的handler。
  • 最后,我们创建一个新服务并使用http.ListenAndServe函数开始监听传入的请求,并传入ServeMux给这个方法以匹配请求。

继续运行应用程序:

$ go run main.go
Listening...
复制代码

并在浏览器中访问 http://localhost:3000/foo 。你会发现请求已经被成功重定向。

你可能已经注意到了一些有趣的东西:ListenAndServe 函数的签名是 ListenAndServe(addr string, handler Handler) ,但我们传递了一个 ServeMux 作为第二个参数。

能这么做是因为 ServeMux 类型也有一个 ServeHTTP 方法,这意味着它也满足 Handler 接口。

对我而言,它只是将 ServeMux 视为 一种特殊的 handler ,而不是把响应本身通过第二个 handler 参数传递给请求。这不像刚刚听说时那么惊讶 - 将 handler 链接在一起在 Go 中相当普遍。

自定义 handler

我们创建一个自定义 handler,它以当前本地时间的指定格式响应:

type timeHandler struct {
  format string
}

func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  tm := time.Now().Format(th.format)
  w.Write([]byte("The time is: " + tm))
}
复制代码

这里确切的代码并不太重要。

真正重要的是我们有一个对象(在这种情况下它是一个 timeHandler 结构,它同样可以是一个字符串或函数或其他任何东西),并且我们已经实现了一个带有签名 ServeHTTP(http.ResponseWriter, *http.Request) 的方法。这就是我们实现一个 handler 所需的全部内容。

让我们将其嵌入一个具体的例子中:

File: main.go
package main

import (
  "log"
  "net/http"
  "time"
)

type timeHandler struct {
  format string
}

func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  tm := time.Now().Format(th.format)
  w.Write([]byte("The time is: " + tm))
}

func main() {
  mux := http.NewServeMux()

  th := &timeHandler{format: time.RFC1123}
  mux.Handle("/time", th)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}
复制代码

main 函数中,我们使用 符号生成指针,用与普通结构完全相同的方式初始化 timeHandler 。然后,与前面的示例一样,我们使用 mux.Handle 函数将其注册到我们的ServeMux。

现在,当我们运行应用程序时,ServeMux会将任何通过 /time 路径的请求直接传递给我们的 timeHandler.ServeHTTP 方法。

试一试: http://localhost:3000/time

另请注意,我们可以轻松地在多个路径中重复使用timeHandler:

func main() {
  mux := http.NewServeMux()

  th1123 := &timeHandler{format: time.RFC1123}
  mux.Handle("/time/rfc1123", th1123)

  th3339 := &timeHandler{format: time.RFC3339}
  mux.Handle("/time/rfc3339", th3339)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}
复制代码

普通函数作为 handler

对于简单的情况(如上例),定义新的自定义类型和 ServeHTTP 方法感觉有点啰嗦。让我们看看另一个方法,我们利用 Go 的http.HandlerFunc类型来使正常的函数满足 Handler 接口。

任何具有签名 func(http.ResponseWriter, *http.Request) 的函数都可以转换为 HandlerFunc 类型。这很有用,因为 HandleFunc 对象带有一个内置的 ServeHTTP 方法 - 这非常巧妙且方便 - 执行原始函数的内容。

如果这听起来令人费解,请尝试查看相关的源代码。你将看到它是一种让函数满足 Handler 接口的非常简洁的方法。

我们使用这种方法来重写 timeHandler 应用程序:

File: main.go
package main

import (
  "log"
  "net/http"
  "time"
)

func timeHandler(w http.ResponseWriter, r *http.Request) {
  tm := time.Now().Format(time.RFC1123)
  w.Write([]byte("The time is: " + tm))
}

func main() {
  mux := http.NewServeMux()

  // Convert the timeHandler function to a HandlerFunc type
  th := http.HandlerFunc(timeHandler)
  // And add it to the ServeMux
  mux.Handle("/time", th)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}
复制代码

事实上,将函数转换为HandlerFunc类型,然后将其添加到ServeMux的情况比较常见,Go提供了一个快捷的转换方法:mux.HandleFunc方法。

如果我们使用这个转换方法, main() 函数将是这个样子:

func main() {
  mux := http.NewServeMux()

  mux.HandleFunc("/time", timeHandler)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}
复制代码

大多数时候使用这样的 handler 很有效。但是当事情变得越来越复杂时,将会受限。

你可能已经注意到,与之前的方法不同,我们必须在 timeHandler 函数中对时间格式进行硬编码。当我们想要将信息或变量从 main() 传递给 handler 时会发生什么?

一个简洁的方法是将我们的 handler 逻辑放入一个闭包中,把我们想用的变量包起来:

File: main.go
package main

import (
  "log"
  "net/http"
  "time"
)

func timeHandler(format string) http.Handler {
  fn := func(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(format)
    w.Write([]byte("The time is: " + tm))
  }
  return http.HandlerFunc(fn)
}

func main() {
  mux := http.NewServeMux()

  th := timeHandler(time.RFC1123)
  mux.Handle("/time", th)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}
复制代码

timeHandler 函数现在有一点点不同。现在使用它来返回handler,而不是将函数强制转换为handler(就像我们之前所做的那样)。能这么做有两个关键点。

首先它创建了一个匿名函数 fn ,它访问形成闭包的 format 变量。无论我们如何处理闭包,它总是能够访问它作用域下所创建的局部变量 - 在这种情况下意味着它总是可以访问 format 变量。

其次我们的闭包有签名为 func(http.ResponseWriter, *http.Request) 的函数。你可能还记得,这意味着我们可以将其转换为HandlerFunc类型(以便它满足Handler接口)。然后我们的 timeHandler 函数返回这个转换后的闭包。

在这个例子中,我们仅仅将一个简单的字符串传递给handler。但在实际应用程序中,您可以使用此方法传递数据库连接,模板映射或任何其他应用程序级的上下文。它是全局变量的一个很好的替代方案,并且可以使测试的自包含handler变得更整洁。

你可能还会看到相同的模式,如下所示:

func timeHandler(format string) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(format)
    w.Write([]byte("The time is: " + tm))
  })
}
复制代码

或者在返回时使用隐式转换为 HandlerFunc 类型:

func timeHandler(format string) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(format)
    w.Write([]byte("The time is: " + tm))
  }
}
复制代码

DefaultServeMux

你可能已经看到过很多地方提到的 DefaultServeMux,包括最简单的 Hello World 示例到 Go 源代码。

我花了很长时间才意识到它并不特别。 DefaultServeMux 只是一个普通的 ServeMux,就像我们已经使用的那样,默认情况下在使用 HTTP 包时会实例化。以下是 Go 源代码中的相关行:

var DefaultServeMux = NewServeMux()
复制代码

通常,你不应使用 DefaultServeMux,因为它会带来 安全风险

由于 DefaultServeMux 存储在全局变量中,因此任何程序包都可以访问它并注册路由 - 包括应用程序导入的任何第三方程序包。如果其中一个第三方软件包遭到破坏,他们可以使用 DefaultServeMux 向 Web 公开恶意 handler。

因此,根据经验,避免使用 DefaultServeMux 是一个好主意,取而代之使用你自己的本地范围的 ServeMux,就像我们到目前为止一样。但如果你决定使用它……

HTTP 包提供了一些使用 DefaultServeMux 的便捷方式:http.Handle和http.HandleFunc。这些与我们已经看过的同名函数完全相同,不同之处在于它们将 handler 添加到 DefaultServeMux 而不是你自己创建的 handler。

此外,如果没有提供其他 handler(即第二个参数设置为 nil ),ListenAndServe 将退回到使用 DefaultServeMux。

因此,作为最后一步,让我们更新我们的 timeHandler 应用程序以使用 DefaultServeMux:

File: main.go
package main

import (
  "log"
  "net/http"
  "time"
)

func timeHandler(format string) http.Handler {
  fn := func(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(format)
    w.Write([]byte("The time is: " + tm))
  }
  return http.HandlerFunc(fn)
}

func main() {
  // Note that we skip creating the ServeMux...

  var format string = time.RFC1123
  th := timeHandler(format)

  // We use http.Handle instead of mux.Handle...
  http.Handle("/time", th)

  log.Println("Listening...")
  // And pass nil as the handler to ListenAndServe.
  http.ListenAndServe(":3000", nil)
}
复制代码

如果你喜欢这篇博文,请不要忘记查看我的新书 《用 Go 构建专​​业的 Web 应用程序》

在推特上关注我@ajmedwards。

此文章中的所有代码都可以在MIT Licence许可下免费使用。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First Rails

Head First Rails

David Griffiths / O'Reilly Media / 2008-12-30 / USD 49.99

Figure its about time that you hop on the Ruby on Rails bandwagon? You've heard that it'll increase your productivity exponentially, and allow you to created full fledged web applications with minimal......一起来看看 《Head First Rails》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

在线进制转换器
在线进制转换器

各进制数互转换器

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具