commit e9d29f9713221c509dbef8144a4934739df3b9b2 Author: handsomezhuzhu <2658601135@qq.com> Date: Fri Jan 16 01:55:32 2026 +0800 init diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ba39f62 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +# 阶段一: 编译 +FROM golang:1.21-alpine AS builder +WORKDIR /app +COPY main.go . + +# 编译 Go 程序,使用 CGO_ENABLED=0 生成静态链接的可执行文件 +RUN go build -ldflags "-s -w" -o api-proxy main.go + +# 阶段二: 运行 +FROM alpine:latest +WORKDIR /app +# 从编译阶段复制可执行文件 +COPY --from=builder /app/api-proxy . + +# 暴露您的程序监听端口 (假设您在 main.go 中设置为 8080) +EXPOSE 7890 + +# 定义容器启动命令 +CMD ["./api-proxy"] + + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ca3d85f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3.8' +services: + api-proxy: + # 指向 Dockerfile 的路径,"." 表示当前部署目录 + build: . + container_name: api-proxy_service + # 如果您在 main.go 中配置的端口是 7890,那么这里保持 7890 + ports: + - "7890:7890" # 映射:将主机的 80 端口映射到容器内的 7890 端口 + restart: always \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..cd6b930 --- /dev/null +++ b/main.go @@ -0,0 +1,304 @@ +package main + +import ( + "fmt" + "io" + "log" + "net/http" + "os" + "sort" + "strings" +) + +var apiMapping = map[string]string{ + "/discord": "https://discord.com/api", + "/telegram": "https://api.telegram.org", + "/openai": "https://api.openai.com", + "/claude": "https://api.anthropic.com", + "/gemini": "https://generativelanguage.googleapis.com", + "/meta": "https://www.meta.ai/api", + "/groq": "https://api.groq.com/openai", + "/xai": "https://api.x.ai", + "/cohere": "https://api.cohere.ai", + "/huggingface": "https://api-inference.huggingface.co", + "/together": "https://api.together.xyz", + "/novita": "https://api.novita.ai", + "/portkey": "https://api.portkey.ai", + "/fireworks": "https://api.fireworks.ai", + "/openrouter": "https://openrouter.ai/api", + "/cerebras": "https://api.cerebras.ai", +} + +var deniedHeaders = []string{"host", "referer", "cf-", "forward", "cdn"} + +func isAllowedHeader(key string) bool { + for _, deniedHeader := range deniedHeaders { + if strings.Contains(strings.ToLower(key), deniedHeader) { + return false + } + } + return true +} + +func targetURL(pathname string) string { + split := strings.Index(pathname[1:], "/") + prefix := pathname[:split+1] + if base, exists := apiMapping[prefix]; exists { + return base + pathname[len(prefix):] + } + return "" +} + +func handler(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" || r.URL.Path == "/index.html" { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) + + var paths []string + for k := range apiMapping { + paths = append(paths, k) + } + sort.Strings(paths) + + html := ` + + + + + AI API Proxy + + + +
+

AI API Proxy Service

+

+ Maintained by Simon +

+
+ + Service is active and running +
+

This service routes requests to various AI provider APIs through a unified interface.

+ +

Available Endpoints

+
+ + + + + + + + ` + for _, path := range paths { + target := apiMapping[path] + html += fmt.Sprintf("", path, target) + } + html += `
Path PrefixTarget Service URL
%s%s
+ +
+ +` + fmt.Fprint(w, html) + return + } + + if r.URL.Path == "/robots.txt" { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, "User-agent: *\nDisallow: /") + return + } + + query := r.URL.RawQuery + + if query != "" { + query = "?" + query + } + + targetURL := targetURL(r.URL.Path + query) + + if targetURL == "" { + http.Error(w, "Not Found", http.StatusNotFound) + return + } + + // Create new request + client := &http.Client{} + proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body) + if err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + for key, values := range r.Header { + if isAllowedHeader(key) { + for _, value := range values { + proxyReq.Header.Add(key, value) + } + } + } + + // Make the request + resp, err := client.Do(proxyReq) + if err != nil { + log.Printf("Failed to fetch: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + defer resp.Body.Close() + + // Copy response headers + for key, values := range resp.Header { + for _, value := range values { + w.Header().Add(key, value) + } + } + + // Set security headers + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("Referrer-Policy", "no-referrer") + + // Set status code + w.WriteHeader(resp.StatusCode) + + // Copy response body + _, err = io.Copy(w, resp.Body) + if err != nil { + log.Printf("Error copying response: %v", err) + } +} + +func main() { + port := "7890" + if len(os.Args) > 1 { + port = os.Args[1] + } + http.HandleFunc("/", handler) + log.Printf("Starting server on :" + port) + if err := http.ListenAndServe(":"+port, nil); err != nil { + log.Fatal(err) + } +}