# http断开后,如何继续执行服务端代码

# 问题

# 业务需求

  • 服务端需要调用一个event stream格式响应的接口,实时响应数据给客户端。
  • 响应数据是被分为多个数据块,以流失数据的形式给到。
  • 服务端能够实现对event stream接口的完整调用

# 遇到问题

由于公司主流的技术架构还是lamp那一套,apache服务器通过调用php module来执行php的脚本。当http请求断开后,php脚本就会终止执行。当客户端没有等待http响应完成就主动断开了连接,就会导致服务端没有从封装的event stream接口中获取到完整的数据。是一次无效的接口调用。

# 思考

为了解决上面的问题,基于公司现有的技术架构,我首先想到了如下方案:

# 方案1.0

  • 开启多个常驻的php进程来处理event stream接口的调用,生产数据。
  • 使用redis list来进行数据之间的同步。
  • 服务端通告轮询redis list消费数据,直到接受到"data [done]"结束。
# 优缺点
  • 优点是http断开后常驻脚本依旧能够完成接口的调用。
  • 缺点是无法解决多用户的并发问题,同一个常驻脚本同一时间只能处理一个请求。

在考虑到并发之后,我想到了下面的第二个方案:

# 方案2.0

  • 使用go来实现业务逻辑,并以api或者让rpc的形式暴露服务,供php调用。这样既能解决调用event stream接口调用完整性的问题,同时还能实现高并发的能力。而且go有着比php更高的性能。

# 解答

下面的解答示例只演示了http主动断开后go继续执行直到结束的相关demo

# DEMO

package main

import (
	"log"
	"time"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	r.Any("/long_async_operation", func(c *gin.Context) {
		// 启动goroutine
		go func() {
			for i := 0; i < 10; i++ {
				// 进行耗时操作
				time.Sleep(1 * time.Second)
				// 结束时打印日志
				log.Println("long_async_operation done")
			}
		}()
	})

	r.Run(":8081")
}

# 启动服务

$ go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /long_async_operation     --> main.main.func1 (3 handlers)
[GIN-debug] POST   /long_async_operation     --> main.main.func1 (3 handlers)
[GIN-debug] PUT    /long_async_operation     --> main.main.func1 (3 handlers)
[GIN-debug] PATCH  /long_async_operation     --> main.main.func1 (3 handlers)
[GIN-debug] HEAD   /long_async_operation     --> main.main.func1 (3 handlers)
[GIN-debug] OPTIONS /long_async_operation     --> main.main.func1 (3 handlers)
[GIN-debug] DELETE /long_async_operation     --> main.main.func1 (3 handlers)
[GIN-debug] CONNECT /long_async_operation     --> main.main.func1 (3 handlers)
[GIN-debug] TRACE  /long_async_operation     --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8081
[GIN] 2023/05/21 - 12:17:34 | 200 |         750ns |       127.0.0.1 | HEAD     "/long_async_operation"
2023/05/21 12:17:35 long_async_operation done
2023/05/21 12:17:36 long_async_operation done
2023/05/21 12:17:37 long_async_operation done
2023/05/21 12:17:38 long_async_operation done
2023/05/21 12:17:39 long_async_operation done
2023/05/21 12:17:40 long_async_operation done
2023/05/21 12:17:41 long_async_operation done
2023/05/21 12:17:42 long_async_operation done
2023/05/21 12:17:43 long_async_operation done
2023/05/21 12:17:44 long_async_operation done

# 客户端调用

$ curl -I http://localhost:8081/long_async_operation
HTTP/1.1 200 OK
Date: Sun, 21 May 2023 04:17:34 GMT