译|Go Concurrency Patterns: Context(下)

简介: 译|Go Concurrency Patterns: Context(下)

server 程序

server 程序处理诸如 /search?q=golang 的请求为 golang 提供 Google 搜索结果。它注册 handlesearch 来处理 _/search endpoint_。回调函数创建一个名为 ctx 的初始 Context,并安排了在回调函数返回时取消它。如果请求包含 timeout URL 参数,则在超时结束时 Context 将自动取消:

func handleSearch(w http.ResponseWriter, req *http.Request) {
    // ctx is the Context for this handler. Calling cancel closes the
    // ctx.Done channel, which is the cancellation signal for requests
    // started by this handler.
    var (
        ctx    context.Context
        cancel context.CancelFunc
    )
    timeout, err := time.ParseDuration(req.FormValue("timeout"))
    if err == nil {
        // The request has a timeout, so create a context that is
        // canceled automatically when the timeout expires.
        ctx, cancel = context.WithTimeout(context.Background(), timeout)
    } else {
        ctx, cancel = context.WithCancel(context.Background())
    }
    defer cancel() // Cancel ctx as soon as handleSearch returns.

回调函数从请求中提取查询,并通过调用 userip 包提取客户机的 IP 地址。后端请求需要客户端的 IP 地址,因此 handlesearch 将其附加到 _ctx_:

// Check the search query.
   query := req.FormValue("q")
   if query == "" {
       http.Error(w, "no query", http.StatusBadRequest)
       return
   }
   // Store the user IP in ctx for use by code in other packages.
   userIP, err := userip.FromRequest(req)
   if err != nil {
       http.Error(w, err.Error(), http.StatusBadRequest)
       return
   }
   ctx = userip.NewContext(ctx, userIP)

回调函数使用 ctxquery 调用 _google.Search_:

// Run the Google search and print the results.
start := time.Now()
results, err := google.Search(ctx, query)
elapsed := time.Since(start)

如果搜索成功,回调函数渲染结果:

if err := resultsTemplate.Execute(w, struct {
       Results          google.Results
       Timeout, Elapsed time.Duration
   }{
       Results: results,
       Timeout: timeout,
       Elapsed: elapsed,
   }); err != nil {
       log.Print(err)
       return
   }

Package userip

userip 包提供了从请求中提取用户 IP 地址并将其与 Context 关联的函数。 Context 提供键-值映射,其中键和值都是 interface{} 类型。键类型必须支持相等,且值必须安全地供多个 goroutine 同时使用。像 userip 这样的包隐藏映射的细节,并提供对特定 Context 值的强类型访问。

为了避免键冲突, userip 定义了一个未导出的类型 _key_,并使用此类型的值作为 Context 键:

// The key type is unexported to prevent collisions with context keys defined in
// other packages.
type key int
// userIPkey is the context key for the user IP address.  Its value of zero is
// arbitrary.  If this package defined other context keys, they would have
// different integer values.
const userIPKey key = 0

fromrequesthttp.request 中提取 userip 的值:

func FromRequest(req *http.Request) (net.IP, error) {
    ip, _, err := net.SplitHostPort(req.RemoteAddr)
    if err != nil {
        return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
}

newcontext 返回一个的携带入参 userip 值的新 Context

func NewContext(ctx context.Context, userIP net.IP) context.Context {
    return context.WithValue(ctx, userIPKey, userIP)
}

fromcontextcontext 中提取 userip:

func FromContext(ctx context.Context) (net.IP, bool) {
    // ctx.Value returns nil if ctx has no value for the key;
    // the net.IP type assertion returns ok=false for nil.
    userIP, ok := ctx.Value(userIPKey).(net.IP)
    return userIP, ok
}

Package google

google.Search 函数向 google web search api 发出 HTTP 请求,并解析 JSON 编码的结果。它接受 Context 参数 _ctx_,请求运行时,如果 ctx.done 关闭,则立刻返回。

google web search api 请求包括搜索 queryuser ip 作为查询参数:

func Search(ctx context.Context, query string) (Results, error) {
    // Prepare the Google Search API request.
    req, err := http.NewRequest("GET", "https://ajax.googleapis.com/ajax/services/search/web?v=1.0", nil)
    if err != nil {
        return nil, err
    }
    q := req.URL.Query()
    q.Set("q", query)
    // If ctx is carrying the user IP address, forward it to the server.
    // Google APIs use the user IP to distinguish server-initiated requests
    // from end-user requests.
    if userIP, ok := userip.FromContext(ctx); ok {
        q.Set("userip", userIP.String())
    }
    req.URL.RawQuery = q.Encode()

search 使用一个 helper 函数 httpdo 来发出 HTTP 请求;在处理请求或响应时,如果 ctx.done 关闭,将取消调用。search 将闭包传递给 httpdo 处理 HTTP 响应:

var results Results
  err = httpDo(ctx, req, func(resp *http.Response, err error) error {
      if err != nil {
          return err
      }
      defer resp.Body.Close()
      // Parse the JSON search result.
      // https://developers.google.com/web-search/docs/#fonje
      var data struct {
          ResponseData struct {
              Results []struct {
                  TitleNoFormatting string
                  URL               string
              }
          }
      }
      if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
          return err
      }
      for _, res := range data.ResponseData.Results {
          results = append(results, Result{Title: res.TitleNoFormatting, URL: res.URL})
      }
      return nil
  })
  // httpDo waits for the closure we provided to return, so it's safe to
  // read results here.
  return results, err

httpdo 函数运行 HTTP 请求并在新的 goroutine 中处理其响应。如果 ctx.donegoroutine 退出之前关闭, 将取消请求:

func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error {
    // Run the HTTP request in a goroutine and pass the response to f.
    c := make(chan error, 1)
    req = req.WithContext(ctx)
    go func() { c <- f(http.DefaultClient.Do(req)) }()
    select {
    case <-ctx.Done():
        <-c // Wait for f to return.
        return ctx.Err()
    case err := <-c:
        return err
    }
}

根据 Context 调整代码

许多服务框架提供了包和类型,用于承载请求作用域的值。我们可以定义 Context 接口的新实现,以便在使用现有框架的代码和需要 Context 参数的代码之间架起桥梁。

例如,Gorilla 的 github.com/gorilla/context 包允许处理程序通过提供从 HTTP 请求到键值对的映射,将数据与传入请求相关联。在 gorilla.go,我们提供了一个 Context 实现,其 Value 方法返回与 Gorilla 包中 HTTP 请求相关联的值。

其他包提供了类似于 Context 的取消支持。例如,Tomb 提供了一个 kill 方法,通过关闭一个 dying channel 发出取消信号。 tomb 还提供了等待这些 goroutine 退出的方法,类似于 sync.WaitGroup. 在 tomb.go,我们提供了一个 Context 实现,当其父 Context 被取消或提供的 tomb 被杀死时,该 Context 实现被取消。

总结

在 Google,我们要求 Go 程序员将 Context 参数作为第一个参数传递给传入和传出请求之间调用路径上的每个函数。这使得许多不同团队开发的 Go 代码能够很好地互操作。它提供了对超时和取消的简单控制,并确保诸如安全凭据之类的关键值正确地传递到程序中。

想要基于 Context 构建的服务器框架应该提供 Context 的实现,以便在其包和那些需要上下文参数的包之间架起桥梁。它们的客户端库接受来自调用代码的 Context。通过为请求作用域的数据和取消建立公共接口,Context 使包开发人员更容易共享代码以创建可伸缩的服务。

原文:Go Concurrency Patterns: Context

本文作者 : cyningsun

本文地址https://www.cyningsun.com/01-19-2021/go-concurrency-patterns-context-cn.html

版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!

# Golang

  1. 译|There Are No Reference Types in Go
  2. Go 语言没有引用类型,指针也与众不同
  3. 译|What “accept interfaces, return structs” means in Go
  4. 如何用好 Go interface
  5. 一个优雅的 LRU 缓存实现
目录
相关文章
|
5月前
|
Go
go语言并发编程(五) ——Context
go语言并发编程(五) ——Context
|
1月前
|
存储 Go 数据库
Go语言Context包源码学习
【10月更文挑战第21天】Go 语言中的 `context` 包用于在函数调用链中传递请求上下文信息,支持请求的取消、超时和截止时间管理。其核心接口 `Context` 定义了 `Deadline`、`Done`、`Err` 和 `Value` 方法,分别用于处理截止时间、取消信号、错误信息和键值对数据。包内提供了 `emptyCtx`、`cancelCtx`、`timerCtx` 和 `valueCtx` 四种实现类型,满足不同场景需求。示例代码展示了如何使用带有超时功能的上下文进行任务管理和取消。
|
3月前
|
存储 SQL Go
一文弄懂Go语言的Context包,值得收藏!
在开发高效的Go应用程序时,处理超时、取消操作和传递请求范围的数据至关重要。Go标准库中的`context`包提供了一套强大的工具,用于在不同层级间传递取消信号、超时和截止时间等信息。本文首先概述了`context`包的核心功能,接着详细介绍了关键方法,如`WithCancel`、`WithDeadline`、`WithTimeout`和`WithValue`的使用场景。通过创建和派生上下文,开发者能更好地管理请求的生命周期,控制长时间运行的操作,并在并发环境中传递必要的数据。
53 1
|
6月前
|
数据管理 Go 开发者
Golang深入浅出之-Go语言上下文(context)包:处理取消与超时
【4月更文挑战第25天】Go语言中的`context`包在并发、网络请求和长任务中至关重要,提供取消、截止时间和元数据管理。本文探讨`context`基础,如`Background()`、`TODO()`、`WithCancel()`、`WithDeadline()`和`WithTimeout()`。常见问题包括不当传递、过度使用`Background()`和`TODO()`以及忽略错误处理。通过取消和超时示例,强调正确传递上下文、处理取消错误和设置超时以提高应用健壮性和响应性。正确使用`context`是构建稳定高效Go应用的关键。
104 1
|
6月前
|
Go 开发者
Golang深入浅出之-Go语言上下文(context)包:处理取消与超时
【4月更文挑战第23天】Go语言的`context`包提供`Context`接口用于处理任务取消、超时和截止日期。通过传递`Context`对象,开发者能轻松实现复杂控制流。本文解析`context`包特性,讨论常见问题和解决方案,并给出代码示例。关键点包括:1) 确保将`Context`传递给所有相关任务;2) 根据需求选择适当的`Context`创建函数;3) 定期检查`Done()`通道以响应取消请求。正确使用`context`包能提升Go程序的控制流管理效率。
66 1
|
6月前
|
存储 安全 程序员
一文搞懂Go语言Context
一文搞懂Go语言Context
63 0
|
6月前
|
SQL Go 数据库
Go语言Context应用全攻略:异步编程利器
Go语言Context应用全攻略:异步编程利器
170 0
|
6月前
|
安全 Go 数据库
Go语言高级特性:Context深入解读
Go语言高级特性:Context深入解读
47 0
Go 语言 context 最佳实践教程
Go 语言 context 最佳实践教程
372 1
|
安全 Go
Go 语言 context 最佳实践
Go 语言 context 最佳实践
71 0