mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-08-10 23:27:50 +02:00
Added Zoraxy experimental
This commit is contained in:
67
src/mod/dynamicproxy/Server.go
Normal file
67
src/mod/dynamicproxy/Server.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package dynamicproxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
)
|
||||
|
||||
/*
|
||||
Server.go
|
||||
|
||||
Main server for dynamic proxy core
|
||||
*/
|
||||
|
||||
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
//Check if this ip is in blacklist
|
||||
clientIpAddr := geodb.GetRequesterIP(r)
|
||||
if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
template, err := os.ReadFile("./web/forbidden.html")
|
||||
if err != nil {
|
||||
w.Write([]byte("403 - Forbidden"))
|
||||
} else {
|
||||
w.Write(template)
|
||||
}
|
||||
h.logRequest(r, false, 403, "blacklist")
|
||||
return
|
||||
}
|
||||
|
||||
//Check if this is a redirection url
|
||||
if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) {
|
||||
statusCode := h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r)
|
||||
h.logRequest(r, statusCode != 500, statusCode, "redirect")
|
||||
return
|
||||
}
|
||||
|
||||
//Extract request host to see if it is virtual directory or subdomain
|
||||
domainOnly := r.Host
|
||||
if strings.Contains(r.Host, ":") {
|
||||
hostPath := strings.Split(r.Host, ":")
|
||||
domainOnly = hostPath[0]
|
||||
}
|
||||
|
||||
if strings.Contains(r.Host, ".") {
|
||||
//This might be a subdomain. See if there are any subdomain proxy router for this
|
||||
//Remove the port if any
|
||||
|
||||
sep := h.Parent.getSubdomainProxyEndpointFromHostname(domainOnly)
|
||||
if sep != nil {
|
||||
h.subdomainRequest(w, r, sep)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//Clean up the request URI
|
||||
proxyingPath := strings.TrimSpace(r.RequestURI)
|
||||
|
||||
targetProxyEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath)
|
||||
if targetProxyEndpoint != nil {
|
||||
h.proxyRequest(w, r, targetProxyEndpoint)
|
||||
} else {
|
||||
h.proxyRequest(w, r, h.Parent.Root)
|
||||
}
|
||||
}
|
23
src/mod/dynamicproxy/domainsniff/domainsniff.go
Normal file
23
src/mod/dynamicproxy/domainsniff/domainsniff.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package domainsniff
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
//Check if the domain is reachable and return err if not reachable
|
||||
func DomainReachableWithError(domain string) error {
|
||||
timeout := 1 * time.Second
|
||||
conn, err := net.DialTimeout("tcp", domain, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
//Check if domain reachable
|
||||
func DomainReachable(domain string) bool {
|
||||
return DomainReachableWithError(domain) == nil
|
||||
}
|
21
src/mod/dynamicproxy/dpcore/LICENSE
Normal file
21
src/mod/dynamicproxy/dpcore/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-present tobychui
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
414
src/mod/dynamicproxy/dpcore/dpcore.go
Normal file
414
src/mod/dynamicproxy/dpcore/dpcore.go
Normal file
@@ -0,0 +1,414 @@
|
||||
package dpcore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var onExitFlushLoop func()
|
||||
|
||||
const (
|
||||
defaultTimeout = time.Minute * 5
|
||||
)
|
||||
|
||||
// ReverseProxy is an HTTP Handler that takes an incoming request and
|
||||
// sends it to another server, proxying the response back to the
|
||||
// client, support http, also support https tunnel using http.hijacker
|
||||
type ReverseProxy struct {
|
||||
// Set the timeout of the proxy server, default is 5 minutes
|
||||
Timeout time.Duration
|
||||
|
||||
// Director must be a function which modifies
|
||||
// the request into a new request to be sent
|
||||
// using Transport. Its response is then copied
|
||||
// back to the original client unmodified.
|
||||
// Director must not access the provided Request
|
||||
// after returning.
|
||||
Director func(*http.Request)
|
||||
|
||||
// The transport used to perform proxy requests.
|
||||
// default is http.DefaultTransport.
|
||||
Transport http.RoundTripper
|
||||
|
||||
// FlushInterval specifies the flush interval
|
||||
// to flush to the client while copying the
|
||||
// response body. If zero, no periodic flushing is done.
|
||||
FlushInterval time.Duration
|
||||
|
||||
// ErrorLog specifies an optional logger for errors
|
||||
// that occur when attempting to proxy the request.
|
||||
// If nil, logging goes to os.Stderr via the log package's
|
||||
// standard logger.
|
||||
ErrorLog *log.Logger
|
||||
|
||||
// ModifyResponse is an optional function that
|
||||
// modifies the Response from the backend.
|
||||
// If it returns an error, the proxy returns a StatusBadGateway error.
|
||||
ModifyResponse func(*http.Response) error
|
||||
|
||||
//Prepender is an optional prepend text for URL rewrite
|
||||
//
|
||||
Prepender string
|
||||
|
||||
Verbal bool
|
||||
}
|
||||
|
||||
type requestCanceler interface {
|
||||
CancelRequest(req *http.Request)
|
||||
}
|
||||
|
||||
func NewDynamicProxyCore(target *url.URL, prepender string) *ReverseProxy {
|
||||
targetQuery := target.RawQuery
|
||||
director := func(req *http.Request) {
|
||||
req.URL.Scheme = target.Scheme
|
||||
req.URL.Host = target.Host
|
||||
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
|
||||
|
||||
// If Host is empty, the Request.Write method uses
|
||||
// the value of URL.Host.
|
||||
// force use URL.Host
|
||||
req.Host = req.URL.Host
|
||||
if targetQuery == "" || req.URL.RawQuery == "" {
|
||||
req.URL.RawQuery = targetQuery + req.URL.RawQuery
|
||||
} else {
|
||||
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
||||
}
|
||||
|
||||
if _, ok := req.Header["User-Agent"]; !ok {
|
||||
req.Header.Set("User-Agent", "")
|
||||
}
|
||||
}
|
||||
|
||||
return &ReverseProxy{
|
||||
Director: director,
|
||||
Prepender: prepender,
|
||||
Verbal: false,
|
||||
}
|
||||
}
|
||||
|
||||
func singleJoiningSlash(a, b string) string {
|
||||
aslash := strings.HasSuffix(a, "/")
|
||||
bslash := strings.HasPrefix(b, "/")
|
||||
switch {
|
||||
case aslash && bslash:
|
||||
return a + b[1:]
|
||||
case !aslash && !bslash:
|
||||
return a + "/" + b
|
||||
}
|
||||
return a + b
|
||||
}
|
||||
|
||||
func copyHeader(dst, src http.Header) {
|
||||
for k, vv := range src {
|
||||
for _, v := range vv {
|
||||
dst.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hop-by-hop headers. These are removed when sent to the backend.
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
|
||||
var hopHeaders = []string{
|
||||
//"Connection",
|
||||
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
|
||||
"Keep-Alive",
|
||||
"Proxy-Authenticate",
|
||||
"Proxy-Authorization",
|
||||
"Te", // canonicalized version of "TE"
|
||||
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
|
||||
"Transfer-Encoding",
|
||||
//"Upgrade",
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
|
||||
if p.FlushInterval != 0 {
|
||||
if wf, ok := dst.(writeFlusher); ok {
|
||||
mlw := &maxLatencyWriter{
|
||||
dst: wf,
|
||||
latency: p.FlushInterval,
|
||||
done: make(chan bool),
|
||||
}
|
||||
|
||||
go mlw.flushLoop()
|
||||
defer mlw.stop()
|
||||
dst = mlw
|
||||
}
|
||||
}
|
||||
|
||||
io.Copy(dst, src)
|
||||
}
|
||||
|
||||
type writeFlusher interface {
|
||||
io.Writer
|
||||
http.Flusher
|
||||
}
|
||||
|
||||
type maxLatencyWriter struct {
|
||||
dst writeFlusher
|
||||
latency time.Duration
|
||||
mu sync.Mutex
|
||||
done chan bool
|
||||
}
|
||||
|
||||
func (m *maxLatencyWriter) Write(b []byte) (int, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return m.dst.Write(b)
|
||||
}
|
||||
|
||||
func (m *maxLatencyWriter) flushLoop() {
|
||||
t := time.NewTicker(m.latency)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-m.done:
|
||||
if onExitFlushLoop != nil {
|
||||
onExitFlushLoop()
|
||||
}
|
||||
return
|
||||
case <-t.C:
|
||||
m.mu.Lock()
|
||||
m.dst.Flush()
|
||||
m.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *maxLatencyWriter) stop() {
|
||||
m.done <- true
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) logf(format string, args ...interface{}) {
|
||||
if p.ErrorLog != nil {
|
||||
p.ErrorLog.Printf(format, args...)
|
||||
} else {
|
||||
log.Printf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func removeHeaders(header http.Header) {
|
||||
// Remove hop-by-hop headers listed in the "Connection" header.
|
||||
if c := header.Get("Connection"); c != "" {
|
||||
for _, f := range strings.Split(c, ",") {
|
||||
if f = strings.TrimSpace(f); f != "" {
|
||||
header.Del(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove hop-by-hop headers
|
||||
for _, h := range hopHeaders {
|
||||
if header.Get(h) != "" {
|
||||
header.Del(h)
|
||||
}
|
||||
}
|
||||
|
||||
if header.Get("A-Upgrade") != "" {
|
||||
header.Set("Upgrade", header.Get("A-Upgrade"))
|
||||
header.Del("A-Upgrade")
|
||||
}
|
||||
}
|
||||
|
||||
func addXForwardedForHeader(req *http.Request) {
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
// If we aren't the first proxy retain prior
|
||||
// X-Forwarded-For information as a comma+space
|
||||
// separated list and fold multiple headers into one.
|
||||
if prior, ok := req.Header["X-Forwarded-For"]; ok {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
req.Header.Set("X-Forwarded-For", clientIP)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request) error {
|
||||
transport := p.Transport
|
||||
if transport == nil {
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
|
||||
outreq := new(http.Request)
|
||||
// Shallow copies of maps, like header
|
||||
*outreq = *req
|
||||
|
||||
if cn, ok := rw.(http.CloseNotifier); ok {
|
||||
if requestCanceler, ok := transport.(requestCanceler); ok {
|
||||
// After the Handler has returned, there is no guarantee
|
||||
// that the channel receives a value, so to make sure
|
||||
reqDone := make(chan struct{})
|
||||
defer close(reqDone)
|
||||
clientGone := cn.CloseNotify()
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-clientGone:
|
||||
requestCanceler.CancelRequest(outreq)
|
||||
case <-reqDone:
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
p.Director(outreq)
|
||||
outreq.Close = false
|
||||
|
||||
// We may modify the header (shallow copied above), so we only copy it.
|
||||
outreq.Header = make(http.Header)
|
||||
copyHeader(outreq.Header, req.Header)
|
||||
|
||||
// Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
|
||||
removeHeaders(outreq.Header)
|
||||
|
||||
// Add X-Forwarded-For Header.
|
||||
addXForwardedForHeader(outreq)
|
||||
|
||||
res, err := transport.RoundTrip(outreq)
|
||||
if err != nil {
|
||||
if p.Verbal {
|
||||
p.logf("http: proxy error: %v", err)
|
||||
}
|
||||
|
||||
//rw.WriteHeader(http.StatusBadGateway)
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers.
|
||||
removeHeaders(res.Header)
|
||||
|
||||
if p.ModifyResponse != nil {
|
||||
if err := p.ModifyResponse(res); err != nil {
|
||||
if p.Verbal {
|
||||
p.logf("http: proxy error: %v", err)
|
||||
}
|
||||
|
||||
//rw.WriteHeader(http.StatusBadGateway)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//Custom header rewriter functions
|
||||
if res.Header.Get("Location") != "" {
|
||||
//Custom redirection to this rproxy relative path
|
||||
res.Header.Set("Location", filepath.ToSlash(filepath.Join(p.Prepender, res.Header.Get("Location"))))
|
||||
}
|
||||
// Copy header from response to client.
|
||||
copyHeader(rw.Header(), res.Header)
|
||||
|
||||
// The "Trailer" header isn't included in the Transport's response, Build it up from Trailer.
|
||||
if len(res.Trailer) > 0 {
|
||||
trailerKeys := make([]string, 0, len(res.Trailer))
|
||||
for k := range res.Trailer {
|
||||
trailerKeys = append(trailerKeys, k)
|
||||
}
|
||||
rw.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
|
||||
}
|
||||
|
||||
rw.WriteHeader(res.StatusCode)
|
||||
if len(res.Trailer) > 0 {
|
||||
// Force chunking if we saw a response trailer.
|
||||
// This prevents net/http from calculating the length for short
|
||||
// bodies and adding a Content-Length.
|
||||
if fl, ok := rw.(http.Flusher); ok {
|
||||
fl.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
p.copyResponse(rw, res.Body)
|
||||
// close now, instead of defer, to populate res.Trailer
|
||||
res.Body.Close()
|
||||
copyHeader(rw.Header(), res.Trailer)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) error {
|
||||
hij, ok := rw.(http.Hijacker)
|
||||
if !ok {
|
||||
p.logf("http server does not support hijacker")
|
||||
return errors.New("http server does not support hijacker")
|
||||
}
|
||||
|
||||
clientConn, _, err := hij.Hijack()
|
||||
if err != nil {
|
||||
if p.Verbal {
|
||||
p.logf("http: proxy error: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
proxyConn, err := net.Dial("tcp", req.URL.Host)
|
||||
if err != nil {
|
||||
if p.Verbal {
|
||||
p.logf("http: proxy error: %v", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// The returned net.Conn may have read or write deadlines
|
||||
// already set, depending on the configuration of the
|
||||
// Server, to set or clear those deadlines as needed
|
||||
// we set timeout to 5 minutes
|
||||
deadline := time.Now()
|
||||
if p.Timeout == 0 {
|
||||
deadline = deadline.Add(time.Minute * 5)
|
||||
} else {
|
||||
deadline = deadline.Add(p.Timeout)
|
||||
}
|
||||
|
||||
err = clientConn.SetDeadline(deadline)
|
||||
if err != nil {
|
||||
if p.Verbal {
|
||||
p.logf("http: proxy error: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
err = proxyConn.SetDeadline(deadline)
|
||||
if err != nil {
|
||||
if p.Verbal {
|
||||
p.logf("http: proxy error: %v", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = clientConn.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
|
||||
if err != nil {
|
||||
if p.Verbal {
|
||||
p.logf("http: proxy error: %v", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
io.Copy(clientConn, proxyConn)
|
||||
clientConn.Close()
|
||||
proxyConn.Close()
|
||||
}()
|
||||
|
||||
io.Copy(proxyConn, clientConn)
|
||||
proxyConn.Close()
|
||||
clientConn.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) error {
|
||||
if req.Method == "CONNECT" {
|
||||
err := p.ProxyHTTPS(rw, req)
|
||||
return err
|
||||
} else {
|
||||
err := p.ProxyHTTP(rw, req)
|
||||
return err
|
||||
}
|
||||
}
|
310
src/mod/dynamicproxy/dynamicproxy.go
Normal file
310
src/mod/dynamicproxy/dynamicproxy.go
Normal file
@@ -0,0 +1,310 @@
|
||||
package dynamicproxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
"imuslab.com/zoraxy/mod/reverseproxy"
|
||||
"imuslab.com/zoraxy/mod/statistic"
|
||||
"imuslab.com/zoraxy/mod/tlscert"
|
||||
)
|
||||
|
||||
/*
|
||||
Zoraxy Dynamic Proxy
|
||||
*/
|
||||
type RouterOption struct {
|
||||
Port int
|
||||
UseTls bool
|
||||
ForceHttpsRedirect bool
|
||||
TlsManager *tlscert.Manager
|
||||
RedirectRuleTable *redirection.RuleTable
|
||||
GeodbStore *geodb.Store
|
||||
StatisticCollector *statistic.Collector
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
Option *RouterOption
|
||||
ProxyEndpoints *sync.Map
|
||||
SubdomainEndpoint *sync.Map
|
||||
Running bool
|
||||
Root *ProxyEndpoint
|
||||
mux http.Handler
|
||||
server *http.Server
|
||||
tlsListener net.Listener
|
||||
}
|
||||
|
||||
type ProxyEndpoint struct {
|
||||
Root string
|
||||
Domain string
|
||||
RequireTLS bool
|
||||
Proxy *dpcore.ReverseProxy `json:"-"`
|
||||
}
|
||||
|
||||
type SubdomainEndpoint struct {
|
||||
MatchingDomain string
|
||||
Domain string
|
||||
RequireTLS bool
|
||||
Proxy *reverseproxy.ReverseProxy `json:"-"`
|
||||
}
|
||||
|
||||
type ProxyHandler struct {
|
||||
Parent *Router
|
||||
}
|
||||
|
||||
func NewDynamicProxy(option RouterOption) (*Router, error) {
|
||||
proxyMap := sync.Map{}
|
||||
domainMap := sync.Map{}
|
||||
thisRouter := Router{
|
||||
Option: &option,
|
||||
ProxyEndpoints: &proxyMap,
|
||||
SubdomainEndpoint: &domainMap,
|
||||
Running: false,
|
||||
server: nil,
|
||||
}
|
||||
|
||||
thisRouter.mux = &ProxyHandler{
|
||||
Parent: &thisRouter,
|
||||
}
|
||||
|
||||
return &thisRouter, nil
|
||||
}
|
||||
|
||||
// Update TLS setting in runtime. Will restart the proxy server
|
||||
// if it is already running in the background
|
||||
func (router *Router) UpdateTLSSetting(tlsEnabled bool) {
|
||||
router.Option.UseTls = tlsEnabled
|
||||
router.Restart()
|
||||
}
|
||||
|
||||
// Update https redirect, which will require updates
|
||||
func (router *Router) UpdateHttpToHttpsRedirectSetting(useRedirect bool) {
|
||||
router.Option.ForceHttpsRedirect = useRedirect
|
||||
router.Restart()
|
||||
}
|
||||
|
||||
// Start the dynamic routing
|
||||
func (router *Router) StartProxyService() error {
|
||||
//Create a new server object
|
||||
if router.server != nil {
|
||||
return errors.New("Reverse proxy server already running")
|
||||
}
|
||||
|
||||
if router.Root == nil {
|
||||
return errors.New("Reverse proxy router root not set")
|
||||
}
|
||||
|
||||
config := &tls.Config{
|
||||
GetCertificate: router.Option.TlsManager.GetCert,
|
||||
}
|
||||
|
||||
if router.Option.UseTls {
|
||||
//Serve with TLS mode
|
||||
ln, err := tls.Listen("tcp", ":"+strconv.Itoa(router.Option.Port), config)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
router.tlsListener = ln
|
||||
router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux}
|
||||
router.Running = true
|
||||
|
||||
if router.Option.Port == 443 && router.Option.ForceHttpsRedirect {
|
||||
//Add a 80 to 443 redirector
|
||||
httpServer := &http.Server{
|
||||
Addr: ":80",
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusTemporaryRedirect)
|
||||
}),
|
||||
ReadTimeout: 3 * time.Second,
|
||||
WriteTimeout: 3 * time.Second,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
}
|
||||
|
||||
log.Println("Starting HTTP-to-HTTPS redirector (port 80)")
|
||||
go func() {
|
||||
//Start another router to check if the router.server is killed. If yes, kill this server as well
|
||||
go func() {
|
||||
for router.server != nil {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
httpServer.Shutdown(ctx)
|
||||
log.Println(":80 to :433 redirection listener stopped")
|
||||
}()
|
||||
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("Could not start server: %v\n", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
log.Println("Reverse proxy service started in the background (TLS mode)")
|
||||
go func() {
|
||||
if err := router.server.Serve(ln); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("Could not start server: %v\n", err)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
//Serve with non TLS mode
|
||||
router.tlsListener = nil
|
||||
router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux}
|
||||
router.Running = true
|
||||
log.Println("Reverse proxy service started in the background (Plain HTTP mode)")
|
||||
go func() {
|
||||
router.server.ListenAndServe()
|
||||
//log.Println("[DynamicProxy] " + err.Error())
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (router *Router) StopProxyService() error {
|
||||
if router.server == nil {
|
||||
return errors.New("Reverse proxy server already stopped")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
err := router.server.Shutdown(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if router.tlsListener != nil {
|
||||
router.tlsListener.Close()
|
||||
}
|
||||
|
||||
//Discard the server object
|
||||
router.tlsListener = nil
|
||||
router.server = nil
|
||||
router.Running = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restart the current router if it is running.
|
||||
// Startup the server if it is not running initially
|
||||
func (router *Router) Restart() error {
|
||||
//Stop the router if it is already running
|
||||
if router.Running {
|
||||
err := router.StopProxyService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//Start the server
|
||||
err := router.StartProxyService()
|
||||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
Check if a given request is accessed via a proxied subdomain
|
||||
*/
|
||||
|
||||
func (router *Router) IsProxiedSubdomain(r *http.Request) bool {
|
||||
hostname := r.Header.Get("X-Forwarded-Host")
|
||||
if hostname == "" {
|
||||
hostname = r.Host
|
||||
}
|
||||
hostname = strings.Split(hostname, ":")[0]
|
||||
subdEndpoint := router.getSubdomainProxyEndpointFromHostname(hostname)
|
||||
return subdEndpoint != nil
|
||||
}
|
||||
|
||||
/*
|
||||
Add an URL into a custom proxy services
|
||||
*/
|
||||
func (router *Router) AddVirtualDirectoryProxyService(rootname string, domain string, requireTLS bool) error {
|
||||
if domain[len(domain)-1:] == "/" {
|
||||
domain = domain[:len(domain)-1]
|
||||
}
|
||||
|
||||
if rootname[len(rootname)-1:] == "/" {
|
||||
rootname = rootname[:len(rootname)-1]
|
||||
}
|
||||
|
||||
webProxyEndpoint := domain
|
||||
if requireTLS {
|
||||
webProxyEndpoint = "https://" + webProxyEndpoint
|
||||
} else {
|
||||
webProxyEndpoint = "http://" + webProxyEndpoint
|
||||
}
|
||||
//Create a new proxy agent for this root
|
||||
path, err := url.Parse(webProxyEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proxy := dpcore.NewDynamicProxyCore(path, rootname)
|
||||
|
||||
endpointObject := ProxyEndpoint{
|
||||
Root: rootname,
|
||||
Domain: domain,
|
||||
RequireTLS: requireTLS,
|
||||
Proxy: proxy,
|
||||
}
|
||||
|
||||
router.ProxyEndpoints.Store(rootname, &endpointObject)
|
||||
|
||||
log.Println("Adding Proxy Rule: ", rootname+" to "+domain)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Remove routing from RP
|
||||
*/
|
||||
func (router *Router) RemoveProxy(ptype string, key string) error {
|
||||
//fmt.Println(ptype, key)
|
||||
if ptype == "vdir" {
|
||||
router.ProxyEndpoints.Delete(key)
|
||||
return nil
|
||||
} else if ptype == "subd" {
|
||||
router.SubdomainEndpoint.Delete(key)
|
||||
return nil
|
||||
}
|
||||
return errors.New("invalid ptype")
|
||||
}
|
||||
|
||||
/*
|
||||
Add an default router for the proxy server
|
||||
*/
|
||||
func (router *Router) SetRootProxy(proxyLocation string, requireTLS bool) error {
|
||||
if proxyLocation[len(proxyLocation)-1:] == "/" {
|
||||
proxyLocation = proxyLocation[:len(proxyLocation)-1]
|
||||
}
|
||||
|
||||
webProxyEndpoint := proxyLocation
|
||||
if requireTLS {
|
||||
webProxyEndpoint = "https://" + webProxyEndpoint
|
||||
} else {
|
||||
webProxyEndpoint = "http://" + webProxyEndpoint
|
||||
}
|
||||
//Create a new proxy agent for this root
|
||||
path, err := url.Parse(webProxyEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proxy := dpcore.NewDynamicProxyCore(path, "")
|
||||
|
||||
rootEndpoint := ProxyEndpoint{
|
||||
Root: "/",
|
||||
Domain: proxyLocation,
|
||||
RequireTLS: requireTLS,
|
||||
Proxy: proxy,
|
||||
}
|
||||
|
||||
router.Root = &rootEndpoint
|
||||
return nil
|
||||
}
|
149
src/mod/dynamicproxy/proxyRequestHandler.go
Normal file
149
src/mod/dynamicproxy/proxyRequestHandler.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package dynamicproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
"imuslab.com/zoraxy/mod/statistic"
|
||||
"imuslab.com/zoraxy/mod/websocketproxy"
|
||||
)
|
||||
|
||||
func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *ProxyEndpoint {
|
||||
var targetProxyEndpoint *ProxyEndpoint = nil
|
||||
router.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
||||
rootname := key.(string)
|
||||
if strings.HasPrefix(requestURI, rootname) {
|
||||
thisProxyEndpoint := value.(*ProxyEndpoint)
|
||||
targetProxyEndpoint = thisProxyEndpoint
|
||||
}
|
||||
/*
|
||||
if len(requestURI) >= len(rootname) && requestURI[:len(rootname)] == rootname {
|
||||
thisProxyEndpoint := value.(*ProxyEndpoint)
|
||||
targetProxyEndpoint = thisProxyEndpoint
|
||||
}
|
||||
*/
|
||||
return true
|
||||
})
|
||||
|
||||
return targetProxyEndpoint
|
||||
}
|
||||
|
||||
func (router *Router) getSubdomainProxyEndpointFromHostname(hostname string) *SubdomainEndpoint {
|
||||
var targetSubdomainEndpoint *SubdomainEndpoint = nil
|
||||
ep, ok := router.SubdomainEndpoint.Load(hostname)
|
||||
if ok {
|
||||
targetSubdomainEndpoint = ep.(*SubdomainEndpoint)
|
||||
}
|
||||
|
||||
return targetSubdomainEndpoint
|
||||
}
|
||||
|
||||
func (router *Router) rewriteURL(rooturl string, requestURL string) string {
|
||||
if len(requestURL) > len(rooturl) {
|
||||
return requestURL[len(rooturl):]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request, target *SubdomainEndpoint) {
|
||||
r.Header.Set("X-Forwarded-Host", r.Host)
|
||||
requestURL := r.URL.String()
|
||||
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
||||
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
||||
r.Header.Set("A-Upgrade", "websocket")
|
||||
wsRedirectionEndpoint := target.Domain
|
||||
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
|
||||
//Append / to the end of the redirection endpoint if not exists
|
||||
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
|
||||
}
|
||||
if len(requestURL) > 0 && requestURL[:1] == "/" {
|
||||
//Remove starting / from request URL if exists
|
||||
requestURL = requestURL[1:]
|
||||
}
|
||||
u, _ := url.Parse("ws://" + wsRedirectionEndpoint + requestURL)
|
||||
if target.RequireTLS {
|
||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
||||
}
|
||||
h.logRequest(r, true, 101, "subdomain-websocket")
|
||||
wspHandler := websocketproxy.NewProxy(u)
|
||||
wspHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
r.Host = r.URL.Host
|
||||
err := target.Proxy.ServeHTTP(w, r)
|
||||
var dnsError *net.DNSError
|
||||
if err != nil {
|
||||
if errors.As(err, &dnsError) {
|
||||
http.ServeFile(w, r, "./web/hosterror.html")
|
||||
log.Println(err.Error())
|
||||
h.logRequest(r, false, 404, "subdomain-http")
|
||||
} else {
|
||||
http.ServeFile(w, r, "./web/rperror.html")
|
||||
log.Println(err.Error())
|
||||
h.logRequest(r, false, 521, "subdomain-http")
|
||||
}
|
||||
}
|
||||
|
||||
h.logRequest(r, true, 200, "subdomain-http")
|
||||
}
|
||||
|
||||
func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
|
||||
rewriteURL := h.Parent.rewriteURL(target.Root, r.RequestURI)
|
||||
r.URL, _ = url.Parse(rewriteURL)
|
||||
r.Header.Set("X-Forwarded-Host", r.Host)
|
||||
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
||||
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
||||
r.Header.Set("A-Upgrade", "websocket")
|
||||
wsRedirectionEndpoint := target.Domain
|
||||
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
|
||||
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
|
||||
}
|
||||
u, _ := url.Parse("ws://" + wsRedirectionEndpoint + r.URL.String())
|
||||
if target.RequireTLS {
|
||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
|
||||
}
|
||||
h.logRequest(r, true, 101, "vdir-websocket")
|
||||
wspHandler := websocketproxy.NewProxy(u)
|
||||
wspHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
r.Host = r.URL.Host
|
||||
err := target.Proxy.ServeHTTP(w, r)
|
||||
var dnsError *net.DNSError
|
||||
if err != nil {
|
||||
if errors.As(err, &dnsError) {
|
||||
http.ServeFile(w, r, "./web/hosterror.html")
|
||||
log.Println(err.Error())
|
||||
h.logRequest(r, false, 404, "vdir-http")
|
||||
} else {
|
||||
http.ServeFile(w, r, "./web/rperror.html")
|
||||
log.Println(err.Error())
|
||||
h.logRequest(r, false, 521, "vdir-http")
|
||||
}
|
||||
}
|
||||
h.logRequest(r, true, 200, "vdir-http")
|
||||
|
||||
}
|
||||
|
||||
func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, forwardType string) {
|
||||
if h.Parent.Option.StatisticCollector != nil {
|
||||
go func() {
|
||||
requestInfo := statistic.RequestInfo{
|
||||
IpAddr: geodb.GetRequesterIP(r),
|
||||
RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
||||
Succ: succ,
|
||||
StatusCode: statusCode,
|
||||
ForwardType: forwardType,
|
||||
}
|
||||
h.Parent.Option.StatisticCollector.RecordRequest(requestInfo)
|
||||
}()
|
||||
|
||||
}
|
||||
}
|
53
src/mod/dynamicproxy/redirection/handler.go
Normal file
53
src/mod/dynamicproxy/redirection/handler.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package redirection
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
handler.go
|
||||
|
||||
This script store the handlers use for handling
|
||||
redirection request
|
||||
*/
|
||||
|
||||
//Check if a request URL is a redirectable URI
|
||||
func (t *RuleTable) IsRedirectable(r *http.Request) bool {
|
||||
requestPath := r.Host + r.URL.Path
|
||||
rr := t.MatchRedirectRule(requestPath)
|
||||
return rr != nil
|
||||
}
|
||||
|
||||
//Handle the redirect request, return after calling this function to prevent
|
||||
//multiple write to the response writer
|
||||
//Return the status code of the redirection handling
|
||||
func (t *RuleTable) HandleRedirect(w http.ResponseWriter, r *http.Request) int {
|
||||
requestPath := r.Host + r.URL.Path
|
||||
rr := t.MatchRedirectRule(requestPath)
|
||||
if rr != nil {
|
||||
redirectTarget := rr.TargetURL
|
||||
//Always pad a / at the back of the target URL
|
||||
if redirectTarget[len(redirectTarget)-1:] != "/" {
|
||||
redirectTarget += "/"
|
||||
}
|
||||
if rr.ForwardChildpath {
|
||||
//Remove the first / in the path
|
||||
redirectTarget += r.URL.Path[1:] + "?" + r.URL.RawQuery
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(redirectTarget, "http://") && !strings.HasPrefix(redirectTarget, "https://") {
|
||||
redirectTarget = "http://" + redirectTarget
|
||||
}
|
||||
|
||||
http.Redirect(w, r, redirectTarget, rr.StatusCode)
|
||||
return rr.StatusCode
|
||||
} else {
|
||||
//Invalid usage
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("500 - Internal Server Error"))
|
||||
log.Println("Target request URL do not have matching redirect rule. Check with IsRedirectable before calling HandleRedirect!")
|
||||
return 500
|
||||
}
|
||||
}
|
162
src/mod/dynamicproxy/redirection/redirection.go
Normal file
162
src/mod/dynamicproxy/redirection/redirection.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package redirection
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
type RuleTable struct {
|
||||
configPath string //The location where the redirection rules is stored
|
||||
rules sync.Map //Store the redirection rules for this reverse proxy instance
|
||||
}
|
||||
|
||||
type RedirectRules struct {
|
||||
RedirectURL string //The matching URL to redirect
|
||||
TargetURL string //The destination redirection url
|
||||
ForwardChildpath bool //Also redirect the pathname
|
||||
StatusCode int //Status Code for redirection
|
||||
}
|
||||
|
||||
func NewRuleTable(configPath string) (*RuleTable, error) {
|
||||
thisRuleTable := RuleTable{
|
||||
rules: sync.Map{},
|
||||
configPath: configPath,
|
||||
}
|
||||
//Load all the rules from the config path
|
||||
if !utils.FileExists(configPath) {
|
||||
os.MkdirAll(configPath, 0775)
|
||||
}
|
||||
|
||||
// Load all the *.json from the configPath
|
||||
files, err := filepath.Glob(filepath.Join(configPath, "*.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse the json content into RedirectRules
|
||||
var rules []*RedirectRules
|
||||
for _, file := range files {
|
||||
b, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
thisRule := RedirectRules{}
|
||||
|
||||
err = json.Unmarshal(b, &thisRule)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
rules = append(rules, &thisRule)
|
||||
}
|
||||
|
||||
//Map the rules into the sync map
|
||||
for _, rule := range rules {
|
||||
log.Println("Redirection rule added: " + rule.RedirectURL + " -> " + rule.TargetURL)
|
||||
thisRuleTable.rules.Store(rule.RedirectURL, rule)
|
||||
}
|
||||
|
||||
return &thisRuleTable, nil
|
||||
}
|
||||
|
||||
func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardPathname bool, statusCode int) error {
|
||||
// Create a new RedirectRules object with the given parameters
|
||||
newRule := &RedirectRules{
|
||||
RedirectURL: redirectURL,
|
||||
TargetURL: destURL,
|
||||
ForwardChildpath: forwardPathname,
|
||||
StatusCode: statusCode,
|
||||
}
|
||||
|
||||
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
|
||||
filename := strings.ReplaceAll(strings.ReplaceAll(redirectURL, "/", "-"), ".", "_") + ".json"
|
||||
|
||||
// Create the full file path by joining the t.configPath with the filename
|
||||
filepath := path.Join(t.configPath, filename)
|
||||
|
||||
// Create a new file for writing the JSON data
|
||||
file, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
log.Printf("Error creating file %s: %s", filepath, err)
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Encode the RedirectRules object to JSON and write it to the file
|
||||
err = json.NewEncoder(file).Encode(newRule)
|
||||
if err != nil {
|
||||
log.Printf("Error encoding JSON to file %s: %s", filepath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Store the RedirectRules object in the sync.Map
|
||||
t.rules.Store(redirectURL, newRule)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *RuleTable) DeleteRedirectRule(redirectURL string) error {
|
||||
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
|
||||
filename := strings.ReplaceAll(strings.ReplaceAll(redirectURL, "/", "-"), ".", "_") + ".json"
|
||||
|
||||
// Create the full file path by joining the t.configPath with the filename
|
||||
filepath := path.Join(t.configPath, filename)
|
||||
|
||||
// Check if the file exists
|
||||
if _, err := os.Stat(filepath); os.IsNotExist(err) {
|
||||
return nil // File doesn't exist, nothing to delete
|
||||
}
|
||||
|
||||
// Delete the file
|
||||
if err := os.Remove(filepath); err != nil {
|
||||
log.Printf("Error deleting file %s: %s", filepath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete the key-value pair from the sync.Map
|
||||
t.rules.Delete(redirectURL)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get a list of all the redirection rules
|
||||
func (t *RuleTable) GetAllRedirectRules() []*RedirectRules {
|
||||
rules := []*RedirectRules{}
|
||||
t.rules.Range(func(key, value interface{}) bool {
|
||||
r, ok := value.(*RedirectRules)
|
||||
if ok {
|
||||
rules = append(rules, r)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return rules
|
||||
}
|
||||
|
||||
// Check if a given request URL matched any of the redirection rule
|
||||
func (t *RuleTable) MatchRedirectRule(requestedURL string) *RedirectRules {
|
||||
// Iterate through all the keys in the rules map
|
||||
var targetRedirectionRule *RedirectRules = nil
|
||||
var maxMatch int = 0
|
||||
|
||||
t.rules.Range(func(key interface{}, value interface{}) bool {
|
||||
// Check if the requested URL starts with the key as a prefix
|
||||
if strings.HasPrefix(requestedURL, key.(string)) {
|
||||
// This request URL matched the domain
|
||||
if len(key.(string)) > maxMatch {
|
||||
maxMatch = len(key.(string))
|
||||
targetRedirectionRule = value.(*RedirectRules)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return targetRedirectionRule
|
||||
}
|
44
src/mod/dynamicproxy/subdomain.go
Normal file
44
src/mod/dynamicproxy/subdomain.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package dynamicproxy
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/url"
|
||||
|
||||
"imuslab.com/zoraxy/mod/reverseproxy"
|
||||
)
|
||||
|
||||
/*
|
||||
Add an URL intoa custom subdomain service
|
||||
|
||||
*/
|
||||
|
||||
func (router *Router) AddSubdomainRoutingService(hostnameWithSubdomain string, domain string, requireTLS bool) error {
|
||||
if domain[len(domain)-1:] == "/" {
|
||||
domain = domain[:len(domain)-1]
|
||||
}
|
||||
|
||||
webProxyEndpoint := domain
|
||||
if requireTLS {
|
||||
webProxyEndpoint = "https://" + webProxyEndpoint
|
||||
} else {
|
||||
webProxyEndpoint = "http://" + webProxyEndpoint
|
||||
}
|
||||
|
||||
//Create a new proxy agent for this root
|
||||
path, err := url.Parse(webProxyEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proxy := reverseproxy.NewReverseProxy(path)
|
||||
|
||||
router.SubdomainEndpoint.Store(hostnameWithSubdomain, &SubdomainEndpoint{
|
||||
MatchingDomain: hostnameWithSubdomain,
|
||||
Domain: domain,
|
||||
RequireTLS: requireTLS,
|
||||
Proxy: proxy,
|
||||
})
|
||||
|
||||
log.Println("Adding Subdomain Rule: ", hostnameWithSubdomain+" to "+domain)
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user