diff --git a/src/main.go b/src/main.go index 7f6e5d6..b222506 100644 --- a/src/main.go +++ b/src/main.go @@ -209,18 +209,36 @@ func proxyWithRedirect(c *gin.Context, u string, redirectCount int) { realHost = "https://" + realHost } + // 检查是否为gzip压缩内容 + isGzipCompressed := resp.Header.Get("Content-Encoding") == "gzip" + // 使用智能处理器自动处理所有内容 - processedBody, processedSize, err := ProcessSmart(resp.Body, resp.Header.Get("Content-Encoding") == "gzip", realHost) + processedBody, processedSize, err := ProcessSmart(resp.Body, isGzipCompressed, realHost) if err != nil { - // 优雅降级 - 处理失败时直接返回原内容 - c.String(http.StatusInternalServerError, fmt.Sprintf("内容处理错误: %v", err)) + // 优雅降级 - 处理失败时使用直接代理模式 + fmt.Printf("智能处理失败,回退到直接代理: %v\n", err) + + // 复制原始响应头 + for key, values := range resp.Header { + for _, value := range values { + c.Header(key, value) + } + } + + c.Status(resp.StatusCode) + + // 直接转发原始内容 + if _, err := io.Copy(c.Writer, resp.Body); err != nil { + fmt.Printf("直接代理模式复制内容失败: %v\n", err) + } return } // 智能设置响应头 if processedSize > 0 { - // 内容被处理过,使用chunked传输以获得最佳性能 + // 内容被处理过,清理压缩相关头,使用chunked传输 resp.Header.Del("Content-Length") + resp.Header.Del("Content-Encoding") // 重要:清理压缩头,防止浏览器重复解压 resp.Header.Set("Transfer-Encoding", "chunked") } diff --git a/src/proxysh.go b/src/proxysh.go index 3a06ffa..587574c 100644 --- a/src/proxysh.go +++ b/src/proxysh.go @@ -95,8 +95,12 @@ func ProcessSmart(input io.ReadCloser, isCompressed bool, host string) (io.Reade // 快速读取内容 content, err := processor.readContent(input, isCompressed) - if err != nil || len(content) == 0 { - // 优雅降级:返回空读取器而不是已消费的input + if err != nil { + // 优雅降级:读取错误时返回错误,让上层处理 + return nil, 0, fmt.Errorf("内容读取失败: %v", err) + } + if len(content) == 0 { + // 空内容,返回空读取器 return strings.NewReader(""), 0, nil } @@ -209,18 +213,6 @@ func (sp *SmartProcessor) isBinaryContent(content string) bool { func (sp *SmartProcessor) readContent(input io.ReadCloser, isCompressed bool) (string, error) { defer input.Close() - var reader io.Reader = input - - // 压缩处理 - if isCompressed { - gzReader, err := gzip.NewReader(input) - if err != nil { - return "", err - } - defer gzReader.Close() - reader = gzReader - } - // 使用缓冲池 buffer := sp.bufferPool.Get().([]byte) defer sp.bufferPool.Put(buffer) @@ -228,6 +220,36 @@ func (sp *SmartProcessor) readContent(input io.ReadCloser, isCompressed bool) (s var result strings.Builder result.Grow(SMART_BUFFER_SIZE) // 预分配 + var reader io.Reader = input + var gzReader *gzip.Reader + + // 智能压缩处理 - 防止双重解压 + if isCompressed { + // 先读取一小部分数据来检测是否真的是gzip格式 + peek := make([]byte, 2) + n, err := input.Read(peek) + if err != nil && err != io.EOF { + return "", fmt.Errorf("读取数据失败: %v", err) + } + + // 检查gzip魔数 (0x1f, 0x8b) + if n >= 2 && peek[0] == 0x1f && peek[1] == 0x8b { + // 确认是gzip格式,创建MultiReader组合peek数据和剩余数据 + combinedReader := io.MultiReader(bytes.NewReader(peek[:n]), input) + gzReader, err = gzip.NewReader(combinedReader) + if err != nil { + return "", fmt.Errorf("gzip解压失败: %v", err) + } + defer gzReader.Close() + reader = gzReader + } else { + // 不是gzip格式或者HTTP客户端已经解压,使用原始数据 + combinedReader := io.MultiReader(bytes.NewReader(peek[:n]), input) + reader = combinedReader + } + } + + // 读取内容 for { n, err := reader.Read(buffer) if n > 0 { @@ -237,7 +259,7 @@ func (sp *SmartProcessor) readContent(input io.ReadCloser, isCompressed bool) (s break } if err != nil { - return "", err + return "", fmt.Errorf("读取内容失败: %v", err) } // 大文件保护 diff --git a/src/public/index.html b/src/public/index.html index f5fc353..6417d5a 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -363,6 +363,15 @@ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } + /* 黑夜模式下的Docker按钮样式 */ + .dark .docker-button { + background: linear-gradient(135deg, #374151, #4b5563); + } + + .dark .docker-button:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + } + /* 模态框设计 */ .modal { position: fixed; diff --git a/src/public/search.html b/src/public/search.html index cb8b3dd..18ba8ed 100644 --- a/src/public/search.html +++ b/src/public/search.html @@ -855,14 +855,14 @@ elements.backToSearch.classList.remove('show'); try { - const response = await fetch(`/api/search?q=${encodeURIComponent(term)}&page=${page}`); + const response = await fetch(`/search?q=${encodeURIComponent(term)}&page=${page}`); const data = await response.json(); - if (data.success) { + if (data && data.results) { searchResults = data.results; - displaySearchResults(data.results, data.total, page); + displaySearchResults(data.results, data.count, page); } else { - showToast('搜索失败:' + (data.message || '未知错误'), 'error'); + showToast('搜索失败:' + (data.error || '未知错误'), 'error'); } } catch (error) { console.error('搜索错误:', error); @@ -894,7 +894,7 @@ function createResultCard(result) { const card = document.createElement('div'); card.className = 'result-card'; - card.onclick = () => viewImageTags(result.name); + card.onclick = () => viewImageTags(result.repo_name || result.name); const badges = []; if (result.is_official) badges.push('官方'); @@ -902,21 +902,21 @@ card.innerHTML = `