实战:用取消参数使 Go net/http 服务更灵活( 二 )
我们可以用 go run server.go 来启动服务 。 使用 curl localhost:8888 来发送一个请求:
$ time curl localhost:8888curl: (52) Empty reply from servercurl localhost:88880.01s user 0.01s system 0% CPU 2.021 total
这个请求需要两秒来完成处理 , 服务返回的响应是空的 。 虽然我们的服务知道在 1 秒之后我们写不了响应了 , 但 handler 还是多耗了 100% 的时间(2 秒)来完成处理 。
虽然这是个类似超时的处理 , 但它更大的作用是在到达超时时间时 , 阻止服务进行更多的操作 , 结束请求 。 在我们上面的例子中 , handler 在完成之前一直在处理请求 , 即使已经超出响应写超时时间(1 秒)100%(耗时 2 秒) 。
最根本的问题是 , 对于处理器来说 , 我们应该怎么设置超时时间才更有效?
处理超时我们的目标是确保我们的 slowHandler 在 1s 内完成处理 。 如果超过了 1s , 我们的服务会停止运行并返回对应的超时错误 。
在 Go 和一些其它编程语言中 , 组合往往是设计和开发中最好的方式 。 标准库的 `net/http` 包[5]有很多相互兼容的元素 , 开发者可以不需经过复杂的设计考虑就可以轻易将它们组合在一起 。
基于此 , net/http 包提供了`TimeoutHandler`[6] — 返回了一个在给定的时间限制内运行的 handler 。
函数签名:
func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler
第一个参数是 Handler , 第二个参数是 time.Duration (超时时间) , 第三个参数是 string 类型 , 当到达超时时间后返回的信息 。
用 TimeoutHandler 来封装我们的 slowHandler , 我们只需要:
package mainimport ( "fmt" "io" "net/http" "time")func slowHandler(w http.ResponseWriter, req *http.Request) { time.Sleep(2 * time.Second) io.WriteString(w, "I am slow!\n")}func main() { srv := http.Server{Addr:":8888",WriteTimeout: 5 * time.Second,Handler:http.TimeoutHandler(http.HandlerFunc(slowHandler), 1*time.Second, "Timeout!\n"), } if err := srv.ListenAndServe(); err != nil {fmt.Printf("Server failed: %s\n", err) }}
两个需要留意的地方是:
- 我们在 http.TimetoutHandler 里封装 slowHanlder , 超时时间设为 1s , 超时信息为 “Timeout!” 。
- 我们把 WriteTimeout 增加到 5s , 以给予 http.TimeoutHandler 足够的时间执行 。 如果我们不这么做 , 当 TimeoutHandler 开始执行时 , 已经过了 deadline , 不能再写到响应 。
$ time curl localhost:8888Timeout!curl localhost:88880.01s user 0.01s system 1% CPU 1.023 total
1s 后 , 我们的 TimeoutHandler 开始执行 , 阻止运行 slowHandler , 返回文本信息 ”Timeout!“ 。 如果我们设置信息为空 , handler 会返回默认的超时响应信息 , 如下:TimeoutTimeout
如果忽略掉输出 , 这还算是整洁 , 不是吗?现在我们的程序不会有过长耗时的处理;也避免了有人恶意发送导致长耗时处理的请求时 , 导致的潜在的 DoS 攻击 。尽管我们设置超时时间是一个伟大的开始 , 但它仍然只是初级的保护 。 如果你可能会面临 DoS 攻击 , 你应该采用更高级的保护工具和技术 。 (可以试试Cloudflare[7] )
我们的 slowHandler 仅仅是个简单的 demo 。 但是 , 如果我们的程序复杂些 , 能向其他服务和资源发出请求会发生什么呢?如果我们的程序在超时时向诸如 S3 的服务发出了请求会怎么样?
会发生什么?
未处理的超时和请求取消我们稍微展开下我们的例子:
func slowAPICall() string { d := rand.Intn(5) select { case <-time.After(time.Duration(d) * time.Second):log.Printf("Slow API call done after %s seconds.\n", d)return "foobar" }}func slowHandler(w http.ResponseWriter, r *http.Request) { result := slowAPICall() io.WriteString(w, result+"\n")}
我们假设最初我们不知道 slowHandler 由于通过 slowAPICall 函数向 API 发请求导致需要耗费这么长时间才能处理完成 ,slowAPICall 函数很简单:使用 select 和一个能阻塞 0 到 5 秒的 time.After。 当经过了阻塞的时间后 , time.After 方法通过它的 channel 发送一个值 , 返回 "foobar"。
(另一种方法是 , 使用 sleep(time.Duration(rand.Intn(5)) * time.Second) , 但我们仍然使用 select , 因为它会使我们下面的例子更简单 。 )
如果我们运行起服务 , 我们预期超时 handler 会在 1 秒之后中断请求处理 。 来发送一个请求验证一下:
$ time curl localhost:8888Timeout!curl localhost:88880.01s user 0.01s system 1% CPU 1.021 total
通过观察服务的输出 , 我们会发现 , 它是在几秒之后打出日志的 , 而不是在超时 handler 生效时打出:
- 看不上|为什么还有用户看不上华为Mate40系列来看看内行人怎么说
- 采用|消息称一加9系列将推出三款新机,新增一加9E
- 会员|美容院使用会员管理软件给顾客更好的消费体验!
- 行业|现在行业内客服托管费用是怎么算的
- 闲鱼|电诉宝:“闲鱼”网络欺诈成用户投诉热点 Q3获“不建议下单”评级
- 美国|英国媒体惊叹:165个国家采用北斗将GPS替代,连美国也不例外?
- 桌面|日常使用的软件及网站分享 篇一:几个动态壁纸软件和静态壁纸网站:助你美化你的桌面
- 同轴心配合|用SolidWorks画一个直角传动,画四个零件就行
- 先别|用了周冬雨的照片,我会成为下一个被告?自媒体创作者先别自乱阵脚
- 速度|华为P50Pro或采用很吓人的拍照技术:液体镜头让对焦速度更快