如何在嵌套的 HTTP 中间件中使用 context.Done()?

huangapple go评论85阅读模式
英文:

how to use context.Done() with nested http middleware

问题

我想知道在使用context.Done()方法时,在HTTP服务器和实现中间件时如何正确地实现/使用它,我的目标是在客户端断开连接时取消后续事件,包括嵌套的中间件。

为了测试,我创建了以下代码,我不知道这是否是正确的做法,因为我不得不在HandleFunc中创建一个channel和一个goroutine来处理请求,并将所有这些放在一个select等待语句中。

package main

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

func hello(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	log.Println("handler started")
	defer log.Println("hander ended")

	ch := make(chan struct{})

	go func() {
		time.Sleep(5 * time.Second)
		fmt.Fprintln(w, "Hello")
		ch <- struct{}{}
	}()

	select {
	case <-ch:
	case <-ctx.Done():
		err := ctx.Err()
		log.Println(err)
		http.Error(w, err.Error(), http.StatusPartialContent)
	}
}

func main() {
	http.HandleFunc("/", hello)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

基本上,这里的请求通过睡眠5秒来模拟负载,然后打印Hello,但是如果客户端取消请求,例如:

$ curl 0:8080

然后按下ctrl + c,将会记录如下:

2017/07/07 22:22:40 handler started
2017/07/07 22:22:42 context canceled
2017/07/07 22:22:42 hander ended

这个方法是有效的,但我想知道这种模式(goroutine和select)是否应该在每个嵌套的处理程序中使用,或者是否有更好的实现方式:

ch := make(chan struct{})
go func() {
	// 一些逻辑
    ch <- struct{}{}
}()

select {
case <-ch:
case <-ctx.Done():
	err := ctx.Err()
	log.Println(err)
	http.Error(w, err.Error(), http.StatusPartialContent)
}
英文:

I would like to know how to properly implement/use context.Done() method when using it within an HTTP server and implementing middleware, my goal is to cancel subsequent events when a client disconnects across nested middleware.

For testing I created the following code, I don't know if is the correct way of doing it since I had to create a channel within the HandleFunc and a goroutine to handle the requests, putting all this together within a select wait statement.

package main

import (
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;net/http&quot;
	&quot;time&quot;
)

func hello(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	log.Println(&quot;handler started&quot;)
	defer log.Println(&quot;hander ended&quot;)

	ch := make(chan struct{})

	go func() {
		time.Sleep(5 * time.Second)
		fmt.Fprintln(w, &quot;Hello&quot;)
		ch &lt;- struct{}{}
	}()

	select {
	case &lt;-ch:
	case &lt;-ctx.Done():
		err := ctx.Err()
		log.Println(err)
		http.Error(w, err.Error(), http.StatusPartialContent)
	}
}

func main() {
	http.HandleFunc(&quot;/&quot;, hello)
	log.Fatal(http.ListenAndServe(&quot;:8080&quot;, nil))
}

Basically here the request simulates load by sleeping 5 seconds, and then prints Hello, but if the client cancels the request, for example:

$ curl 0:8080

And then pressing <kbd>ctl</kbd> + <kbd> c</kbd>, this will be loged:

2017/07/07 22:22:40 handler started
2017/07/07 22:22:42 context canceled
2017/07/07 22:22:42 hander ended

This works but wondering if this pattern (the goroutine and select) should be used in every nested handler or if there is a better way of implementing this.:

ch := make(chan struct{})
go func() {
	// some logic	
    ch &lt;- struct{}{}
}()

select {
case &lt;-ch:
case &lt;-ctx.Done():
	err := ctx.Err()
	log.Println(err)
	http.Error(w, err.Error(), http.StatusPartialContent)
}

答案1

得分: 1

在Google,我们要求Go程序员将Context参数作为每个函数的第一个参数传递,该函数在进出请求之间的调用路径上。

英文:

> At Google, we require that Go programmers pass a Context parameter as the first argument to every function on the call path between incoming and outgoing requests.
>
> -- Go Concurrency Patterns: Context

huangapple
  • 本文由 发表于 2017年7月8日 04:26:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/44979106.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定