123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- package dynamicproxy
- import (
- "context"
- "crypto/tls"
- "errors"
- "fmt"
- "log"
- "net"
- "net/http"
- "net/url"
- "strconv"
- "strings"
- "sync"
- "time"
- "imuslab.com/arozos/ReverseProxy/mod/dynamicproxy/dpcore"
- "imuslab.com/arozos/ReverseProxy/mod/reverseproxy"
- "imuslab.com/arozos/ReverseProxy/mod/tlscert"
- )
- /*
- Allow users to setup manual proxying for specific path
- */
- type Router struct {
- ListenPort int
- ProxyEndpoints *sync.Map
- SubdomainEndpoint *sync.Map
- Running bool
- Root *ProxyEndpoint
- tlsCertManager *tlscert.Manager
- mux http.Handler
- TlsManager *tlscert.Manager
- useTLS bool
- server *http.Server
- tlsListener net.Listener
- }
- type RouterOption struct {
- Port int
- }
- 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(port int, tlsManager *tlscert.Manager) (*Router, error) {
- proxyMap := sync.Map{}
- domainMap := sync.Map{}
- thisRouter := Router{
- ListenPort: port,
- ProxyEndpoints: &proxyMap,
- SubdomainEndpoint: &domainMap,
- Running: false,
- tlsCertManager: tlsManager,
- useTLS: false,
- server: nil,
- }
- thisRouter.mux = &ProxyHandler{
- Parent: &thisRouter,
- }
- return &thisRouter, nil
- }
- //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.tlsCertManager.GetCert,
- }
- ln, err := tls.Listen("tcp", ":"+strconv.Itoa(router.ListenPort), config)
- if err != nil {
- log.Println(err)
- return err
- }
- router.tlsListener = ln
- router.server = &http.Server{Addr: ":" + strconv.Itoa(router.ListenPort), Handler: router.mux}
- router.Running = true
- go func() {
- if err := router.server.Serve(ln); err != nil && err != http.ErrServerClosed {
- log.Fatalf("Could not start server: %v\n", err)
- }
- }()
- 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
- }
- /*
- 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
- }
- //Do all the main routing in here
- func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- 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
- domainOnly := r.Host
- if strings.Contains(r.Host, ":") {
- hostPath := strings.Split(r.Host, ":")
- domainOnly = hostPath[0]
- }
- sep := h.Parent.getSubdomainProxyEndpointFromHostname(domainOnly)
- if sep != nil {
- h.subdomainRequest(w, r, sep)
- return
- }
- }
- //Clean up the request URI
- proxyingPath := strings.TrimSpace(r.RequestURI)
- /*
- requestingRsc := filepath.Base(r.RequestURI)
- if !strings.Contains(requestingRsc, ".") {
- //Not a file. Add the tail slash if not exists
- if proxyingPath[len(proxyingPath)-1:] != "/" {
- proxyingPath = proxyingPath + "/"
- http.Redirect(w, r, proxyingPath, http.StatusTemporaryRedirect)
- return
- }
- }
- */
- targetProxyEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath)
- if targetProxyEndpoint != nil {
- h.proxyRequest(w, r, targetProxyEndpoint)
- } else {
- h.proxyRequest(w, r, h.Parent.Root)
- }
- }
|