
go 1.7+ 推荐使用 context.context 配合 http.request.withcontext() 实现请求的主动取消,替代已弃用的 cancelrequest;通过控制 context 的生命周期(如调用 cancel()),可安全中断阻塞中的长轮询请求。
在 Go 中实现长轮询(long-polling)时,客户端常需根据用户操作(如点击“取消”按钮)提前中止正在等待响应的 HTTP 请求。此时仅靠超时机制(如 http.Client.Timeout 或 http.Transport 设置)无法满足动态取消需求——你需要的是可编程、可触发、线程安全的请求中断能力。
自 Go 1.7 起,标准库正式引入 context 包并深度集成到 net/http 中,推荐方式是:*构造带 cancelable context 的 `http.Request,再交由http.Client.Do()执行**。当该 context 被取消时,底层连接将被优雅关闭,正在阻塞的Read或Decode操作会立即返回io.EOF或context.Canceled` 错误。
以下是一个完整、生产可用的示例:
采用HttpClient向服务器端action请求数据,当然调用服务器端方法获取数据并不止这一种。WebService也可以为我们提供所需数据,那么什么是webService呢?,它是一种基于SAOP协议的远程调用标准,通过webservice可以将不同操作系统平台,不同语言,不同技术整合到一起。 实现Android与服务器端数据交互,我们在PC机器java客户端中,需要一些库,比如XFire,Axis2,CXF等等来支持访问WebService,但是这些库并不适合我们资源有限的android手机客户端,
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)
type ResponseMessage struct {
ID string `json:"id"`
Data string `json:"data"`
}
func longPollWithCancel(client *http.Client, url string, jsonPostBytes []byte) ([]*ResponseMessage, error) {
// 创建可取消的 context
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 确保函数退出时释放资源(除非你明确需要延迟取消)
// 构建请求
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonPostBytes))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
// 绑定 context 到请求
req = req.WithContext(ctx)
// 发起请求
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
// 解码响应(此处可能长时间阻塞)
var results []*ResponseMessage
decoder := json.NewDecoder(resp.Body)
if err := decoder.Decode(&results); err != nil {
// 注意:若 context 已取消,err 很可能是 context.Canceled 或 io.EOF
if ctx.Err() != nil {
return nil, fmt.Errorf("request canceled: %w", ctx.Err())
}
return nil, fmt.Errorf("decode failed: %w", err)
}
return results, nil
}
// 示例:在另一个 goroutine 中触发取消
func exampleWithUserAction() {
client := &http.Client{}
jsonPost := []byte(`{"action":"poll"}`)
// 启动长轮询
done := make(chan error, 1)
go func() {
_, err := longPollWithCancel(client, "https://api.example.com/longpoll", jsonPost)
done <- err
}()
// 模拟用户 3 秒后点击“取消”
time.AfterFunc(3*time.Second, func() {
// ⚠️ 注意:此处 cancel() 必须作用于发起请求时创建的同一 context
// 实际应用中,需将 cancel 函数暴露给 UI 层或事件处理器
fmt.Println("User triggered cancellation...")
// (此处需持有 cancel 函数引用 —— 生产代码应重构为返回 cancel)
})
// 等待结果或超时
select {
case err := <-done:
if err != nil {
fmt.Printf("Poll result: %v\n", err)
} else {
fmt.Println("Poll succeeded")
}
case <-time.After(5 * time.Second):
fmt.Println("Timeout waiting for poll result")
}
}⚠️ 关键注意事项:
- cancel() 必须与 req.WithContext(ctx) 中的 ctx 对应:不能在别处新建 context 或复用其他请求的 cancel 函数;
- resp.Body.Close() 仍需调用:即使请求被取消,也应 defer resp.Body.Close() 防止资源泄漏;
- 错误判断要检查 ctx.Err():json.Decoder.Decode() 在 context 取消后通常返回 io.EOF 或 io.ErrUnexpectedEOF,但根本原因是 ctx.Err(),建议优先检查;
- 避免 http.DefaultClient 直接使用 cancel context:若多个请求共享同一 client,建议为每个请求单独构造带 context 的 request,而非修改 client 全局行为;
- Go 1.19+ 更推荐 context.WithTimeout 或 context.WithDeadline:若取消逻辑含时间约束,直接使用更语义清晰。
总结:Go 的 context 机制为 HTTP 请求提供了强大而统一的取消范式。它不仅适用于长轮询,也适用于文件上传、流式响应、重试逻辑等所有需主动中断的场景。掌握 WithContext + cancel() 模式,是编写健壮、响应式 Go 网络客户端的核心技能之一。









