新闻资讯  快讯  焦点  财经  政策  社会
互 联 网   电商  金融  数据  计算  技巧
生活百科  科技  职场  健康  法律  汽车
手机百科  知识  软件  修理  测评  微信
软件技术  应用  系统  图像  视频  经验
硬件技术  知识  技术  测评  选购  维修
网络技术  硬件  软件  设置  安全  技术
程序开发  语言  移动  数据  开源  百科
安全防护  资讯  黑客  木马  病毒  移动
站长技术  搜索  SEO  推广  媒体  移动
财经百科  股票  知识  理财  财务  金融
教育考试  育儿  小学  高考  考研  留学
您当前的位置:首页 > IT百科 > 程序开发 > 语言 > 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   点击:()  评论:()
声明:本站部分内容来自互联网,内容观点仅代表作者本人,如有任何版权侵犯请与我们联系,我们将立即删除。
▌相关评论
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
▌相关推荐
使用 Go 处理 HTTP 请求主要涉及两件事:ServeMuxes 和 Handlers。ServeMux[1] 本质上是一个 HTTP 请求路由器(或多路复用器)。它将传入的请求与预定义的 URL 路径列表进行比较,...【详细内容】
2019-12-04   Go  点击:(0)  评论:(0)  加入收藏
SEO分析师的头衔,就是寻找大量免费数据来源,并将其整理成有见地的东西。 为什么? 因为将客户的建议基于猜想没有任何价值。 最好将高质量的数据与良好的分析相结合,以帮助我们的...【详细内容】
2019-12-04   Go  点击:(0)  评论:(0)  加入收藏
MongoDB和MySQL分别是领先的开源NoSQL和关系数据库。哪个最适合您的应用程序? 在1990年代的互联网泡沫时期,用于Web应用程序的一种通用软件堆栈是LAMP,它最初代表Linux(OS),Apache...【详细内容】
2019-12-04   Go  点击:(0)  评论:(0)  加入收藏
一、前言1、使用google开发的google-perftools优化nginx的内存分配效率和速度,帮助在高并发的情况下控制内存的使用。2、TCMalloc在内存的分配上效率和速度要比malloc高得多...【详细内容】
2019-12-03   Go  点击:(0)  评论:(0)  加入收藏
简述Golang中的锁机制主要包含互斥锁和读写锁互斥锁互斥锁是传统并发程序对共享资源进行控制访问的主要手段。在Go中主要使用 sync.Mutex的结构体表示。一个简单的示例:func...【详细内容】
2019-12-03   Go  点击:(0)  评论:(0)  加入收藏
做google网站优化这些年不难发现该行业难度逐年变高,很多人由于跟不上技术更新而纷纷转行。网上就有人讲未来发展更倾向于网站竞价,seo更难做。真是这样吗?我只认同前半句,后面...【详细内容】
2019-11-26   Go  点击:(6)  评论:(0)  加入收藏
我和几个小伙伴一起翻译了Google前一段时间放出来的Google’s Engineering Practices documentation(https://github.com/google/eng-practices),翻译后的GitHub仓库:https://github.com/xindoo/eng-practices-cn,欢迎加st...【详细内容】
2019-11-26   Go  点击:(5)  评论:(0)  加入收藏
折腾WordPress的朋友都知道修改固定链接后,在分类链接中会出现一个/category/目录,为了去掉分类链接中category目录,网上出现了各种去除category目录的类似插件,比如WP No categ...【详细内容】
2019-11-25   Go  点击:(8)  评论:(0)  加入收藏
安全专家发现Gmail动态邮件功能出现跨站攻击(cross-site scripting,XSS)漏洞,能让攻击者透过此类电子邮件发动攻击,所幸Google已在上个月修补Gmail这项漏洞。这个漏洞是由安全公...【详细内容】
2019-11-20   Go  点击:(9)  评论:(0)  加入收藏
很多语言使用async和await作为语法,将函数变成async,然后在代码的某个位置,它会进行等待,直到任务处理完成返回。在flutter中也是这样的基本功能,但是需要特别指出的是这种语法糖只是feature和stream的封装,可以让你更清晰...【详细内容】
2019-11-20   Go  点击:(5)  评论:(0)  加入收藏
反射是 Go 语言比较重要的一个特性之一,虽然在大多数的应用和服务中并不常见,但是很多框架都依赖 Go 语言的反射机制实现一些动态的功能。作为一门静态语言,Golang 在设计上都...【详细内容】
2019-11-20   Go  点击:(14)  评论:(0)  加入收藏
【环球旅讯】Google在搜索结果页面的顶部置入了更多广告,这给该平台的主要客户——在线旅游企业带来了困扰。Expedia和TripAdvisor近期公布了第三季度财报,并将业务...【详细内容】
2019-11-13   Go  点击:(13)  评论:(0)  加入收藏
日常中经常需要使用 golang 读写 csv 文件,比如将数据库中的数据按照要求导出到 csv 中,读取 csv 文件中的数据到内存中进行处理等操作。ps: 如果是 Excel 文件(*.xlsm、.xls...【详细内容】
2019-11-13   Go  点击:(14)  评论:(0)  加入收藏
1创建web服务器:使用cmd命令进入到存放项目的目录中执行以下命令:python django-admin.py startproject mysite成功后会在目录中出现 mysite 目录,此处创建的为开发服务器2创...【详细内容】
2019-11-12   Go  点击:(9)  评论:(0)  加入收藏
整理 | Jane出品 | AI科技大本营(ID:rgznai100)一个好工具,能提高开发效率,优化项目研发过程,无论是企业还是开发者个人都在寻求适合自己的开发工具。但是,选择正确的工具并不容易...【详细内容】
2019-11-11   Go  点击:(18)  评论:(0)  加入收藏
一个成功的付费媒体投放活动需要用“少即是多”的方式,而屏蔽非目标受众才能过滤进来真正的受众,屏蔽非目标受众也能使那些完全不想要的流量不出现在您的广告中,目标投放加限制...【详细内容】
2019-11-11   Go  点击:(13)  评论:(0)  加入收藏
这篇文章是为不熟悉 Go 的指针或指针类型的程序员而准备的。什么是指针?简单点说,指针是指向另一个地址的值。这是教科书上的解释,但如果你转自一门不用谈论变量地址的开发语言...【详细内容】
2019-11-06   Go  点击:(6)  评论:(0)  加入收藏
外贸网站建好开始铺垫内容的时候,很多人肯定关系自己的内容页面什么时候被收录,什么时候会有排名,但是最重要的点莫过于–页面被收录才可能会有排名。 在新站权重不高,关键...【详细内容】
2019-11-04   Go  点击:(8)  评论:(0)  加入收藏
此篇写的是MongoDB数据库在JAVA程序中的基本功能,这是一篇多年前写的手记,因此采用的技术比较老(采用的是MyEclipse和Jar包的方式),虽然现有的技术已经更新换代,但操作原理基本相...【详细内容】
2019-11-04   Go  点击:(8)  评论:(0)  加入收藏
我写了一篇有关如何使用DevOps时尚风格部署mongodb集群的新文章,在本文中,我正在使用Terraform,Ansible,Packer和更酷的技术,我强烈建议您阅读它。以DevOps时尚风格(将基础架构作...【详细内容】
2019-11-04   Go  点击:(4)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条