25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 

262 satır
6.5 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. }
  99. func (cw *captureResponseWriter) Write(b []byte) (int, error) {
  100. n, err := cw.ResponseWriter.Write(b)
  101. if n > 0 {
  102. cw.collector.Add(b[:n])
  103. }
  104. return n, err
  105. }
  106. func (cw *captureResponseWriter) WriteString(s string) (int, error) {
  107. n, err := cw.ResponseWriter.WriteString(s)
  108. if n > 0 {
  109. cw.collector.Add([]byte(s[:n]))
  110. }
  111. return n, err
  112. }
  113. func normalizeCaptureRequestID(requestID string) string {
  114. if strings.TrimSpace(requestID) != "" {
  115. return requestID
  116. }
  117. return fmt.Sprintf("capture-%d", time.Now().UnixNano())
  118. }
  119. func createCaptureFile(requestID string) (*os.File, error) {
  120. dir := filepath.Join("./data", "relay-capture", time.Now().Format("2006-01-02"))
  121. if err := os.MkdirAll(dir, 0755); err != nil {
  122. return nil, err
  123. }
  124. return os.OpenFile(
  125. filepath.Join(dir, normalizeCaptureRequestID(requestID)+".json"),
  126. os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644,
  127. )
  128. }
  129. func isSensitiveCaptureHeader(key string) bool {
  130. switch strings.ToLower(key) {
  131. case "authorization", "proxy-authorization", "x-api-key", "api-key", "cookie":
  132. return true
  133. default:
  134. return false
  135. }
  136. }
  137. func copyHeadersForCapture(headers http.Header, redactSensitive bool) map[string][]string {
  138. copied := make(map[string][]string, len(headers))
  139. for key, values := range headers {
  140. copiedValues := make([]string, len(values))
  141. if redactSensitive && isSensitiveCaptureHeader(key) {
  142. for i := range copiedValues {
  143. copiedValues[i] = "[REDACTED]"
  144. }
  145. } else {
  146. copy(copiedValues, values)
  147. }
  148. copied[key] = copiedValues
  149. }
  150. return copied
  151. }
  152. func buildCaptureRequestRecord(c *gin.Context, captureErrors *[]string) relayCaptureRequestRecord {
  153. record := relayCaptureRequestRecord{
  154. Method: c.Request.Method,
  155. Path: c.Request.URL.Path,
  156. Query: c.Request.URL.RawQuery,
  157. Headers: copyHeadersForCapture(c.Request.Header, true),
  158. Body: "",
  159. }
  160. if c.Request.Body == nil {
  161. return record
  162. }
  163. storage, err := common.GetBodyStorage(c)
  164. if err != nil {
  165. *captureErrors = append(*captureErrors, "failed to read request body: "+err.Error())
  166. return record
  167. }
  168. body, err := storage.Bytes()
  169. if err != nil {
  170. *captureErrors = append(*captureErrors, "failed to read request body bytes: "+err.Error())
  171. return record
  172. }
  173. record.Body = string(body)
  174. return record
  175. }
  176. func buildCaptureResponseRecord(c *gin.Context, collector *responseCaptureCollector) relayCaptureResponseRecord {
  177. return relayCaptureResponseRecord{
  178. StatusCode: c.Writer.Status(),
  179. Headers: copyHeadersForCapture(c.Writer.Header(), false),
  180. Body: collector.Body(),
  181. BodyChunks: collector.Chunks(),
  182. }
  183. }
  184. func writeCaptureRecord(requestID string, record relayCaptureRecord) error {
  185. f, err := createCaptureFile(requestID)
  186. if err != nil {
  187. return err
  188. }
  189. defer f.Close()
  190. data, err := common.Marshal(record)
  191. if err != nil {
  192. return err
  193. }
  194. _, err = f.Write(data)
  195. return err
  196. }
  197. func RelayCaptureMiddleware() gin.HandlerFunc {
  198. return func(c *gin.Context) {
  199. userID := int64(c.GetInt("id"))
  200. if !isCaptureEnabled(userID) {
  201. c.Next()
  202. return
  203. }
  204. start := time.Now()
  205. requestID := normalizeCaptureRequestID(c.GetString(common.RequestIdKey))
  206. captureErrors := make([]string, 0)
  207. requestRecord := buildCaptureRequestRecord(c, &captureErrors)
  208. collector := newResponseCaptureCollector()
  209. c.Writer = &captureResponseWriter{
  210. ResponseWriter: c.Writer,
  211. collector: collector,
  212. }
  213. c.Next()
  214. record := relayCaptureRecord{
  215. RequestID: requestID,
  216. CapturedAt: start.UTC().Format(time.RFC3339Nano),
  217. DurationMs: time.Since(start).Milliseconds(),
  218. UserID: userID,
  219. Request: requestRecord,
  220. Response: buildCaptureResponseRecord(c, collector),
  221. CaptureErrors: captureErrors,
  222. }
  223. if err := writeCaptureRecord(requestID, record); err != nil {
  224. common.SysError("relay capture: failed to write json capture, request_id=" + requestID + ", error=" + err.Error())
  225. }
  226. }
  227. }