Created
September 17, 2025 12:49
-
-
Save mstrYoda/2eb0d5032888204f5c39d6683819faa8 to your computer and use it in GitHub Desktop.
An example http handler and proxy for mobile applications to protect their LLM keys.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "bytes" | |
| "flag" | |
| "io" | |
| "log" | |
| "net/http" | |
| "net/url" | |
| "time" | |
| ) | |
| var ( | |
| // Command line flags | |
| listenAddr = flag.String("addr", ":8080", "TCP address to listen on") | |
| targetURL = flag.String("target", "", "Target URL to proxy requests to (required)") | |
| apiKey = flag.String("api-key", "", "API key to inject for LLM provider") | |
| authType = flag.String("auth-type", "bearer", "Authentication type: bearer, api-key, custom") | |
| authHeader = flag.String("auth-header", "Authorization", "Header name for API key injection") | |
| proxyKey = flag.String("proxy-key", "", "Optional key required to access this proxy") | |
| verbose = flag.Bool("verbose", false, "Enable verbose request/response logging") | |
| ) | |
| func main() { | |
| flag.Parse() | |
| if *targetURL == "" { | |
| log.Fatal("Target URL is required. Use -target flag to specify it.") | |
| } | |
| if *apiKey == "" { | |
| log.Fatal("API key is required for LLM provider access. Use -api-key flag.") | |
| } | |
| // Parse and validate target URL | |
| targetU, err := url.Parse(*targetURL) | |
| if err != nil { | |
| log.Fatalf("Invalid target URL: %v", err) | |
| } | |
| if targetU.Host == "" { | |
| log.Fatal("Target URL must include host (e.g., https://api.groq.com)") | |
| } | |
| log.Printf("Starting LLM proxy server on %s", *listenAddr) | |
| log.Printf("Proxying to: %s", *targetURL) | |
| log.Printf("Auth type: %s", *authType) | |
| if *proxyKey != "" { | |
| log.Printf("Proxy authentication: ENABLED") | |
| } | |
| // Create HTTP server | |
| server := &http.Server{ | |
| Addr: *listenAddr, | |
| Handler: createProxyHandler(targetU), | |
| ReadTimeout: 30 * time.Second, | |
| WriteTimeout: 30 * time.Second, | |
| } | |
| log.Printf("π LLM Proxy ready! Send requests to: http://localhost%s", *listenAddr) | |
| if err := server.ListenAndServe(); err != nil { | |
| log.Fatalf("Error starting server: %v", err) | |
| } | |
| } | |
| func createProxyHandler(target *url.URL) http.HandlerFunc { | |
| // Create HTTP client with standard settings (like curl) | |
| client := &http.Client{ | |
| Timeout: 5 * time.Minute, | |
| Transport: &http.Transport{ | |
| DisableKeepAlives: false, | |
| MaxIdleConns: 100, | |
| MaxIdleConnsPerHost: 10, | |
| IdleConnTimeout: 90 * time.Second, | |
| }, | |
| } | |
| return func(w http.ResponseWriter, r *http.Request) { | |
| // Check proxy authentication if required | |
| if *proxyKey != "" { | |
| proxyAuth := r.Header.Get("X-Proxy-Auth") | |
| if proxyAuth != *proxyKey { | |
| http.Error(w, "Unauthorized: Invalid proxy key", http.StatusUnauthorized) | |
| log.Printf("β Unauthorized access attempt from %s", r.RemoteAddr) | |
| return | |
| } | |
| } | |
| // Log original request | |
| if *verbose { | |
| log.Printf("π€ Request: %s %s", r.Method, r.URL.Path) | |
| } | |
| // Build target URL | |
| targetURL := &url.URL{ | |
| Scheme: target.Scheme, | |
| Host: target.Host, | |
| Path: r.URL.Path, | |
| } | |
| // Handle query parameters | |
| if r.URL.RawQuery != "" { | |
| targetURL.RawQuery = r.URL.RawQuery | |
| } | |
| // Read request body | |
| var body io.Reader | |
| if r.Body != nil { | |
| bodyBytes, err := io.ReadAll(r.Body) | |
| if err != nil { | |
| http.Error(w, "Error reading request body", http.StatusBadRequest) | |
| return | |
| } | |
| body = bytes.NewReader(bodyBytes) | |
| if *verbose && len(bodyBytes) > 0 { | |
| log.Printf("π€ Request Body: %s", string(bodyBytes)) | |
| } | |
| } | |
| // Create new request | |
| req, err := http.NewRequest(r.Method, targetURL.String(), body) | |
| if err != nil { | |
| http.Error(w, "Error creating request", http.StatusInternalServerError) | |
| return | |
| } | |
| // Copy headers from original request (except proxy auth) | |
| for name, values := range r.Header { | |
| if name == "X-Proxy-Auth" { | |
| continue | |
| } | |
| for _, value := range values { | |
| req.Header.Add(name, value) | |
| } | |
| } | |
| // Set the Host header to target host | |
| req.Host = target.Host | |
| req.Header.Set("Host", target.Host) | |
| // Inject API key | |
| switch *authType { | |
| case "bearer": | |
| req.Header.Set(*authHeader, "Bearer "+*apiKey) | |
| case "api-key", "api_key": | |
| req.Header.Set(*authHeader, *apiKey) | |
| case "custom": | |
| req.Header.Set(*authHeader, *apiKey) | |
| default: | |
| req.Header.Set(*authHeader, "Bearer "+*apiKey) | |
| } | |
| // Remove any proxy-identifying headers | |
| req.Header.Del("X-Forwarded-For") | |
| req.Header.Del("X-Forwarded-Host") | |
| req.Header.Del("X-Forwarded-Proto") | |
| req.Header.Del("X-Real-IP") | |
| req.Header.Del("X-Real-Ip") | |
| if *verbose { | |
| log.Printf("π€ Sending to: %s", targetURL.String()) | |
| log.Printf("π€ Headers:") | |
| for name, values := range req.Header { | |
| for _, value := range values { | |
| log.Printf(" %s: %s", name, value) | |
| } | |
| } | |
| } | |
| // Make the request | |
| resp, err := client.Do(req) | |
| if err != nil { | |
| log.Printf("β Error proxying request to %s: %v", target.Host, err) | |
| http.Error(w, "Bad Gateway", http.StatusBadGateway) | |
| return | |
| } | |
| defer resp.Body.Close() | |
| // Copy response status | |
| w.WriteHeader(resp.StatusCode) | |
| // Copy response headers | |
| for name, values := range resp.Header { | |
| for _, value := range values { | |
| w.Header().Add(name, value) | |
| } | |
| } | |
| // Copy response body | |
| _, err = io.Copy(w, resp.Body) | |
| if err != nil { | |
| log.Printf("β Error copying response: %v", err) | |
| return | |
| } | |
| // Log response | |
| statusEmoji := "β " | |
| if resp.StatusCode >= 400 { | |
| statusEmoji = "β" | |
| } | |
| log.Printf("%s %s %s -> %d", statusEmoji, r.Method, r.URL.Path, resp.StatusCode) | |
| if *verbose { | |
| log.Printf("π₯ Response: %d %s", resp.StatusCode, resp.Status) | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment