Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

303 linhas
7.3 KiB

  1. package middleware
  2. import (
  3. "bytes"
  4. "fmt"
  5. "net/http"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "sync"
  10. "time"
  11. "github.com/QuantumNous/new-api/common"
  12. "github.com/QuantumNous/new-api/model"
  13. "github.com/gin-gonic/gin"
  14. )
  15. var captureEnabledUsers sync.Map // key: int64, value: struct{}
  16. func isCaptureEnabled(userID int64) bool {
  17. _, ok := captureEnabledUsers.Load(userID)
  18. return ok
  19. }
  20. func SetCaptureEnabled(userID int64, enabled bool) {
  21. if enabled {
  22. captureEnabledUsers.Store(userID, struct{}{})
  23. } else {
  24. captureEnabledUsers.Delete(userID)
  25. }
  26. }
  27. func LoadCaptureEnabledUsers() error {
  28. var ids []int64
  29. if err := model.DB.Model(&model.User{}).
  30. Where("capture_relay = ?", true).
  31. Pluck("id", &ids).Error; err != nil {
  32. return err
  33. }
  34. for _, id := range ids {
  35. captureEnabledUsers.Store(id, struct{}{})
  36. }
  37. return nil
  38. }
  39. func GetCaptureEnabledUsersCount() int {
  40. count := 0
  41. captureEnabledUsers.Range(func(key, value interface{}) bool {
  42. count++
  43. return true
  44. })
  45. return count
  46. }
  47. type relayCaptureRecord struct {
  48. RequestID string `json:"request_id"`
  49. CapturedAt string `json:"captured_at"`
  50. DurationMs int64 `json:"duration_ms"`
  51. UserID int64 `json:"user_id"`
  52. Request relayCaptureRequestRecord `json:"request"`
  53. Response relayCaptureResponseRecord `json:"response"`
  54. CaptureErrors []string `json:"capture_errors"`
  55. }
  56. type relayCaptureRequestRecord struct {
  57. Method string `json:"method"`
  58. Path string `json:"path"`
  59. Query string `json:"query"`
  60. Headers map[string][]string `json:"headers"`
  61. Body string `json:"body"`
  62. }
  63. type relayCaptureResponseRecord struct {
  64. StatusCode int `json:"status_code"`
  65. Headers map[string][]string `json:"headers"`
  66. Body string `json:"body"`
  67. BodyChunks []string `json:"body_chunks"`
  68. }
  69. type responseCaptureCollector struct {
  70. body bytes.Buffer
  71. chunks []string
  72. }
  73. func newResponseCaptureCollector() *responseCaptureCollector {
  74. return &responseCaptureCollector{
  75. chunks: make([]string, 0),
  76. }
  77. }
  78. func (collector *responseCaptureCollector) Add(chunk []byte) {
  79. if len(chunk) == 0 {
  80. return
  81. }
  82. copied := make([]byte, len(chunk))
  83. copy(copied, chunk)
  84. collector.body.Write(copied)
  85. collector.chunks = append(collector.chunks, string(copied))
  86. }
  87. func (collector *responseCaptureCollector) Body() string {
  88. return collector.body.String()
  89. }
  90. func (collector *responseCaptureCollector) Chunks() []string {
  91. chunks := make([]string, len(collector.chunks))
  92. copy(chunks, collector.chunks)
  93. return chunks
  94. }
  95. type captureResponseWriter struct {
  96. gin.ResponseWriter
  97. collector *responseCaptureCollector
  98. ch chan<- []byte
  99. }
  100. func (cw *captureResponseWriter) Write(b []byte) (int, error) {
  101. n, err := cw.ResponseWriter.Write(b)
  102. if n > 0 {
  103. chunk := b[:n]
  104. if cw.collector != nil {
  105. cw.collector.Add(chunk)
  106. }
  107. if cw.ch != nil {
  108. buf := make([]byte, n)
  109. copy(buf, chunk)
  110. cw.ch <- buf
  111. }
  112. }
  113. return n, err
  114. }
  115. func (cw *captureResponseWriter) WriteString(s string) (int, error) {
  116. n, err := cw.ResponseWriter.WriteString(s)
  117. if n > 0 {
  118. chunk := s[:n]
  119. if cw.collector != nil {
  120. cw.collector.Add([]byte(chunk))
  121. }
  122. if cw.ch != nil {
  123. buf := make([]byte, n)
  124. copy(buf, chunk)
  125. cw.ch <- buf
  126. }
  127. }
  128. return n, err
  129. }
  130. func startCaptureWriter(f *os.File, requestID string, start time.Time) (in chan<- []byte, done <-chan struct{}) {
  131. chIn := make(chan []byte, 128)
  132. doneCh := make(chan struct{})
  133. go func() {
  134. defer close(doneCh)
  135. defer f.Close()
  136. for chunk := range chIn {
  137. _, _ = f.Write(chunk)
  138. }
  139. _, _ = fmt.Fprintf(f, "\n=== END duration_ms=%d ===\n", time.Since(start).Milliseconds())
  140. }()
  141. return chIn, doneCh
  142. }
  143. func normalizeCaptureRequestID(requestID string) string {
  144. if strings.TrimSpace(requestID) != "" {
  145. return requestID
  146. }
  147. return fmt.Sprintf("capture-%d", time.Now().UnixNano())
  148. }
  149. func createCaptureFile(requestID string) (*os.File, error) {
  150. dir := filepath.Join("./data", "relay-capture", time.Now().Format("2006-01-02"))
  151. if err := os.MkdirAll(dir, 0755); err != nil {
  152. return nil, err
  153. }
  154. return os.OpenFile(
  155. filepath.Join(dir, normalizeCaptureRequestID(requestID)+".json"),
  156. os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644,
  157. )
  158. }
  159. func isSensitiveCaptureHeader(key string) bool {
  160. switch strings.ToLower(key) {
  161. case "authorization", "proxy-authorization", "x-api-key", "api-key", "cookie":
  162. return true
  163. default:
  164. return false
  165. }
  166. }
  167. func copyHeadersForCapture(headers http.Header, redactSensitive bool) map[string][]string {
  168. copied := make(map[string][]string, len(headers))
  169. for key, values := range headers {
  170. copiedValues := make([]string, len(values))
  171. if redactSensitive && isSensitiveCaptureHeader(key) {
  172. for i := range copiedValues {
  173. copiedValues[i] = "[REDACTED]"
  174. }
  175. } else {
  176. copy(copiedValues, values)
  177. }
  178. copied[key] = copiedValues
  179. }
  180. return copied
  181. }
  182. func buildCaptureRequestRecord(c *gin.Context, captureErrors *[]string) relayCaptureRequestRecord {
  183. record := relayCaptureRequestRecord{
  184. Method: c.Request.Method,
  185. Path: c.Request.URL.Path,
  186. Query: c.Request.URL.RawQuery,
  187. Headers: copyHeadersForCapture(c.Request.Header, true),
  188. Body: "",
  189. }
  190. if c.Request.Body == nil {
  191. return record
  192. }
  193. storage, err := common.GetBodyStorage(c)
  194. if err != nil {
  195. *captureErrors = append(*captureErrors, "failed to read request body: "+err.Error())
  196. return record
  197. }
  198. body, err := storage.Bytes()
  199. if err != nil {
  200. *captureErrors = append(*captureErrors, "failed to read request body bytes: "+err.Error())
  201. return record
  202. }
  203. record.Body = string(body)
  204. return record
  205. }
  206. func buildCaptureResponseRecord(c *gin.Context, collector *responseCaptureCollector) relayCaptureResponseRecord {
  207. return relayCaptureResponseRecord{
  208. StatusCode: c.Writer.Status(),
  209. Headers: copyHeadersForCapture(c.Writer.Header(), false),
  210. Body: collector.Body(),
  211. BodyChunks: collector.Chunks(),
  212. }
  213. }
  214. func writeRequestBlock(c *gin.Context, f *os.File, userID int64) {
  215. _, _ = fmt.Fprintf(f, "=== REQUEST %s ===\n", time.Now().UTC().Format(time.RFC3339Nano))
  216. _, _ = fmt.Fprintf(f, "%s %s\n", c.Request.Method, c.Request.URL.Path)
  217. _, _ = fmt.Fprintf(f, "user_id: %d\n", userID)
  218. for key, values := range c.Request.Header {
  219. for _, v := range values {
  220. _, _ = fmt.Fprintf(f, "%s: %s\n", key, v)
  221. }
  222. }
  223. _, _ = fmt.Fprintf(f, "\n")
  224. if c.Request.Body != nil {
  225. if storage, err := common.GetBodyStorage(c); err == nil {
  226. if body, err := storage.Bytes(); err == nil && len(body) > 0 {
  227. _, _ = f.Write(body)
  228. }
  229. }
  230. }
  231. _, _ = fmt.Fprintf(f, "\n")
  232. }
  233. func RelayCaptureMiddleware() gin.HandlerFunc {
  234. return func(c *gin.Context) {
  235. userID := int64(c.GetInt("id"))
  236. if !isCaptureEnabled(userID) {
  237. c.Next()
  238. return
  239. }
  240. start := time.Now()
  241. requestID := c.GetString(common.RequestIdKey)
  242. f, err := createCaptureFile(requestID)
  243. if err != nil {
  244. c.Next()
  245. return
  246. }
  247. writeRequestBlock(c, f, userID)
  248. _, _ = fmt.Fprintf(f, "\n=== RESPONSE ===\n")
  249. chIn, doneCh := startCaptureWriter(f, requestID, start)
  250. c.Writer = &captureResponseWriter{
  251. ResponseWriter: c.Writer,
  252. ch: chIn,
  253. }
  254. c.Next()
  255. close(chIn)
  256. select {
  257. case <-doneCh:
  258. case <-time.After(30 * time.Second):
  259. common.SysError("relay capture: timeout waiting for writer goroutine, request_id=" + requestID)
  260. }
  261. }
  262. }