Przeglądaj źródła

auto update script executed

Toby Chui 1 rok temu
rodzic
commit
da8d83d289
14 zmienionych plików z 440 dodań i 270 usunięć
  1. 2 1
      .gitignore
  2. 5 0
      api.go
  3. 10 10
      cert.go
  4. 0 185
      common.go
  5. 4 2
      config.go
  6. 13 3
      main.go
  7. 18 29
      mod/dynamicproxy/dynamicproxy.go
  8. 157 0
      mod/dynamicproxy/redirection/redirection.go
  9. 75 0
      redirect.go
  10. 37 37
      reverseproxy.go
  11. BIN
      sys.db
  12. 2 1
      web/components/cert.html
  13. 109 0
      web/components/redirection.html
  14. 8 2
      web/index.html

+ 2 - 1
.gitignore

@@ -27,4 +27,5 @@ _testmain.go
 
 conf/*
 ReverseProxy_*_*
-certs/*
+certs/*
+rules/*

+ 5 - 0
api.go

@@ -25,4 +25,9 @@ func initAPIs() {
 	http.HandleFunc("/cert/list", handleListCertificate)
 	http.HandleFunc("/cert/checkDefault", handleDefaultCertCheck)
 	http.HandleFunc("/cert/delete", handleCertRemove)
+
+	//Redirection config
+	http.HandleFunc("/redirect/list", handleListRedirectionRules)
+	http.HandleFunc("/redirect/add", handleAddRedirectionRule)
+	http.HandleFunc("/redirect/delete", handleDeleteRedirectionRule)
 }

+ 10 - 10
cert.go

@@ -12,7 +12,7 @@ import (
 	"imuslab.com/arozos/ReverseProxy/mod/utils"
 )
 
-//Check if the default certificates is correctly setup
+// Check if the default certificates is correctly setup
 func handleDefaultCertCheck(w http.ResponseWriter, r *http.Request) {
 	type CheckResult struct {
 		DefaultPubExists bool
@@ -25,10 +25,10 @@ func handleDefaultCertCheck(w http.ResponseWriter, r *http.Request) {
 		pri,
 	})
 
-	sendJSONResponse(w, string(js))
+	utils.SendJSONResponse(w, string(js))
 }
 
-//Return a list of domains where the certificates covers
+// Return a list of domains where the certificates covers
 func handleListCertificate(w http.ResponseWriter, r *http.Request) {
 	filenames, err := tlsCertManager.ListCertDomains()
 	if err != nil {
@@ -48,7 +48,7 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
 		for _, filename := range filenames {
 			fileInfo, err := os.Stat(filepath.Join(tlsCertManager.CertStore, filename+".crt"))
 			if err != nil {
-				sendErrorResponse(w, "invalid domain certificate discovered: "+filename)
+				utils.SendErrorResponse(w, "invalid domain certificate discovered: "+filename)
 				return
 			}
 			modifiedTime := fileInfo.ModTime().Format("2006-01-02 15:04:05")
@@ -77,7 +77,7 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
 
 }
 
-//Handle front-end toggling TLS mode
+// Handle front-end toggling TLS mode
 func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
 	currentTlsSetting := false
 	if sysdb.KeyExists("settings", "usetls") {
@@ -103,12 +103,12 @@ func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 
-		sendOK(w)
+		utils.SendOK(w)
 
 	}
 }
 
-//Handle upload of the certificate
+// Handle upload of the certificate
 func handleCertUpload(w http.ResponseWriter, r *http.Request) {
 	// check if request method is POST
 	if r.Method != "POST" {
@@ -175,15 +175,15 @@ func handleCertUpload(w http.ResponseWriter, r *http.Request) {
 	fmt.Fprintln(w, "File upload successful!")
 }
 
-//Handle cert remove
+// Handle cert remove
 func handleCertRemove(w http.ResponseWriter, r *http.Request) {
 	domain, err := utils.PostPara(r, "domain")
 	if err != nil {
-		sendErrorResponse(w, "invalid domain given")
+		utils.SendErrorResponse(w, "invalid domain given")
 		return
 	}
 	err = tlsCertManager.RemoveCert(domain)
 	if err != nil {
-		sendErrorResponse(w, err.Error())
+		utils.SendErrorResponse(w, err.Error())
 	}
 }

+ 0 - 185
common.go

@@ -1,185 +0,0 @@
-package main
-
-import (
-	"bufio"
-	"encoding/base64"
-	"errors"
-	"io/ioutil"
-	"log"
-	"net/http"
-	"os"
-	"strconv"
-	"strings"
-	"time"
-)
-
-/*
-	Basic Response Functions
-
-	Send response with ease
-*/
-//Send text response with given w and message as string
-func sendTextResponse(w http.ResponseWriter, msg string) {
-	w.Write([]byte(msg))
-}
-
-//Send JSON response, with an extra json header
-func sendJSONResponse(w http.ResponseWriter, json string) {
-	w.Header().Set("Content-Type", "application/json")
-	w.Write([]byte(json))
-}
-
-func sendErrorResponse(w http.ResponseWriter, errMsg string) {
-	w.Header().Set("Content-Type", "application/json")
-	w.Write([]byte("{\"error\":\"" + errMsg + "\"}"))
-}
-
-func sendOK(w http.ResponseWriter) {
-	w.Header().Set("Content-Type", "application/json")
-	w.Write([]byte("\"OK\""))
-}
-
-/*
-	The paramter move function (mv)
-
-	You can find similar things in the PHP version of ArOZ Online Beta. You need to pass in
-	r (HTTP Request Object)
-	getParamter (string, aka $_GET['This string])
-
-	Will return
-	Paramter string (if any)
-	Error (if error)
-
-*/
-func mv(r *http.Request, getParamter string, postMode bool) (string, error) {
-	if postMode == false {
-		//Access the paramter via GET
-		keys, ok := r.URL.Query()[getParamter]
-
-		if !ok || len(keys[0]) < 1 {
-			//log.Println("Url Param " + getParamter +" is missing")
-			return "", errors.New("GET paramter " + getParamter + " not found or it is empty")
-		}
-
-		// Query()["key"] will return an array of items,
-		// we only want the single item.
-		key := keys[0]
-		return string(key), nil
-	} else {
-		//Access the parameter via POST
-		r.ParseForm()
-		x := r.Form.Get(getParamter)
-		if len(x) == 0 || x == "" {
-			return "", errors.New("POST paramter " + getParamter + " not found or it is empty")
-		}
-		return string(x), nil
-	}
-
-}
-
-func stringInSlice(a string, list []string) bool {
-	for _, b := range list {
-		if b == a {
-			return true
-		}
-	}
-	return false
-}
-
-func fileExists(filename string) bool {
-	_, err := os.Stat(filename)
-	if os.IsNotExist(err) {
-		return false
-	}
-	return true
-}
-
-func IsDir(path string) bool {
-	if fileExists(path) == false {
-		return false
-	}
-	fi, err := os.Stat(path)
-	if err != nil {
-		log.Fatal(err)
-		return false
-	}
-	switch mode := fi.Mode(); {
-	case mode.IsDir():
-		return true
-	case mode.IsRegular():
-		return false
-	}
-	return false
-}
-
-func inArray(arr []string, str string) bool {
-	for _, a := range arr {
-		if a == str {
-			return true
-		}
-	}
-	return false
-}
-
-func timeToString(targetTime time.Time) string {
-	return targetTime.Format("2006-01-02 15:04:05")
-}
-
-func IntToString(number int) string {
-	return strconv.Itoa(number)
-}
-
-func StringToInt(number string) (int, error) {
-	return strconv.Atoi(number)
-}
-
-func StringToInt64(number string) (int64, error) {
-	i, err := strconv.ParseInt(number, 10, 64)
-	if err != nil {
-		return -1, err
-	}
-	return i, nil
-}
-
-func Int64ToString(number int64) string {
-	convedNumber := strconv.FormatInt(number, 10)
-	return convedNumber
-}
-
-func GetUnixTime() int64 {
-	return time.Now().Unix()
-}
-
-func LoadImageAsBase64(filepath string) (string, error) {
-	if !fileExists(filepath) {
-		return "", errors.New("File not exists")
-	}
-	f, _ := os.Open(filepath)
-	reader := bufio.NewReader(f)
-	content, _ := ioutil.ReadAll(reader)
-	encoded := base64.StdEncoding.EncodeToString(content)
-	return string(encoded), nil
-}
-
-//Get the IP address of the current authentication user
-func getUserIPAddr(w http.ResponseWriter, r *http.Request) {
-	requestPort, _ := mv(r, "port", false)
-	showPort := false
-	if requestPort == "true" {
-		//Show port as well
-		showPort = true
-	}
-	IPAddress := r.Header.Get("X-Real-Ip")
-	if IPAddress == "" {
-		IPAddress = r.Header.Get("X-Forwarded-For")
-	}
-	if IPAddress == "" {
-		IPAddress = r.RemoteAddr
-	}
-	if !showPort {
-		IPAddress = IPAddress[:strings.LastIndex(IPAddress, ":")]
-
-	}
-	w.Write([]byte(IPAddress))
-	return
-}

