- Add flexible URL template configuration via environment variables - Implement automatic repository path parsing and extraction - Add 404 response handling for all non-200 status codes - Support both SecNex and GitHub URL formats through URL template - Include comprehensive documentation and Docker support - Add proper Go project structure with .gitignore 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
151 lines
3.6 KiB
Go
151 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
|
|
"git.secnex.io/secnex/masterlog"
|
|
)
|
|
|
|
type Config struct {
|
|
Port string
|
|
Url string
|
|
}
|
|
|
|
func main() {
|
|
config := loadConfig()
|
|
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
proxyHandler(w, r, config)
|
|
})
|
|
|
|
masterlog.Info("Starting server", map[string]interface{}{
|
|
"port": config.Port,
|
|
"url": config.Url,
|
|
})
|
|
|
|
if err := http.ListenAndServe(config.Port, nil); err != nil {
|
|
masterlog.Error("Error starting server", map[string]interface{}{"error": err})
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func loadConfig() *Config {
|
|
config := &Config{
|
|
Url: getEnv("URL", "https://git.secnex.io/secnex/%s/raw/branch/main/%s"),
|
|
Port: getEnv("PORT", ":8080"),
|
|
}
|
|
|
|
return config
|
|
}
|
|
|
|
func getEnv(key, defaultValue string) string {
|
|
if value := os.Getenv(key); value != "" {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func proxyHandler(w http.ResponseWriter, r *http.Request, config *Config) {
|
|
// Only handle GET requests
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
// Extract repository and file path from URL
|
|
// Expected format: /repositoryName/path/to/file
|
|
cleanPath := path.Clean(r.URL.Path)
|
|
if cleanPath == "/" || cleanPath == "" {
|
|
http.Error(w, "Repository name required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
pathParts := strings.Split(strings.TrimPrefix(cleanPath, "/"), "/")
|
|
if len(pathParts) < 1 {
|
|
http.Error(w, "Invalid path format", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
repository := pathParts[0]
|
|
filePath := strings.Join(pathParts[1:], "/")
|
|
|
|
// Log the incoming request
|
|
masterlog.Info("Incoming request", map[string]interface{}{
|
|
"method": r.Method,
|
|
"repository": repository,
|
|
"file_path": filePath,
|
|
"original_path": r.URL.Path,
|
|
})
|
|
|
|
// Build target URL based on server type
|
|
var targetURL string
|
|
targetURL = fmt.Sprintf(config.Url, repository, filePath)
|
|
|
|
// Create HTTP client with timeout
|
|
client := &http.Client{}
|
|
req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, targetURL, nil)
|
|
if err != nil {
|
|
masterlog.Error("Error creating request", map[string]interface{}{"error": err})
|
|
http.Error(w, "Failed to create request", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Copy headers from original request
|
|
for key, values := range r.Header {
|
|
// Skip certain headers that shouldn't be forwarded
|
|
if strings.ToLower(key) == "host" {
|
|
continue
|
|
}
|
|
for _, value := range values {
|
|
req.Header.Add(key, value)
|
|
}
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
masterlog.Error("Error fetching target URL", map[string]interface{}{"error": err, "url": targetURL})
|
|
http.Error(w, "Not Found", http.StatusNotFound)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Check if response is successful
|
|
if resp.StatusCode != http.StatusOK {
|
|
masterlog.Info("Target returned non-200 status", map[string]interface{}{
|
|
"url": targetURL,
|
|
"status_code": resp.StatusCode,
|
|
})
|
|
http.Error(w, "Not Found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Copy headers from target response
|
|
for key, values := range resp.Header {
|
|
for _, value := range values {
|
|
w.Header().Add(key, value)
|
|
}
|
|
}
|
|
|
|
// Set status code to 200
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
// Copy the response body
|
|
_, err = io.Copy(w, resp.Body)
|
|
if err != nil {
|
|
masterlog.Error("Error copying response", map[string]interface{}{"error": err})
|
|
// Can't send error response after headers have been written
|
|
return
|
|
}
|
|
|
|
masterlog.Info("Successfully proxied request", map[string]interface{}{
|
|
"url": targetURL,
|
|
"status_code": http.StatusOK,
|
|
"content_length": resp.ContentLength,
|
|
})
|
|
}
|