为了账号安全,请及时绑定邮箱和手机立即绑定

如何中断 HTTP 处理程序?

如何中断 HTTP 处理程序?

Go
饮歌长啸 2023-07-31 16:30:01
假设我有一个像这样的 http 处理程序:func ReallyLongFunction(w http.ResponseWriter, r *http.Request) {        fmt.Fprintf(w, "Hello World!")        // run code that takes a long time here        // Executing dd command with cmd.Exec..., etc.})如果用户刷新页面或以其他方式终止请求而不运行后续代码,有没有办法可以中断此功能,我该如何做?我尝试这样做:notify := r.Context().Done()go func() {    <-notify     println("Client closed the connection")     s.downloadCleanup()     return}()但每当我中断它之后的代码仍然会运行。
查看完整描述

2 回答

?
侃侃尔雅

TA贡献1801条经验 获得超15个赞

没有办法从该 goroutine外部的任何代码中强制拆除该 goroutine。


因此,真正中断处理的唯一方法是定期检查客户端是否消失(或者是否有另一个信号来停止处理)。


基本上这相当于构建你的处理程序是这样的


func ReallyLongFunction(w http.ResponseWriter, r *http.Request) {

    fmt.Fprintf(w, "Hello World!")


    done := r.Context().Done()


    // Check wheteher we're done


    // do some small piece of stuff


    // check whether we're done


    // do another small piece of stuff


    // …rinse, repeat

})

现在,检查是否有内容写入通道但不阻塞操作的方法是使用“使用默认值选择”习惯用法:


select {

    case <- done:

        // We're done

    default:

}

当且仅当done被写入或被关闭(上下文就是这种情况)时,此状态才会执行“// We're done”块中的代码,否则default执行分支中的空块。


所以我们可以将其重构为类似的东西


func ReallyLongFunction(w http.ResponseWriter, r *http.Request) {

    fmt.Fprintf(w, "Hello World!")


    done := r.Context().Done()

    closed := func () bool {

        select {

            case <- done:

                return true

            default:

                return false

        }

    }


    if closed() {

        return

    }


    // do some small piece of stuff


    if closed() {

        return

    }


    // do another small piece of stuff


    // …rinse, repeat

})

停止在 HTTP 处理程序中启动的外部进程

为了解决OP的评论......

os/exec.Cmd类型具有Process字段,该字段具有类型os.Process并且该类型支持Kill强制关闭正在运行的进程的方法。

唯一的问题是exec.Cmd.Run阻塞直到进程退出,因此正在执行的 goroutine 无法执行其他代码,并且如果exec.Cmd.Run在 HTTP 处理程序中调用,则无法取消它。

如何最好地处理以这种异步方式运行程序在很大程度上取决于进程本身的组织方式,但我会这样滚动:

  1. 在处理程序中,准备进程,然后使用exec.Cmd.Start(而不是Run)启动它。

    检查已返回的错误值Start:如果是,则nil 进程已成功启动。否则,以某种方式将失败传达给客户端并退出处理程序。

    一旦知道流程已经启动,该exec.Cmd值的一些字段就会填充与流程相关的信息;特别感兴趣的是Process类型为 的字段 os.Process:该类型具有Kill可用于强制关闭进程的方法。

  2. 启动一个 goroutine 并向其传递该exec.Cmd值和某种合适类型的通道(见下文)。

    该 Goroutine 应该调用Wait它,一旦返回,它应该通过该通道将该事实传达回原始 Goroutine。

    到底要通信什么,是一个悬而未决的问题,因为它取决于您是否想要收集进程写入其标准输出和错误流的内容和/或可能是与进程活动相关的一些其他数据。

    发送数据后,该 goroutine 退出。

  3. 主 goroutine(执行处理程序)应该exec.Cmd.Process.Kill在检测到处理程序应该终止时调用。

    终止进程最终会解除对 goroutine 的阻塞,该 goroutine在进程退出时Wait以相同的值执行。exec.Cmd

    终止进程后,处理程序 goroutine 会在通道上等待监听该进程的 goroutine 的回复。处理程序对该数据执行某些操作(可能是记录它或其他什么)并退出。


查看完整回答
反对 回复 2023-07-31
?
慕莱坞森

TA贡献1810条经验 获得超4个赞

您应该从内部取消 goroutine,因此对于长时间的计算任务,您可以提供检查点,以停止并检查取消:


这是服务器的测试代码,该代码具有例如长计算任务和取消检查点:


package main


import (

    "fmt"

    "io"

    "log"

    "net/http"

    "time"

)


func main() {

    http.HandleFunc(`/`, func(w http.ResponseWriter, r *http.Request) {

        ctx := r.Context()


        log.Println("wait a couple of seconds ...")

        for i := 0; i < 10; i++ { // long calculation

            select {

            case <-ctx.Done():

                log.Println("Client closed the connection:", ctx.Err())

                return

            default:

                fmt.Print(".")

                time.Sleep(200 * time.Millisecond) // long calculation

            }

        }

        io.WriteString(w, `Hi`)

        log.Println("Done.")

    })

    log.Println(http.ListenAndServe(":8081", nil))

}

这是超时的客户端代码:


package main


import (

    "io/ioutil"

    "log"

    "net/http"

    "time"

)


func main() {

    log.Println("HTTP GET")

    client := &http.Client{

        Timeout: 1 * time.Second,

    }

    r, err := client.Get(`http://127.0.0.1:8081/`)

    if err != nil {

        log.Fatal(err)

    }

    defer r.Body.Close()

    bs, err := ioutil.ReadAll(r.Body)

    if err != nil {

        log.Fatal(err)

    }

    log.Println("HTTP Done.")

    log.Println(string(bs))

}

您可以使用普通浏览器检查是否取消,或者关闭、刷新、断开连接,或者...,取消。



查看完整回答
反对 回复 2023-07-31
  • 2 回答
  • 0 关注
  • 90 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信