mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-20 12:42:34 +02:00
177 lines
4.3 KiB
Go
Executable file
177 lines
4.3 KiB
Go
Executable file
package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type HTTPRoute struct {
|
|
Alias string
|
|
Url *url.URL
|
|
Path string
|
|
PathMode string
|
|
Proxy *ReverseProxy
|
|
|
|
l logrus.FieldLogger
|
|
}
|
|
|
|
func NewHTTPRoute(config *ProxyConfig) (*HTTPRoute, error) {
|
|
url, err := url.Parse(fmt.Sprintf("%s://%s:%s", config.Scheme, config.Host, config.Port))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var tr *http.Transport
|
|
if config.NoTLSVerify {
|
|
tr = transportNoTLS
|
|
} else {
|
|
tr = transport
|
|
}
|
|
|
|
proxy := NewSingleHostReverseProxy(url, tr)
|
|
|
|
if !isValidProxyPathMode(config.PathMode) {
|
|
return nil, fmt.Errorf("invalid path mode: %s", config.PathMode)
|
|
}
|
|
|
|
route := &HTTPRoute{
|
|
Alias: config.Alias,
|
|
Url: url,
|
|
Path: config.Path,
|
|
Proxy: proxy,
|
|
PathMode: config.PathMode,
|
|
l: hrlog.WithFields(logrus.Fields{
|
|
"alias": config.Alias,
|
|
"path": config.Path,
|
|
"path_mode": config.PathMode,
|
|
}),
|
|
}
|
|
|
|
var rewriteBegin = proxy.Rewrite
|
|
var rewrite func(*ProxyRequest)
|
|
var modifyResponse func(*http.Response) error
|
|
|
|
switch {
|
|
case config.Path == "", config.PathMode == ProxyPathMode_Forward:
|
|
rewrite = rewriteBegin
|
|
case config.PathMode == ProxyPathMode_Sub:
|
|
rewrite = func(pr *ProxyRequest) {
|
|
rewriteBegin(pr)
|
|
// disable compression
|
|
pr.Out.Header.Set("Accept-Encoding", "identity")
|
|
// remove path prefix
|
|
pr.Out.URL.Path = strings.TrimPrefix(pr.Out.URL.Path, config.Path)
|
|
}
|
|
modifyResponse = func(r *http.Response) error {
|
|
contentType, ok := r.Header["Content-Type"]
|
|
if !ok || len(contentType) == 0 {
|
|
route.l.Debug("unknown content type for ", r.Request.URL.String())
|
|
return nil
|
|
}
|
|
// disable cache
|
|
r.Header.Set("Cache-Control", "no-store")
|
|
|
|
var err error = nil
|
|
switch {
|
|
case strings.HasPrefix(contentType[0], "text/html"):
|
|
err = utils.respHTMLSubPath(r, config.Path)
|
|
case strings.HasPrefix(contentType[0], "application/javascript"):
|
|
err = utils.respJSSubPath(r, config.Path)
|
|
default:
|
|
route.l.Debug("unknown content type(s): ", contentType)
|
|
}
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to remove path prefix %s: %v", config.Path, err)
|
|
route.l.WithField("action", "path_sub").Error(err)
|
|
r.Status = err.Error()
|
|
r.StatusCode = http.StatusInternalServerError
|
|
}
|
|
return err
|
|
}
|
|
default:
|
|
rewrite = func(pr *ProxyRequest) {
|
|
rewriteBegin(pr)
|
|
pr.Out.URL.Path = strings.TrimPrefix(pr.Out.URL.Path, config.Path)
|
|
}
|
|
}
|
|
|
|
if logLevel == logrus.DebugLevel {
|
|
route.Proxy.Rewrite = func(pr *ProxyRequest) {
|
|
rewrite(pr)
|
|
route.l.Debug("request URL: ", pr.In.Host, pr.In.URL.Path)
|
|
route.l.Debug("request headers: ", pr.In.Header)
|
|
}
|
|
route.Proxy.ModifyResponse = func(r *http.Response) error {
|
|
route.l.Debug("response URL: ", r.Request.URL.String())
|
|
route.l.Debug("response headers: ", r.Header)
|
|
if modifyResponse != nil {
|
|
return modifyResponse(r)
|
|
}
|
|
return nil
|
|
}
|
|
} else {
|
|
route.Proxy.Rewrite = rewrite
|
|
}
|
|
|
|
return route, nil
|
|
}
|
|
|
|
func (r *HTTPRoute) Start() {}
|
|
func (r *HTTPRoute) Stop() {
|
|
httpRoutes.Delete(r.Alias)
|
|
}
|
|
|
|
func isValidProxyPathMode(mode string) bool {
|
|
switch mode {
|
|
case ProxyPathMode_Forward, ProxyPathMode_Sub, ProxyPathMode_RemovedPath:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func redirectToTLS(w http.ResponseWriter, r *http.Request) {
|
|
// Redirect to the same host but with HTTPS
|
|
var redirectCode int
|
|
if r.Method == http.MethodGet {
|
|
redirectCode = http.StatusMovedPermanently
|
|
} else {
|
|
redirectCode = http.StatusPermanentRedirect
|
|
}
|
|
http.Redirect(w, r, fmt.Sprintf("https://%s%s?%s", r.Host, r.URL.Path, r.URL.RawQuery), redirectCode)
|
|
}
|
|
|
|
func findHTTPRoute(host string, path string) (*HTTPRoute, error) {
|
|
subdomain := strings.Split(host, ".")[0]
|
|
routeMap, ok := httpRoutes.UnsafeGet(subdomain)
|
|
if ok {
|
|
return routeMap.FindMatch(path)
|
|
}
|
|
return nil, fmt.Errorf("no matching route for subdomain %s", subdomain)
|
|
}
|
|
|
|
func httpProxyHandler(w http.ResponseWriter, r *http.Request) {
|
|
route, err := findHTTPRoute(r.Host, r.URL.Path)
|
|
if err != nil {
|
|
err = fmt.Errorf("request failed %s %s%s, error: %v",
|
|
r.Method,
|
|
r.Host,
|
|
r.URL.Path,
|
|
err,
|
|
)
|
|
logrus.Error(err)
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
return
|
|
}
|
|
route.Proxy.ServeHTTP(w, r)
|
|
}
|
|
|
|
// alias -> (path -> routes)
|
|
type HTTPRoutes = SafeMap[string, *pathPoolMap]
|
|
|
|
var httpRoutes HTTPRoutes = NewSafeMap[string](newPathPoolMap)
|