您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > Go语言

Go 如何处理 HTTP 请求?掌握这两点即可

时间:2019-12-04 11:33:59  来源:  作者:

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

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

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

ServeHTTP(http.ResponseWriter, *http.Request)

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

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

继续运行应用程序:

$ go run main.go
Listening...

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

你可能已经注意到了一些有趣的东西: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[11]。

另请注意,我们可以轻松地在多个路径中重复使用 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[12] 类型来使正常的函数满足 Handler 接口。

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

如果这听起来令人费解,请尝试查看相关的源代码[13]。你将看到它是一种让函数满足 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[14] 方法。

如果我们使用这个转换方法,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[15] 和http.HandleFunc[16]。这些与我们已经看过的同名函数完全相同,不同之处在于它们将 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 应用程序》[17] !

在推特上关注我 @ajmedwards[18]。

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


via: https://www.alexedwards.net/blog/a-recap-of-request-handling

作者:Alex Edwards[20]译者:咔叽咔叽[21]校对:polaris1119[22]

本文由 GCTT[23] 原创编译,Go 中文网[24] 荣誉推出

文中链接

[1]

ServeMux: https://docs.studygolang.com/pkg/net/http/#ServeMux

[2]

http.Handler: https://docs.studygolang.com/pkg/net/http/#Handler

[3]

FileServer: https://docs.studygolang.com/pkg/net/http/#FileServer

[4]

NotFoundHandler: https://docs.studygolang.com/pkg/net/http/#NotFoundHandler

[5]

RedirectHandler: https://docs.studygolang.com/pkg/net/http/#RedirectHandler

[6]

http.NewServeMux: https://docs.studygolang.com/pkg/net/http/#NewServeMux

[7]

http.RedirectHandler: https://docs.studygolang.com/pkg/net/http/#RedirectHandler

[8]

mux.Handle: https://docs.studygolang.com/pkg/net/http/#ServeMux.Handle

[9]

http.ListenAndServe: https://docs.studygolang.com/pkg/net/http/#ListenAndServe

[10]

http://localhost:3000/foo: http://localhost:3000/foo

[11]

http://localhost:3000/time: http://localhost:3000/time

[12]

http.HandlerFunc: https://docs.studygolang.com/pkg/net/http/#HandlerFunc

[13]

相关的源代码: https://golang.org/src/net/http/server.go?s=57023:57070#L1904

[14]

mux.HandleFunc: https://docs.studygolang.com/pkg/net/http/#ServeMux.HandleFunc

[15]

http.Handle: https://docs.studygolang.com/pkg/net/http/#Handle

[16]

http.HandleFunc: https://docs.studygolang.com/pkg/net/http/#HandleFunc

[17]

《用 Go 构建专业的 Web 应用程序》: https://lets-go.alexedwards.net/

[18]

@ajmedwards: https://twitter.com/ajmedwards

[19]

MIT Licence: http://opensource.org/licenses/MIT

[20]

Alex Edwards: https://www.alexedwards.net/

[21]

咔叽咔叽: https://github.com/watermelo

[22]

polaris1119: https://github.com/polaris1119

[23]

GCTT: https://github.com/studygolang/GCTT

[24]

Go 中文网: https://studygolang.com/

 

推荐阅读

  • sync.Pool 一定会提升性能吗?建议你先学习一下它的设计
  • Go 号称几行代码开启一个 HTTP Server,底层都做了什么?


