first commit

This commit is contained in:
2026-04-17 11:56:39 +07:00
commit 2a79993f43
506 changed files with 342249 additions and 0 deletions
+269
View File
@@ -0,0 +1,269 @@
package proxymap
import (
"crypto/tls"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"acme-reverseproxy/nfs"
"github.com/sirupsen/logrus"
)
// stripHostPort возвращает только имя хоста, если в строке был порт.
func stripHostPort(host string) string {
h, _, err := net.SplitHostPort(host)
if err != nil {
return host
}
return h
}
// tlsServerName задаёт SNI для исходящего TLS к бэкенду.
// При подключении по IP многие серверы отклоняют SNI с IP (tls: unrecognized name);
// в этом случае используем публичное имя из ключа маппинга — как правило, оно совпадает с CN/SAN сертификата бэкенда.
func tlsServerName(frontendKey string, u *url.URL) string {
backend := u.Hostname()
if ip := net.ParseIP(backend); ip != nil {
return stripHostPort(frontendKey)
}
return backend
}
func ToReverseProxyMap(m map[string]string) (ReverseProxyMap, error) {
rpm := ReverseProxyMap{}
for k, v := range m {
rpURL, err := url.Parse(v)
if err != nil {
return nil, err
}
proxy := httputil.NewSingleHostReverseProxy(rpURL)
// Настройка транспорта для поддержки HTTP и HTTPS бэкендов
if rpURL.Scheme == "https" {
sn := tlsServerName(k, rpURL)
logrus.Infof("Configuring HTTPS transport for backend: %s (TLS ServerName/SNI: %s)", rpURL.String(), sn)
// Создаем транспорт с поддержкой TLS для HTTPS бэкендов
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
ServerName: sn,
InsecureSkipVerify: false,
},
// Настройки подключения
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
DisableCompression: true,
ForceAttemptHTTP2: true,
}
proxy.Transport = transport
} else {
logrus.Debugf("Configuring HTTP transport for backend: %s", rpURL.String())
// Для HTTP используем стандартный транспорт
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
// Настройки подключения
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
}
proxy.Transport = transport
}
// Настройка директив для правильной работы прокси
originalDirector := proxy.Director
proxy.Director = func(req *http.Request) {
originalDirector(req)
// httputil.NewSingleHostReverseProxy уже правильно устанавливает Host и URL
// Логируем проксирование
logrus.Infof("Proxying request: %s %s -> %s%s (Request Host: %s, Target: %s, Scheme: %s)",
req.Method, req.URL.Path, rpURL.Scheme, req.URL.Path, req.Host, rpURL.Host, rpURL.Scheme)
}
// Обработка ошибок проксирования
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
logrus.Errorf("Reverse proxy error for %s -> %s: %v", r.Host, rpURL.String(), err)
http.Error(w, "Bad Gateway", http.StatusBadGateway)
}
// Логирование успешных ответов
originalModifyResponse := proxy.ModifyResponse
proxy.ModifyResponse = func(resp *http.Response) error {
if originalModifyResponse != nil {
if err := originalModifyResponse(resp); err != nil {
return err
}
}
logrus.Debugf("Proxy response: %s %s -> status %d",
resp.Request.Method, resp.Request.URL.Path, resp.StatusCode)
return nil
}
rpm[k] = proxy
logrus.Infof("Configured reverse proxy: %s -> %s (scheme: %s)", k, rpURL.String(), rpURL.Scheme)
}
return rpm, nil
}
type ReverseProxyMap map[string]http.Handler
func NewReverseProxiesHandler(rpm ReverseProxyMap) ReverseProxiesHandler {
return &reverseProxiesHandler{
Map: rpm,
NotFoundHandler: http.NotFoundHandler(),
WellKnownDir: "/tmp/.well-known",
}
}
func NewReverseProxiesHandlerWithWellKnown(rpm ReverseProxyMap, wellKnownDir string) ReverseProxiesHandler {
return &reverseProxiesHandler{
Map: rpm,
NotFoundHandler: http.NotFoundHandler(),
WellKnownDir: wellKnownDir,
}
}
type ReverseProxiesHandler interface {
http.Handler
}
type reverseProxiesHandler struct {
Map ReverseProxyMap
NotFoundHandler http.Handler
WellKnownDir string
}
func (rph reverseProxiesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
logrus.Infof("Incoming request: %s %s from %s (Host: %s)", r.Method, r.URL.Path, r.RemoteAddr, r.Host)
if strings.HasPrefix(r.URL.Path, "/.well-known/acme-wellknown/") {
logrus.Infof("Handling .well-known/acme-wellknown request: %s", r.URL.Path)
rph.handleWellKnown(w, r)
return
}
host, _, err := net.SplitHostPort(r.Host)
if err != nil {
if addrErr, ok := err.(*net.AddrError); ok && !strings.Contains(addrErr.Err, "missing port") {
logrus.Errorf("Error splitting host/port: %T %#v", err, err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
logrus.Debugf("Using %q as hostname (no port)", r.Host)
host = r.Host
}
logrus.Infof("Proxying request: Host=%s, Path=%s, ResolvedHost=%s", r.Host, r.URL.Path, host)
// Try exact match first
if v, ok := rph.Map[r.Host]; ok {
logrus.Infof("Found exact match for host %s, forwarding to backend", r.Host)
v.ServeHTTP(w, r)
return
}
// Try with resolved host (without port)
if v, ok := rph.Map[host]; ok {
logrus.Infof("Found match for resolved host %s, forwarding to backend", host)
v.ServeHTTP(w, r)
return
}
logrus.Warnf("No mapping found for host: %s (resolved: %s). Available hosts: %v",
r.Host, host, rph.getHostList())
if rph.NotFoundHandler == nil {
http.NotFoundHandler().ServeHTTP(w, r)
return
}
rph.NotFoundHandler.ServeHTTP(w, r)
}
func (rph reverseProxiesHandler) getHostList() []string {
hosts := make([]string, 0, len(rph.Map))
for host := range rph.Map {
hosts = append(hosts, host)
}
return hosts
}
func (rph reverseProxiesHandler) handleWellKnown(w http.ResponseWriter, r *http.Request) {
// Проверяем и при необходимости монтируем NFS перед обработкой
if err := nfs.EnsureMounted(); err != nil {
logrus.Errorf("Failed to ensure NFS is mounted: %v", err)
http.Error(w, "NFS mount error", http.StatusInternalServerError)
return
}
path := strings.TrimPrefix(r.URL.Path, "/.well-known/acme-wellknown/")
if path == "" || strings.Contains(path, "..") {
logrus.Warnf("Invalid .well-known path: %s", r.URL.Path)
http.Error(w, "Invalid path", http.StatusBadRequest)
return
}
fullPath := filepath.Join(rph.WellKnownDir, path)
logrus.Infof("Well-known request: method=%s, path=%s, fullPath=%s", r.Method, path, fullPath)
switch r.Method {
case "GET", "HEAD":
data, err := ioutil.ReadFile(fullPath)
if err != nil {
if os.IsNotExist(err) {
http.NotFound(w, r)
return
}
logrus.Errorf("Error reading file %s: %v", fullPath, err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.WriteHeader(http.StatusOK)
if r.Method == "GET" {
w.Write(data)
}
case "POST", "PUT":
data, err := ioutil.ReadAll(r.Body)
if err != nil {
logrus.Errorf("Error reading request body: %v", err)
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
dir := filepath.Dir(fullPath)
if err := os.MkdirAll(dir, 0755); err != nil {
logrus.Errorf("Error creating directory %s: %v", dir, err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
if err := ioutil.WriteFile(fullPath, data, 0644); err != nil {
logrus.Errorf("Error writing file %s: %v", fullPath, err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
if r.Method == "PUT" {
w.Write([]byte("File created/updated successfully"))
} else {
w.Write([]byte("File created successfully"))
}
case "DELETE":
if err := os.Remove(fullPath); err != nil {
if os.IsNotExist(err) {
http.NotFound(w, r)
return
}
logrus.Errorf("Error deleting file %s: %v", fullPath, err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("File deleted successfully"))
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}