+ 4 - 2
config.go

@@ -7,6 +7,8 @@ import (
 	"os"
 	"path/filepath"
 	"strings"
+
+	"imuslab.com/arozos/ReverseProxy/mod/utils"
 )
 
 type Record struct {
@@ -37,7 +39,7 @@ func RemoveReverseProxyConfig(rootname string) error {
 	filename := getFilenameFromRootName(rootname)
 	removePendingFile := strings.ReplaceAll(filepath.Join("conf", filename), "\\", "/")
 	log.Println("Config Removed: ", removePendingFile)
-	if fileExists(removePendingFile) {
+	if utils.FileExists(removePendingFile) {
 		err := os.Remove(removePendingFile)
 		if err != nil {
 			log.Println(err.Error())
@@ -49,7 +51,7 @@ func RemoveReverseProxyConfig(rootname string) error {
 	return nil
 }
 
-//Return ptype, rootname and proxyTarget, error if any
+// Return ptype, rootname and proxyTarget, error if any
 func LoadReverseProxyConfig(filename string) (*Record, error) {
 	thisRecord := Record{}
 	configContent, err := ioutil.ReadFile(filename)

+ 13 - 3
main.go

@@ -10,6 +10,7 @@ import (
 
 	"imuslab.com/arozos/ReverseProxy/mod/aroz"
 	"imuslab.com/arozos/ReverseProxy/mod/database"
+	"imuslab.com/arozos/ReverseProxy/mod/dynamicproxy/redirection"
 	"imuslab.com/arozos/ReverseProxy/mod/tlscert"
 )
 
@@ -17,9 +18,10 @@ var (
 	handler        *aroz.ArozHandler
 	sysdb          *database.Database
 	tlsCertManager *tlscert.Manager
+	redirectTable  *redirection.RuleTable
 )
 
-//Kill signal handler. Do something before the system the core terminate.
+// Kill signal handler. Do something before the system the core terminate.
 func SetupCloseHandler() {
 	c := make(chan os.Signal, 2)
 	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
@@ -61,12 +63,20 @@ func main() {
 		log.Fatal(err)
 	}
 	sysdb = db
-
 	//Create tables for the database
 	sysdb.NewTable("settings")
 
 	//Create a TLS certificate manager
-	tlsCertManager, _ = tlscert.NewManager("./certs")
+	tlsCertManager, err = tlscert.NewManager("./certs")
+	if err != nil {
+		panic(err)
+	}
+
+	//Create a redirection rule table
+	redirectTable, err = redirection.NewRuleTable("./rules")
+	if err != nil {
+		panic(err)
+	}
 
 	//Start the reverse proxy server in go routine
 	go func() {

+ 18 - 29
mod/dynamicproxy/dynamicproxy.go

@@ -20,8 +20,7 @@ import (
 )
 
 /*
-	Allow users to setup manual proxying for specific path
-
+Allow users to setup manual proxying for specific path
 */
 type Router struct {
 	ListenPort             int
@@ -84,20 +83,20 @@ func NewDynamicProxy(option RouterOption) (*Router, error) {
 	return &thisRouter, nil
 }
 
-//Update TLS setting in runtime. Will restart the proxy server
-//if it is already running in the background
+// 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.useTLS = tlsEnabled
 	router.Restart()
 }
 
-//Update https redirect, which will require updates
+// Update https redirect, which will require updates
 func (router *Router) UpdateHttpToHttpsRedirectSetting(useRedirect bool) {
 	router.useHttpToHttpsRedirect = useRedirect
 	router.Restart()
 }
 
-//Start the dynamic routing
+// Start the dynamic routing
 func (router *Router) StartProxyService() error {
 	//Create a new server object
 	if router.server != nil {
@@ -195,8 +194,8 @@ func (router *Router) StopProxyService() error {
 	return nil
 }
 
-//Restart the current router if it is running.
-//Startup the server if it is not running initially
+// 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 {
@@ -212,7 +211,7 @@ func (router *Router) Restart() error {
 }
 
 /*
-	Add an URL into a custom proxy services
+Add an URL into a custom proxy services
 */
 func (router *Router) AddVirtualDirectoryProxyService(rootname string, domain string, requireTLS bool) error {
 	if domain[len(domain)-1:] == "/" {
@@ -251,8 +250,7 @@ func (router *Router) AddVirtualDirectoryProxyService(rootname string, domain st
 }
 
 /*
-	Remove routing from RP
-
+Remove routing from RP
 */
 func (router *Router) RemoveProxy(ptype string, key string) error {
 	fmt.Println(ptype, key)
@@ -267,7 +265,7 @@ func (router *Router) RemoveProxy(ptype string, key string) error {
 }
 
 /*
-	Add an default router for the proxy server
+Add an default router for the proxy server
 */
 func (router *Router) SetRootProxy(proxyLocation string, requireTLS bool) error {
 	if proxyLocation[len(proxyLocation)-1:] == "/" {
@@ -299,16 +297,18 @@ func (router *Router) SetRootProxy(proxyLocation string, requireTLS bool) error
 	return nil
 }
 
-//Do all the main routing in here
+// Do all the main routing in here
 func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	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
-		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)
@@ -318,17 +318,6 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	//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 {

+ 157 - 0
mod/dynamicproxy/redirection/redirection.go

@@ -0,0 +1,157 @@
+package redirection
+
+import (
+	"encoding/json"
+	"log"
+	"os"
+	"path"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"imuslab.com/arozos/ReverseProxy/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
+	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
+
+			return true
+		}
+		return false
+	})
+
+	return nil
+}

+ 75 - 0
redirect.go

@@ -0,0 +1,75 @@
+package main
+
+import (
+	"encoding/json"
+	"net/http"
+	"strconv"
+
+	"imuslab.com/arozos/ReverseProxy/mod/utils"
+)
+
+/*
+	Redirect.go
+
+	This script handle all the http handlers
+	related to redirection function in the reverse proxy
+*/
+
+func handleListRedirectionRules(w http.ResponseWriter, r *http.Request) {
+	rules := redirectTable.GetAllRedirectRules()
+	js, _ := json.Marshal(rules)
+	utils.SendJSONResponse(w, string(js))
+}
+
+func handleAddRedirectionRule(w http.ResponseWriter, r *http.Request) {
+	redirectUrl, err := utils.PostPara(r, "redirectUrl")
+	if err != nil {
+		utils.SendErrorResponse(w, "redirect url cannot be empty")
+		return
+	}
+	destUrl, err := utils.PostPara(r, "destUrl")
+	if err != nil {
+		utils.SendErrorResponse(w, "destination url cannot be empty")
+	}
+
+	forwardChildpath, err := utils.PostPara(r, "forwardChildpath")
+	if err != nil {
+		//Assume true
+		forwardChildpath = "true"
+	}
+
+	redirectTypeString, err := utils.PostPara(r, "redirectType")
+	if err != nil {
+		redirectTypeString = "307"
+	}
+
+	redirectionStatusCode, err := strconv.Atoi(redirectTypeString)
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid status code number")
+		return
+	}
+
+	err = redirectTable.AddRedirectRule(redirectUrl, destUrl, forwardChildpath == "true", redirectionStatusCode)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	utils.SendOK(w)
+}
+
+func handleDeleteRedirectionRule(w http.ResponseWriter, r *http.Request) {
+	redirectUrl, err := utils.PostPara(r, "redirectUrl")
+	if err != nil {
+		utils.SendErrorResponse(w, "redirect url cannot be empty")
+		return
+	}
+
+	err = redirectTable.DeleteRedirectRule(redirectUrl)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	utils.SendOK(w)
+}

+ 37 - 37
reverseproxy.go

@@ -16,7 +16,7 @@ var (
 	dynamicProxyRouter *dynamicproxy.Router
 )
 
-//Add user customizable reverse proxy
+// Add user customizable reverse proxy
 func ReverseProxtInit() {
 	inboundPort := 80
 	if sysdb.KeyExists("settings", "inbound") {
@@ -90,38 +90,38 @@ func ReverseProxtInit() {
 }
 
 func ReverseProxyHandleOnOff(w http.ResponseWriter, r *http.Request) {
-	enable, _ := mv(r, "enable", true) //Support root, vdir and subd
+	enable, _ := utils.PostPara(r, "enable") //Support root, vdir and subd
 	if enable == "true" {
 		err := dynamicProxyRouter.StartProxyService()
 		if err != nil {
-			sendErrorResponse(w, err.Error())
+			utils.SendErrorResponse(w, err.Error())
 			return
 		}
 	} else {
 		err := dynamicProxyRouter.StopProxyService()
 		if err != nil {
-			sendErrorResponse(w, err.Error())
+			utils.SendErrorResponse(w, err.Error())
 			return
 		}
 	}
 
-	sendOK(w)
+	utils.SendOK(w)
 }
 
 func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
-	eptype, err := mv(r, "type", true) //Support root, vdir and subd
+	eptype, err := utils.PostPara(r, "type") //Support root, vdir and subd
 	if err != nil {
-		sendErrorResponse(w, "type not defined")
+		utils.SendErrorResponse(w, "type not defined")
 		return
 	}
 
-	endpoint, err := mv(r, "ep", true)
+	endpoint, err := utils.PostPara(r, "ep")
 	if err != nil {
-		sendErrorResponse(w, "endpoint not defined")
+		utils.SendErrorResponse(w, "endpoint not defined")
 		return
 	}
 
-	tls, _ := mv(r, "tls", true)
+	tls, _ := utils.PostPara(r, "tls")
 	if tls == "" {
 		tls = "false"
 	}
@@ -129,9 +129,9 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
 	useTLS := (tls == "true")
 	rootname := ""
 	if eptype == "vdir" {
-		vdir, err := mv(r, "rootname", true)
+		vdir, err := utils.PostPara(r, "rootname")
 		if err != nil {
-			sendErrorResponse(w, "vdir not defined")
+			utils.SendErrorResponse(w, "vdir not defined")
 			return
 		}
 
@@ -142,9 +142,9 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
 		dynamicProxyRouter.AddVirtualDirectoryProxyService(vdir, endpoint, useTLS)
 
 	} else if eptype == "subd" {
-		subdomain, err := mv(r, "rootname", true)
+		subdomain, err := utils.PostPara(r, "rootname")
 		if err != nil {
-			sendErrorResponse(w, "subdomain not defined")
+			utils.SendErrorResponse(w, "subdomain not defined")
 			return
 		}
 		rootname = subdomain
@@ -154,46 +154,46 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
 		dynamicProxyRouter.SetRootProxy(endpoint, useTLS)
 	} else {
 		//Invalid eptype
-		sendErrorResponse(w, "Invalid endpoint type")
+		utils.SendErrorResponse(w, "Invalid endpoint type")
 		return
 	}
 
 	//Save it
 	SaveReverseProxyConfig(eptype, rootname, endpoint, useTLS)
 
-	sendOK(w)
+	utils.SendOK(w)
 
 }
 
 func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
-	ep, err := mv(r, "ep", true)
+	ep, err := utils.GetPara(r, "ep")
 	if err != nil {
-		sendErrorResponse(w, "Invalid ep given")
+		utils.SendErrorResponse(w, "Invalid ep given")
 	}
 
-	ptype, err := mv(r, "ptype", true)
+	ptype, err := utils.PostPara(r, "ptype")
 	if err != nil {
-		sendErrorResponse(w, "Invalid ptype given")
+		utils.SendErrorResponse(w, "Invalid ptype given")
 	}
 
 	err = dynamicProxyRouter.RemoveProxy(ptype, ep)
 	if err != nil {
-		sendErrorResponse(w, err.Error())
+		utils.SendErrorResponse(w, err.Error())
 	}
 
 	RemoveReverseProxyConfig(ep)
-	sendOK(w)
+	utils.SendOK(w)
 }
 
 func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) {
 	js, _ := json.Marshal(dynamicProxyRouter)
-	sendJSONResponse(w, string(js))
+	utils.SendJSONResponse(w, string(js))
 }
 
 func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
-	eptype, err := mv(r, "type", true) //Support root, vdir and subd
+	eptype, err := utils.PostPara(r, "type") //Support root, vdir and subd
 	if err != nil {
-		sendErrorResponse(w, "type not defined")
+		utils.SendErrorResponse(w, "type not defined")
 		return
 	}
 
@@ -205,7 +205,7 @@ func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
 		})
 
 		js, _ := json.Marshal(results)
-		sendJSONResponse(w, string(js))
+		utils.SendJSONResponse(w, string(js))
 	} else if eptype == "subd" {
 		results := []*dynamicproxy.SubdomainEndpoint{}
 		dynamicProxyRouter.SubdomainEndpoint.Range(func(key, value interface{}) bool {
@@ -213,16 +213,16 @@ func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
 			return true
 		})
 		js, _ := json.Marshal(results)
-		sendJSONResponse(w, string(js))
+		utils.SendJSONResponse(w, string(js))
 	} else if eptype == "root" {
 		js, _ := json.Marshal(dynamicProxyRouter.Root)
-		sendJSONResponse(w, string(js))
+		utils.SendJSONResponse(w, string(js))
 	} else {
-		sendErrorResponse(w, "Invalid type given")
+		utils.SendErrorResponse(w, "Invalid type given")
 	}
 }
 
-//Handle https redirect
+// Handle https redirect
 func HandleUpdateHttpsRedirect(w http.ResponseWriter, r *http.Request) {
 	useRedirect, err := utils.GetPara(r, "set")
 	if err != nil {
@@ -230,11 +230,11 @@ func HandleUpdateHttpsRedirect(w http.ResponseWriter, r *http.Request) {
 		//Load the current status
 		err = sysdb.Read("settings", "redirect", &currentRedirectToHttps)
 		if err != nil {
-			sendErrorResponse(w, err.Error())
+			utils.SendErrorResponse(w, err.Error())
 			return
 		}
 		js, _ := json.Marshal(currentRedirectToHttps)
-		sendJSONResponse(w, string(js))
+		utils.SendJSONResponse(w, string(js))
 	} else {
 		if useRedirect == "true" {
 			sysdb.Write("settings", "redirect", true)
@@ -250,17 +250,17 @@ func HandleUpdateHttpsRedirect(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-//Handle incoming port set. Change the current proxy incoming port
+// Handle incoming port set. Change the current proxy incoming port
 func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
-	newIncomingPort, err := mv(r, "incoming", true)
+	newIncomingPort, err := utils.PostPara(r, "incoming")
 	if err != nil {
-		sendErrorResponse(w, "invalid incoming port given")
+		utils.SendErrorResponse(w, "invalid incoming port given")
 		return
 	}
 
 	newIncomingPortInt, err := strconv.Atoi(newIncomingPort)
 	if err != nil {
-		sendErrorResponse(w, "invalid incoming port given")
+		utils.SendErrorResponse(w, "invalid incoming port given")
 		return
 	}
 
@@ -276,5 +276,5 @@ func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
 
 	sysdb.Write("settings", "inbound", newIncomingPortInt)
 
-	sendOK((w))
+	utils.SendOK(w)
 }

BIN
sys.db


+ 2 - 1
web/components/cert.html

@@ -46,7 +46,7 @@
     <i class="ui checkmark icon"></i> Certificate for domain <span id="certUploadingDomain"></span> uploaded.
 </div>
 <br>
-<div >
+<div>
     <table class="ui very basic celled table">
         <thead>
           <tr><th>Domain</th>
@@ -57,6 +57,7 @@
 
     </tbody>
     </table>
+    <button class="ui green basic button" onclick="initManagedDomainCertificateList();"><i class="refresh icon"></i> Refresh List</button>
 </div>
 <div class="ui message">
     <h4><i class="info circle icon"></i> Sub-domain Certificates</h4>

+ 109 - 0
web/components/redirection.html

@@ -0,0 +1,109 @@
+<h3><i class="level up alternate icon"></i> Redirection Rules</h3>
+<p>Add exception case for redirecting any matching URLs</p>
+<div class="ui basic segment">
+    <table class="ui very basic celled table">
+        <thead>
+            <tr>
+                <th>Redirection URL</th>
+                <th>Destination URL</th>
+                <th>Copy Pathname</th>
+                <th>Status Code</th>
+            </tr>
+        </thead>
+        <tbody id="redirectionRuleList">
+            <tr>
+                <td></td>
+                <td></td>
+                <td></td>
+                <td></td>
+            </tr>
+        </tbody>
+    </table>
+</div>
+<div class="ui divider"></div>
+<p>Add path redirection to your domain</p>
+<div class="ui divider"></div>
+<div class="ui form">
+    <div class="field">
+      <label>Redirection URL</label>
+      <input type="text" name="redirection-url" placeholder="Redirection URL">
+      <small><i class="ui circle info icon"></i> Redirection URL, any matching prefix of the request URL will be redirected to the following destination URL.</small>
+    </div>
+    <div class="field">
+      <label>Destination URL</label>
+      <input type="text" name="destination-url" placeholder="Destination URL">
+    </div>
+    <div class="field">
+      <div class="ui checkbox">
+        <input type="checkbox" name="forward-childpath" tabindex="0" class="hidden" checked>
+        <label>Forward Pathname</label>
+      </div>
+      <div class="ui message">
+        <p>Append the current pathname after the redirect destination</p>
+        <i class="check square outline icon"></i> old.example.com<b>/blog</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> new.example.com<b>/blog</b> <br>
+        <i class="square outline icon"></i> Disabled old.example.com<b>/blog</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> new.example.com
+      </div>
+    </div>
+    <div class="grouped fields">
+        <label>Redirection Status Code</label>
+        <div class="field">
+          <div class="ui radio checkbox">
+            <input type="radio" name="redirect-type" value="307" checked>
+            <label>Temporary Redirect <br><small>Status Code: 307</small></label>
+          </div>
+        </div>
+        <div class="field">
+          <div class="ui radio checkbox">
+            <input type="radio" name="redirect-type" value="301">
+            <label>Moved Permanently <br><small>Status Code: 301</small></label>
+          </div>
+        </div>
+    </div>
+    <button class="ui button" onclick="addRules();">Add</button>
+</div>
+<script>
+    $(".checkbox").checkbox();
+
+    function addRules(){
+        let redirectUrl = document.querySelector('input[name="redirection-url"]').value;
+        let destUrl = document.querySelector('input[name="destination-url"]').value;
+        let forwardChildpath = document.querySelector('input[name="forward-childpath"]').checked;
+        let redirectType = document.querySelector('input[name="redirect-type"]:checked').value;
+
+        $.ajax({
+            url: "/redirect/add", 
+            method: "POST",
+            data: {
+                redirectUrl: redirectUrl,
+                destUrl: destUrl,
+                forwardChildpath: forwardChildpath,
+                redirectType: parseInt(redirectType),
+            },
+            success: function(data){
+                alert(data);
+            }
+        })
+    }
+
+    function deleteRule(redirectDomain){
+
+    }
+
+    function initRedirectionRuleList(){
+        $("#redirectionRuleList").html("");
+        $.get("/redirect/list", function(data){
+            console.log(data);
+            data.forEach(function(entry){
+                $("#redirectionRuleList").append(`<tr>
+                    <td>${entry.RedirectURL}</td>
+                    <td>${entry.TargetURL}</td>
+                    <td>${entry.ForwardChildpath?"<i class='ui green checkmark icon'></i>":"<i class='ui red remove icon'></i>"}</td>
+                    <td>${entry.StatusCode==307?"Temporary Redirect (307)":"Moved Permanently (301)"}</td>
+                </tr>`);
+            });
+            
+        });
+    }
+    initRedirectionRuleList();
+    
+</script>

+ 8 - 2
web/index.html

@@ -49,6 +49,9 @@
                         <a class="item" tag="cert">
                             <i class="lock icon"></i> TLS / SSL certificate
                         </a>
+                        <a class="item" tag="redirectset">
+                            <i class="level up alternate icon"></i> Redirection
+                        </a>
                     </div>
                 </div>
                 <div class="twelve wide column">
@@ -69,8 +72,11 @@
                     <!-- Set proxy root -->
                     <div id="setroot" class="functiontab" target="rproot.html"></div>
 
-                     <!-- Set TLS cert -->
-                     <div id="cert" class="functiontab" target="cert.html"></div>
+                    <!-- Set TLS cert -->
+                    <div id="cert" class="functiontab" target="cert.html"></div>
+
+                    <!-- Redirections -->
+                    <div id="redirectset" class="functiontab" target="redirection.html"></div>
                 </div>
               </div>
             </div>