Tags:Go   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
一. 配置yum源在目录 /etc/yum.repos.d/ 下新建文件 google-chrome.repovim /etc/yum.repos.d/google-chrome.repo按i进入编辑模式写入如下内容:[google-chrome]name=googl...【详细内容】
2021-12-23  Tags: Go  点击:(7)  评论:(0)  加入收藏
昨日谷歌宣布,自2022年12月19日开始停止对OnHub的软件支持,OnHub路由器仍将提供Wi-Fi信号,但用户无法用谷歌Home应用程序管理它。无法更新Wi-Fi网络设置、添加额外的Wifi设备或...【详细内容】
2021-12-22  Tags: Go  点击:(5)  评论:(0)  加入收藏
zip 是一种常见的归档格式,本文讲解 Go 如何操作 zip。首先看看 zip 文件是如何工作的。以一个小文件为例:(类 Unix 系统下)$ cat hello.textHello!执行 zip 命令进行归档:$ zip...【详细内容】
2021-12-17  Tags: Go  点击:(13)  评论:(0)  加入收藏
流水线(Pipeline)是把一个重复的过程分解为若干个子过程,使每个子过程与其他子过程并行进行的技术。本文主要介绍了诞生于云原生时代的流水线框架 Argo。 什么是流水线?在计算机...【详细内容】
2021-11-30  Tags: Go  点击:(21)  评论:(0)  加入收藏
大家好,我是 polarisxu。前段时间,Russ Cox 明确了泛型相关的事情,原计划在标准库中加入泛型相关的包,改放到 golang.org/x/exp 下。目前,Go 泛型的主要设计者 ianlancetaylor 完...【详细内容】
2021-11-30  Tags: Go  点击:(24)  评论:(0)  加入收藏
前言最近因为项目需要写了一段时间的 Go ,相对于 Java 来说语法简单同时又有着一些 Python 之类的语法糖,让人大呼”真香“。 但现阶段相对来说还是 Python 写的多一些,偶尔还...【详细内容】
2021-11-25  Tags: Go  点击:(29)  评论:(0)  加入收藏
前几节课我们学习了Django加载网页数据的相关知识,今天我们讲一下怎么加载静态文件,我们以加载图片为例,学习怎么配置静态文件。 1.思路讲解 首先我们需要新建文件(test2)作为我...【详细内容】
2021-11-23  Tags: Go  点击:(43)  评论:(0)  加入收藏
在本教程中,我们将介绍如何使用 Django 发送电子邮件。我们将介绍如何配置 Django SMTP 连接,如何为您的电子邮件提供商设置应用程序密码,以及如何通过 Django shell 发送电子...【详细内容】
2021-11-10  Tags: Go  点击:(22)  评论:(0)  加入收藏
golang context 很好用,就使用php实现了github地址 : https://github.com/qq1060656096/php-go-context context使用闭坑指南1. 将一个Context参数作为第一个参数传递给传入和...【详细内容】
2021-11-05  Tags: Go  点击:(41)  评论:(0)  加入收藏
谷歌宣布调整服务费费率,从明年起Google Play上所有付费订阅的抽成将从30%降低到15%。此外,电子书和点播音乐流媒体服务还将有资格享受低至10%的费率。此前,Google Play上的开...【详细内容】
2021-10-28  Tags: Go  点击:(38)  评论:(0)  加入收藏
▌简易百科推荐
zip 是一种常见的归档格式,本文讲解 Go 如何操作 zip。首先看看 zip 文件是如何工作的。以一个小文件为例:(类 Unix 系统下)$ cat hello.textHello!执行 zip 命令进行归档:$ zip...【详细内容】
2021-12-17  Go语言中文网    Tags:Go语言   点击:(13)  评论:(0)  加入收藏
大家好,我是 polarisxu。前段时间,Russ Cox 明确了泛型相关的事情,原计划在标准库中加入泛型相关的包,改放到 golang.org/x/exp 下。目前,Go 泛型的主要设计者 ianlancetaylor 完...【详细内容】
2021-11-30  Go语言中文网    Tags:slices 包   点击:(24)  评论:(0)  加入收藏
前言最近因为项目需要写了一段时间的 Go ,相对于 Java 来说语法简单同时又有着一些 Python 之类的语法糖,让人大呼”真香“。 但现阶段相对来说还是 Python 写的多一些,偶尔还...【详细内容】
2021-11-25  crossoverJie    Tags:Go   点击:(29)  评论:(0)  加入收藏
go-micro是基于 Go 语言用于开发的微服务的 RPC 框架,主要功能如下:服务发现,负载均衡 ,消息编码,请求/响应,Async Messaging,可插拔接口,最后这个功能牛p安装步骤安装proto...【详细内容】
2021-09-06    石老师小跟班  Tags:go-micro   点击:(197)  评论:(0)  加入收藏
GoLand 2021.2 EAP 5 现已发布。用户可以从工具箱应用程序中获得 EAP 构建,也可以从官方网站手动下载。并且从此 EAP 开始,只有拥有有效的 JetBrains 帐户才能加入该计划。手...【详细内容】
2021-06-29  IT实战联盟  今日头条  Tags:GoLand   点击:(185)  评论:(0)  加入收藏
作者:HDT3213今天给大家带来的开源项目是 Godis:一个用 Go 语言实现的 Redis 服务器。支持: 5 种数据结构(string、list、hash、set、sortedset) 自动过期(TTL) 发布订阅、地理位...【详细内容】
2021-06-18  HelloGitHub  今日头条  Tags:Go   点击:(125)  评论:(0)  加入收藏
统一规范篇合理规划目录本篇主要描述了公司内部同事都必须遵守的一些开发规矩,如统一开发空间,既使用统一的开发工具来保证代码最后的格式的统一,开发中对文件和代码长度的控制...【详细内容】
2021-05-18  1024课堂    Tags:Go语言   点击:(232)  评论:(0)  加入收藏
闭包概述 闭包不是Go语言独有的概念,在很多编程语言中都有闭包 闭包就是解决局部变量不能被外部访问的一种解决方案 是把函数当作返回值的一种应用 代码演示总体思想:在函数...【详细内容】
2021-05-14  HelloGo  今日头条  Tags:Go语言   点击:(223)  评论:(0)  加入收藏
一时想不开,想了解一下Go语言,于是安装了并体验了一下。下载1. 进入golang.google.cn 点击Download Go 2.选择对应的操作系统,点击后开始下载。 安装1. windows下执行傻瓜式安...【详细内容】
2021-05-12  程序员fearlazy  fearlazy  Tags:Go语言   点击:(237)  评论:(0)  加入收藏
1.简介channel是Go语言的一大特性,基于channel有很多值得探讨的问题,如 channel为什么是并发安全的? 同步通道和异步通道有啥区别? 通道为何会阻塞协程? 使用通道导致阻塞的协程...【详细内容】
2021-05-10  程序员麻辣烫  今日头条  Tags:Go通道   点击:(274)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条