plugin_runtime.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. package service
  2. import (
  3. "context"
  4. "crypto/sha256"
  5. "encoding/hex"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "net/http"
  11. "os/exec"
  12. "strconv"
  13. "strings"
  14. "sync"
  15. "sync/atomic"
  16. "time"
  17. pluginv1 "github.com/Wei-Shaw/sub2api/pkg/pluginapi/v1"
  18. hclog "github.com/hashicorp/go-hclog"
  19. hcplugin "github.com/hashicorp/go-plugin"
  20. )
  21. type pluginRuntime struct {
  22. installation *PluginInstallation
  23. client *hcplugin.Client
  24. api pluginv1.TransportPluginClient
  25. inFlight atomic.Int64
  26. draining atomic.Bool
  27. done chan struct{}
  28. doneOnce sync.Once
  29. }
  30. func startPluginRuntime(ctx context.Context, installation *PluginInstallation, startTimeout time.Duration, socketDir string) (*pluginRuntime, error) {
  31. if installation == nil {
  32. return nil, errors.New("插件安装记录为空")
  33. }
  34. checksum, err := hex.DecodeString(installation.BinarySHA256)
  35. if err != nil || len(checksum) != sha256.Size {
  36. return nil, errors.New("插件二进制哈希无效")
  37. }
  38. cmd := exec.CommandContext(context.WithoutCancel(ctx), installation.BinaryPath)
  39. client := hcplugin.NewClient(&hcplugin.ClientConfig{
  40. HandshakeConfig: pluginv1.HandshakeConfig,
  41. Plugins: pluginv1.ClientPluginMap(),
  42. Cmd: cmd,
  43. AllowedProtocols: []hcplugin.Protocol{hcplugin.ProtocolGRPC},
  44. StartTimeout: startTimeout,
  45. SecureConfig: &hcplugin.SecureConfig{
  46. Checksum: checksum,
  47. Hash: sha256.New(),
  48. },
  49. Logger: hclog.NewNullLogger(),
  50. SyncStdout: io.Discard,
  51. SyncStderr: io.Discard,
  52. UnixSocketConfig: &hcplugin.UnixSocketConfig{TempDir: socketDir},
  53. SkipHostEnv: true,
  54. })
  55. rpcClient, err := client.Client()
  56. if err != nil {
  57. client.Kill()
  58. return nil, fmt.Errorf("启动插件进程: %w", err)
  59. }
  60. dispensed, err := rpcClient.Dispense(pluginv1.TransportPluginName)
  61. if err != nil {
  62. client.Kill()
  63. return nil, fmt.Errorf("获取插件传输能力: %w", err)
  64. }
  65. api, ok := dispensed.(pluginv1.TransportPluginClient)
  66. if !ok {
  67. client.Kill()
  68. return nil, errors.New("插件未实现传输 gRPC 客户端")
  69. }
  70. runtime := &pluginRuntime{
  71. installation: installation,
  72. client: client,
  73. api: api,
  74. done: make(chan struct{}),
  75. }
  76. infoCtx, cancel := context.WithTimeout(ctx, startTimeout)
  77. defer cancel()
  78. info, err := api.GetInfo(infoCtx, &pluginv1.GetInfoRequest{})
  79. if err != nil {
  80. runtime.kill()
  81. return nil, fmt.Errorf("读取插件信息: %w", err)
  82. }
  83. if info.PluginId != installation.PluginKey || info.PluginVersion != installation.Version ||
  84. info.ProtocolVersion != pluginv1.ProtocolVersion || info.TransportApiVersion != pluginv1.TransportAPIVersion {
  85. runtime.kill()
  86. return nil, errors.New("插件运行时信息与已校验清单不一致")
  87. }
  88. health, err := api.Health(infoCtx, &pluginv1.HealthRequest{})
  89. if err != nil || !health.Healthy {
  90. runtime.kill()
  91. if err != nil {
  92. return nil, fmt.Errorf("插件健康检查失败: %w", err)
  93. }
  94. return nil, fmt.Errorf("插件不健康: %s", health.Message)
  95. }
  96. return runtime, nil
  97. }
  98. func (r *pluginRuntime) validateAndApplyConfig(ctx context.Context, configJSON []byte) error {
  99. _, err := r.validateAndApplyNormalizedConfig(ctx, configJSON)
  100. return err
  101. }
  102. func (r *pluginRuntime) validateAndApplyNormalizedConfig(ctx context.Context, configJSON []byte) ([]byte, error) {
  103. validation, err := r.api.ValidateConfig(ctx, &pluginv1.ValidateConfigRequest{ConfigJson: configJSON})
  104. if err != nil {
  105. return nil, fmt.Errorf("插件配置校验失败: %w", err)
  106. }
  107. if !validation.Valid {
  108. return nil, fmt.Errorf("插件配置无效: %s", validation.Message)
  109. }
  110. if len(validation.NormalizedConfigJson) > 0 {
  111. configJSON = validation.NormalizedConfigJson
  112. }
  113. if len(configJSON) == 0 || len(configJSON) > pluginConfigMaxBytes || !json.Valid(configJSON) {
  114. return nil, errors.New("插件返回的规范化配置不是有效且大小受限的 JSON")
  115. }
  116. var normalized any
  117. if err := json.Unmarshal(configJSON, &normalized); err != nil {
  118. return nil, fmt.Errorf("解析插件规范化配置: %w", err)
  119. }
  120. if normalized == nil {
  121. return nil, errors.New("插件返回的规范化配置根节点必须是对象")
  122. }
  123. if _, ok := normalized.(map[string]any); !ok {
  124. return nil, errors.New("插件返回的规范化配置根节点必须是对象")
  125. }
  126. configJSON, err = json.Marshal(normalized)
  127. if err != nil {
  128. return nil, fmt.Errorf("序列化插件规范化配置: %w", err)
  129. }
  130. applied, err := r.api.ApplyConfig(ctx, &pluginv1.ApplyConfigRequest{ConfigJson: configJSON})
  131. if err != nil {
  132. return nil, fmt.Errorf("应用插件配置失败: %w", err)
  133. }
  134. if !applied.Applied {
  135. return nil, fmt.Errorf("插件拒绝应用配置: %s", applied.Message)
  136. }
  137. return configJSON, nil
  138. }
  139. func (r *pluginRuntime) checkHealth(ctx context.Context) error {
  140. if r == nil || r.api == nil || r.client == nil || r.client.Exited() {
  141. return errors.New("插件进程已退出")
  142. }
  143. health, err := r.api.Health(ctx, &pluginv1.HealthRequest{})
  144. if err != nil {
  145. return fmt.Errorf("插件健康检查失败: %w", err)
  146. }
  147. if health == nil || !health.Healthy {
  148. message := "插件报告不健康"
  149. if health != nil && strings.TrimSpace(health.Message) != "" {
  150. message = "插件不健康: " + health.Message
  151. }
  152. return errors.New(message)
  153. }
  154. return nil
  155. }
  156. func (r *pluginRuntime) beginRequest() bool {
  157. if r == nil || r.draining.Load() {
  158. return false
  159. }
  160. r.inFlight.Add(1)
  161. if r.draining.Load() {
  162. r.finishRequest()
  163. return false
  164. }
  165. return true
  166. }
  167. func (r *pluginRuntime) finishRequest() {
  168. if r.inFlight.Add(-1) == 0 && r.draining.Load() {
  169. r.doneOnce.Do(func() { close(r.done) })
  170. }
  171. }
  172. func (r *pluginRuntime) drain(timeout time.Duration) {
  173. if r == nil {
  174. return
  175. }
  176. r.draining.Store(true)
  177. if r.inFlight.Load() == 0 {
  178. r.doneOnce.Do(func() { close(r.done) })
  179. }
  180. timer := time.NewTimer(timeout)
  181. defer timer.Stop()
  182. select {
  183. case <-r.done:
  184. case <-timer.C:
  185. }
  186. r.kill()
  187. }
  188. func (r *pluginRuntime) kill() {
  189. if r != nil && r.client != nil {
  190. r.client.Kill()
  191. }
  192. }
  193. func (r *pluginRuntime) roundTrip(ctx context.Context, request *http.Request, proxyURL string, account *Account) (*http.Response, error) {
  194. if request == nil || request.URL == nil || account == nil {
  195. return nil, errors.New("插件出站请求参数不完整")
  196. }
  197. streamCtx, cancel := context.WithCancel(ctx)
  198. stream, err := r.api.Forward(streamCtx)
  199. if err != nil {
  200. cancel()
  201. return nil, normalizePluginRPCError(ctx, "创建插件转发流", err, false)
  202. }
  203. requestID := strconv.FormatInt(time.Now().UnixNano(), 36) + "-" + strconv.FormatInt(account.ID, 36)
  204. if err := stream.Send(&pluginv1.ForwardRequest{Frame: &pluginv1.ForwardRequest_Start{Start: &pluginv1.ForwardRequestStart{
  205. RequestId: requestID,
  206. Method: request.Method,
  207. Url: request.URL.String(),
  208. Host: request.Host,
  209. Headers: headersToPlugin(request.Header),
  210. ProxyUrl: proxyURL,
  211. AccountId: account.ID,
  212. AccountConcurrency: int32(account.Concurrency),
  213. Platform: account.Platform,
  214. AccountType: account.Type,
  215. ContentLength: request.ContentLength,
  216. HasBody: request.Body != nil && request.Body != http.NoBody,
  217. }}}); err != nil {
  218. cancel()
  219. // gRPC Send 返回错误时无法证明服务端没有收到元数据,必须禁止自动重放。
  220. return nil, normalizePluginRPCError(ctx, "发送插件请求元数据", err, true)
  221. }
  222. sendErr := make(chan error, 1)
  223. go func() {
  224. err := sendPluginRequestBody(stream, request.Body)
  225. sendErr <- err
  226. if err != nil {
  227. cancel()
  228. }
  229. }()
  230. first, err := stream.Recv()
  231. if err != nil {
  232. cancel()
  233. select {
  234. case bodyErr := <-sendErr:
  235. if bodyErr != nil {
  236. return nil, normalizePluginRPCError(ctx, "发送插件请求体", bodyErr, true)
  237. }
  238. default:
  239. }
  240. return nil, normalizePluginRPCError(ctx, "接收插件响应头", err, true)
  241. }
  242. if frameError := first.GetError(); frameError != nil {
  243. cancel()
  244. return nil, &PluginTransportError{Code: frameError.Code, Message: frameError.Message, RequestSent: frameError.RequestSent}
  245. }
  246. start := first.GetStart()
  247. if start == nil || start.StatusCode < 100 || start.StatusCode > 599 {
  248. cancel()
  249. return nil, &PluginTransportError{
  250. Code: "PLUGIN_INVALID_RESPONSE",
  251. Message: "插件未返回有效的 HTTP 响应头",
  252. RequestSent: true,
  253. }
  254. }
  255. pipeReader, pipeWriter := io.Pipe()
  256. body := &pluginResponseBody{
  257. reader: pipeReader,
  258. cancel: cancel,
  259. done: r.finishRequest,
  260. }
  261. go receivePluginResponseBody(stream, pipeWriter, sendErr)
  262. return &http.Response{
  263. Status: start.Status,
  264. StatusCode: int(start.StatusCode),
  265. Proto: start.Protocol,
  266. ProtoMajor: int(start.ProtocolMajor),
  267. ProtoMinor: int(start.ProtocolMinor),
  268. Header: headersFromPlugin(start.Headers),
  269. Body: body,
  270. ContentLength: start.ContentLength,
  271. Request: request,
  272. }, nil
  273. }
  274. type PluginTransportError struct {
  275. Code string
  276. Message string
  277. RequestSent bool
  278. }
  279. func (e *PluginTransportError) Error() string {
  280. if e == nil {
  281. return "插件传输失败"
  282. }
  283. code := strings.Map(func(value rune) rune {
  284. if value >= 'a' && value <= 'z' || value >= 'A' && value <= 'Z' || value >= '0' && value <= '9' || value == '_' || value == '-' || value == '.' {
  285. return value
  286. }
  287. return -1
  288. }, e.Code)
  289. if len(code) > 64 {
  290. code = code[:64]
  291. }
  292. return fmt.Sprintf("插件传输失败 [%s]: %s", code, sanitizeUpstreamErrorMessage(e.Message))
  293. }
  294. func normalizePluginRPCError(ctx context.Context, operation string, err error, requestMayHaveBeenSent bool) error {
  295. if ctx != nil && ctx.Err() != nil {
  296. return ctx.Err()
  297. }
  298. return &PluginTransportError{
  299. Code: "PLUGIN_RPC_ERROR",
  300. Message: fmt.Sprintf("%s: %v", operation, err),
  301. RequestSent: requestMayHaveBeenSent,
  302. }
  303. }
  304. func sendPluginRequestBody(stream pluginv1.TransportPlugin_ForwardClient, body io.ReadCloser) error {
  305. if body != nil {
  306. defer func() { _ = body.Close() }()
  307. buffer := make([]byte, 32*1024)
  308. for {
  309. read, err := body.Read(buffer)
  310. if read > 0 {
  311. chunk := append([]byte(nil), buffer[:read]...)
  312. if sendErr := stream.Send(&pluginv1.ForwardRequest{Frame: &pluginv1.ForwardRequest_BodyChunk{BodyChunk: chunk}}); sendErr != nil {
  313. return sendErr
  314. }
  315. }
  316. if err == io.EOF {
  317. break
  318. }
  319. if err != nil {
  320. return err
  321. }
  322. }
  323. }
  324. if err := stream.Send(&pluginv1.ForwardRequest{Frame: &pluginv1.ForwardRequest_BodyEnd{BodyEnd: true}}); err != nil {
  325. return err
  326. }
  327. return stream.CloseSend()
  328. }
  329. func receivePluginResponseBody(stream pluginv1.TransportPlugin_ForwardClient, writer *io.PipeWriter, sendErr <-chan error) {
  330. defer func() { _ = writer.Close() }()
  331. for {
  332. frame, err := stream.Recv()
  333. if err == io.EOF {
  334. return
  335. }
  336. if err != nil {
  337. _ = writer.CloseWithError(normalizePluginRPCError(stream.Context(), "接收插件响应体", err, true))
  338. return
  339. }
  340. if chunk := frame.GetBodyChunk(); len(chunk) > 0 {
  341. if _, err := writer.Write(chunk); err != nil {
  342. return
  343. }
  344. continue
  345. }
  346. if frame.GetEnd() != nil {
  347. select {
  348. case err := <-sendErr:
  349. if err != nil {
  350. _ = writer.CloseWithError(normalizePluginRPCError(stream.Context(), "发送插件请求体", err, true))
  351. }
  352. default:
  353. }
  354. return
  355. }
  356. if frameError := frame.GetError(); frameError != nil {
  357. _ = writer.CloseWithError(&PluginTransportError{Code: frameError.Code, Message: frameError.Message, RequestSent: frameError.RequestSent})
  358. return
  359. }
  360. }
  361. }
  362. type pluginResponseBody struct {
  363. reader *io.PipeReader
  364. cancel context.CancelFunc
  365. done func()
  366. once sync.Once
  367. }
  368. func (b *pluginResponseBody) Read(data []byte) (int, error) {
  369. return b.reader.Read(data)
  370. }
  371. func (b *pluginResponseBody) Close() error {
  372. var err error
  373. b.once.Do(func() {
  374. b.cancel()
  375. err = b.reader.Close()
  376. b.done()
  377. })
  378. return err
  379. }
  380. func headersToPlugin(headers http.Header) map[string]*pluginv1.HeaderValues {
  381. out := make(map[string]*pluginv1.HeaderValues, len(headers))
  382. for key, values := range headers {
  383. out[key] = &pluginv1.HeaderValues{Values: append([]string(nil), values...)}
  384. }
  385. return out
  386. }
  387. func headersFromPlugin(headers map[string]*pluginv1.HeaderValues) http.Header {
  388. out := make(http.Header, len(headers))
  389. for key, values := range headers {
  390. if values != nil {
  391. out[key] = append([]string(nil), values.Values...)
  392. }
  393. }
  394. return out
  395. }