新闻资讯  快讯  焦点  财经  政策  社会
互 联 网   电商  金融  数据  计算  技巧
生活百科  科技  职场  健康  法律  汽车
手机百科  知识  软件  修理  测评  微信
软件技术  应用  系统  图像  视频  经验
硬件技术  知识  技术  测评  选购  维修
网络技术  硬件  软件  设置  安全  技术
程序开发  语言  移动  数据  开源  百科
安全防护  资讯  黑客  木马  病毒  移动
站长技术  搜索  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   点击:()  评论:()
声明:本站部分内容来自互联网,内容观点仅代表作者本人,如有任何版权侵犯请与我们联系,我们将立即删除。
▌相关评论
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
▌相关推荐
近日,蚂蚁金服宣布,支付宝 App 首页大改版,升级为数字生活开放平台。App将进行改版升级,强化生活服务心智,首页新增外卖到家、果蔬商超医药等便民生活版块,并基于智能算法为用户推...【详细内容】
2020-03-13   Go  点击:(49)  评论:(0)  加入收藏
作者:HelloGitHub-Prodesire一、前言在本系列前面所有文章中,我们分别介绍了 argparse、docopt 和 click 的主要功能和用法。它们各具特色,都能出色地完成命令行任务。argpars...【详细内容】
2020-03-08   Go  点击:(6)  评论:(0)  加入收藏
MongoDB提供了一系列组件来提升数据的安全性。数据安全在MongoDB中是最重要的——因此它利用这些组件来减少曝光面。下面是10个可以用来改善你个人或云中MongoDB...【详细内容】
2020-02-16   Go  点击:(15)  评论:(0)  加入收藏
作为一名后端程序员,命令行程序太普遍了。如何开始一个类似的命令行程序的工具以及相关依赖包也非常多。个人最常使用的工具包是: spf13/cobra。 同时结合作者提供的spf13/viper包,命令行程序需要的绝大部分功能作者都...【详细内容】
2020-01-26   Go  点击:(1)  评论:(0)  加入收藏
这篇文章列举了我在 Go 项目中遇见的最常见错误,排序先后不重要。...【详细内容】
2020-01-17   Go  点击:(1)  评论:(0)  加入收藏
介绍按照惯例,本文从 “ Hello,World!”开始。这是一个简单而完整的第一个程序,也是确保正确配置环境的好方法。我们将完成在Go中创建该程序的过程,如下所示:OutputPlease enter...【详细内容】
2020-01-14   Go  点击:(22)  评论:(0)  加入收藏
由于对于国内网站来说,Google基本没有流量了,并且近两年百度站长平台的工具完善了很多,大部分Google Webmaster中的工具在百度站长平台上都有了,百度站长平台更加贴近中国站长。...【详细内容】
2019-12-31   Go  点击:(29)  评论:(0)  加入收藏
一、付费广告与自然搜索结果差别在讲Google搜索广告之前,我们先来明确下付费广告与自然搜索结果之间的差别。当我们在Google 上搜索产品或服务时,搜索结果将由以下两部分组成:...【详细内容】
2019-12-31   Go  点击:(31)  评论:(0)  加入收藏
编写健壮且高性能的网络服务需要付出大量的努力。提高服务性能的方式有很多种,比如优化应用层的代码,更进一步,还可以看看垃圾回收器,操作系统,网络传输,以及部署我们服务的硬件是否有优化空间。...【详细内容】
2019-12-30   Go  点击:(34)  评论:(0)  加入收藏
当我与SEO讨论分析时,问他们:“谈到分析时,您会做什么?首先要做什么?当您要聘用新客户时,您会做什么?” SEO常常真的很想告诉我:“我深入研究数据。这是我的样子。这是我设置的视图...【详细内容】
2019-12-24   Go  点击:(29)  评论:(0)  加入收藏
Google搜索引擎的本质是一个数据库,它会全自动的从全球的网络中发现这些网页并加入自己的数据库中,经过一些处理,然后待搜索用户使用的时候观看。从google发现我们的网页,到最终...【详细内容】
2019-12-20   Go  点击:(35)  评论:(0)  加入收藏
Go 的编译结果基本上只依赖libc(传言,第三方库是否依赖那就另当别论了), 所以docker image 实际可以做的很小 使用apline 作为docker 的运行环境产生尽可能小的运行环境 国...【详细内容】
2019-12-19   Go  点击:(30)  评论:(0)  加入收藏
谷歌收录是指,Google有没有将你的网页,放入自己的数据库。这样可以在谷歌seo的时候,达到可以通过自然流量搜索到你,并且产生询盘、订单等目的。在过去的几年,也就是Google缺数据...【详细内容】
2019-12-18   Go  点击:(29)  评论:(0)  加入收藏
google站长工具最近推出了新版的界面,比原来更加易用了,权哥惊讶地发现里面的数据分析工具实在太强大了,它直接给出了google对我们网站的数据,包括:爆光量、点击率、关键词、对应...【详细内容】
2019-12-17   Go  点击:(24)  评论:(0)  加入收藏
通常我们的数据库都配置为内网访问,但由于业务部署架构的不同,有时也需要通过公网访问 MongoDB 数据库,此时为了防止被端口扫描和脱库,MongoDB 需要配置为 TLS 访问,那在 Go...【详细内容】
2019-12-13   Go  点击:(34)  评论:(0)  加入收藏
之前有写过类似的,实现方式大致上都是一样的package com.bus.wx.action.code; import java.awt.BasicStroke;import java.awt.Color;import java.awt.Graphics2D;import java...【详细内容】
2019-12-12   Go  点击:(46)  评论:(0)  加入收藏
Golang简介Go语言是谷歌2009年发布的第二款开源编程语言。这是一门全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。Go语言具有很强的表达能力,它简洁、...【详细内容】
2019-12-11   Go  点击:(31)  评论:(0)  加入收藏
学习一门新语言并不容易,但是如果有具体的例子和手把手指导教程,就很容易上手了。因此,我决定编写一系列分步指导教程。...【详细内容】
2019-12-10   Go  点击:(18)  评论:(0)  加入收藏
用过数据库的都知道,数据库索引与书籍的索引类似,都是用来帮助快速查找的。MongoDB的索引跟关系型数据库的索引几乎一致。1. 索引的创建mongodb采用ensureIndex来创建索引,如:db...【详细内容】
2019-12-09   Go  点击:(65)  评论:(0)  加入收藏
作者:蘑菇先生出处:http://mushroom.cnblogs.com/ 背景最新有同事反馈,服务间有调用超时的现象,在业务高峰期发生的概率和次数比较高。从日志中调用关系来看,有2个调用链经常发...【详细内容】
2019-12-09   Go  点击:(51)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条