package main
import (
"context"
"fmt"
"net/http"
"time"
)
// 模拟调用下游 API1
func callAPI1(ctx context.Context) error {
select {
case <-time.After(2 * time.Second):
fmt.Println("API1 call completed")
return nil
case <-ctx.Done():
fmt.Println("API1 call cancelled")
return ctx.Err()
}
}
// 模拟调用下游 API2
func callAPI2(ctx context.Context) error {
select {
case <-time.After(3 * time.Second):
fmt.Println("API2 call completed")
return nil
case <-ctx.Done():
fmt.Println("API2 call cancelled")
return ctx.Err()
}
}
func httpHandler(w http.ResponseWriter, r *http.Request) {
// 创建带有超时的 context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 创建一个 channel 用于接收 API 调用结果
resultCh := make(chan error, 2)
// 启动 goroutine 调用 API1
go func() {
resultCh <- callAPI1(ctx)
}()
// 启动 goroutine 调用 API2
go func() {
resultCh <- callAPI2(ctx)
}()
// 等待所有 API 调用完成或 context 取消
for i := 0; i < 2; i++ {
select {
case err := <-resultCh:
if err != nil {
fmt.Println("API call failed:", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
case <-ctx.Done():
fmt.Println("Request cancelled or timed out")
w.WriteHeader(http.StatusRequestTimeout)
return
}
}
fmt.Println("All API calls completed successfully")
w.WriteHeader(http.StatusOK)
}
func main() {
http.HandleFunc("/", httpHandler)
fmt.Println("Server is listening on :8080")
http.ListenAndServe(":8080", nil)
}
思路说明
- 创建带有超时的 context:使用
context.WithTimeout
创建一个带有 5 秒超时的 context。当客户端取消请求(例如关闭连接)或整个操作超时,该 context 会被取消。
- 在 API 调用中使用 context:在
callAPI1
和 callAPI2
函数中,通过 select
语句监听 ctx.Done()
通道。如果该通道接收到数据,说明 context 被取消,此时取消正在进行的 API 调用并返回错误。
- 并发调用 API:在
httpHandler
函数中,通过 goroutine 并发调用 callAPI1
和 callAPI2
,并将结果发送到 resultCh
通道。
- 等待结果或 context 取消:通过
for
循环和 select
语句等待所有 API 调用完成或 context 取消。如果任何一个 API 调用失败或 context 取消,立即返回相应的 HTTP 状态码。