This commit is contained in:
@@ -2,6 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
@@ -90,7 +91,49 @@ type searchDebugSummary struct {
|
||||
GeminiCandidateCap int `json:"geminiCandidateCap,omitempty"`
|
||||
}
|
||||
|
||||
type debugResponseWriter struct {
|
||||
gin.ResponseWriter
|
||||
body bytes.Buffer
|
||||
}
|
||||
|
||||
func (w *debugResponseWriter) Write(data []byte) (int, error) {
|
||||
if w.body.Len() < 4096 {
|
||||
remaining := 4096 - w.body.Len()
|
||||
if len(data) > remaining {
|
||||
w.body.Write(data[:remaining])
|
||||
} else {
|
||||
w.body.Write(data)
|
||||
}
|
||||
}
|
||||
return w.ResponseWriter.Write(data)
|
||||
}
|
||||
|
||||
func RegisterRoutes(router *gin.Engine, app *App) {
|
||||
router.Use(func(c *gin.Context) {
|
||||
started := time.Now()
|
||||
var requestBody string
|
||||
if c.Request.Body != nil && (c.Request.Method == http.MethodPost || c.Request.Method == http.MethodPut || c.Request.Method == http.MethodPatch) {
|
||||
raw, _ := io.ReadAll(io.LimitReader(c.Request.Body, 4096))
|
||||
requestBody = string(raw)
|
||||
c.Request.Body = io.NopCloser(bytes.NewReader(raw))
|
||||
}
|
||||
writer := &debugResponseWriter{ResponseWriter: c.Writer}
|
||||
c.Writer = writer
|
||||
app.debug("http:request:start", gin.H{
|
||||
"method": c.Request.Method,
|
||||
"path": c.Request.URL.Path,
|
||||
"query": c.Request.URL.RawQuery,
|
||||
"body": truncateText(strings.TrimSpace(requestBody), 4000),
|
||||
})
|
||||
c.Next()
|
||||
app.debug("http:request:done", gin.H{
|
||||
"method": c.Request.Method,
|
||||
"path": c.Request.URL.Path,
|
||||
"status": c.Writer.Status(),
|
||||
"durationMs": time.Since(started).Milliseconds(),
|
||||
"response": truncateText(strings.TrimSpace(writer.body.String()), 4000),
|
||||
})
|
||||
})
|
||||
router.GET("/healthz", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||||
})
|
||||
@@ -104,6 +147,9 @@ func RegisterRoutes(router *gin.Engine, app *App) {
|
||||
}
|
||||
|
||||
func (a *App) debug(message string, data any) {
|
||||
if payload, err := json.Marshal(data); err == nil {
|
||||
fmt.Printf("[debug] %s %s\n", message, string(payload))
|
||||
}
|
||||
a.Hub.Broadcast("debug", gin.H{"message": message, "data": data})
|
||||
}
|
||||
|
||||
@@ -154,20 +200,24 @@ func (a *App) streamPreview(c *gin.Context) {
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36")
|
||||
req.Header.Set("Accept-Language", "en-US,en;q=0.9")
|
||||
req.Header.Set("Referer", inferPreviewReferer(target))
|
||||
a.debug("preview:proxy:start", gin.H{"target": target, "referer": req.Header.Get("Referer")})
|
||||
|
||||
resp, err := a.SearchService.Client.Do(req)
|
||||
if err != nil {
|
||||
a.debug("preview:proxy:error", gin.H{"target": target, "error": err.Error()})
|
||||
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
a.debug("preview:proxy:bad_status", gin.H{"target": target, "status": resp.StatusCode})
|
||||
c.JSON(http.StatusBadGateway, gin.H{"error": fmt.Sprintf("preview source returned %d", resp.StatusCode)})
|
||||
return
|
||||
}
|
||||
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
a.debug("preview:proxy:response", gin.H{"target": target, "contentType": contentType, "status": resp.StatusCode})
|
||||
if strings.Contains(strings.ToLower(target), ".m3u8") || strings.Contains(strings.ToLower(contentType), "mpegurl") {
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 2*1024*1024))
|
||||
if err != nil {
|
||||
@@ -175,6 +225,7 @@ func (a *App) streamPreview(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
rewritten := rewriteM3U8Playlist(string(body), target)
|
||||
a.debug("preview:proxy:hls_rewritten", gin.H{"target": target, "bytes": len(body)})
|
||||
c.Header("Content-Type", "application/vnd.apple.mpegurl")
|
||||
c.String(http.StatusOK, rewritten)
|
||||
return
|
||||
@@ -182,9 +233,11 @@ func (a *App) streamPreview(c *gin.Context) {
|
||||
|
||||
if isCacheablePreview(target, contentType) {
|
||||
if cachedPath, err := a.cachePreviewResponse(target, contentType, resp.Body); err == nil {
|
||||
a.debug("preview:proxy:cache_hit_write", gin.H{"target": target, "cachedPath": cachedPath})
|
||||
c.File(cachedPath)
|
||||
return
|
||||
}
|
||||
a.debug("preview:proxy:cache_write_error", gin.H{"target": target, "error": err.Error()})
|
||||
}
|
||||
|
||||
if contentType != "" {
|
||||
@@ -673,6 +726,9 @@ func (a *App) cachePreviewResponse(target, contentType string, body io.Reader) (
|
||||
ext = ".mp4"
|
||||
}
|
||||
path := filepath.Join(a.PreviewCacheDir, fmt.Sprintf("%x%s", sum, ext))
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path, nil
|
||||
}
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
Reference in New Issue
Block a user