فهرست منبع

auto update script executed

Toby Chui 1 سال پیش
والد
کامیت
882444df47

+ 2 - 2
api.go

@@ -4,8 +4,8 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"net/http"
 	"net/http"
 
 
-	"imuslab.com/arozos/ReverseProxy/mod/auth"
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/auth"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 )
 
 
 /*
 /*

+ 1 - 1
blacklist.go

@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"net/http"
 	"net/http"
 
 
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 )
 
 
 /*
 /*

+ 1 - 1
cert.go

@@ -9,7 +9,7 @@ import (
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
 
 
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 )
 
 
 // Check if the default certificates is correctly setup
 // Check if the default certificates is correctly setup

+ 1 - 1
config.go

@@ -8,7 +8,7 @@ import (
 	"path/filepath"
 	"path/filepath"
 	"strings"
 	"strings"
 
 
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 )
 
 
 type Record struct {
 type Record struct {

+ 2 - 1
go.mod

@@ -1,4 +1,4 @@
-module imuslab.com/arozos/ReverseProxy
+module imuslab.com/Zoarxy
 
 
 go 1.16
 go 1.16
 
 
@@ -7,5 +7,6 @@ require (
 	github.com/gorilla/sessions v1.2.1
 	github.com/gorilla/sessions v1.2.1
 	github.com/gorilla/websocket v1.4.2
 	github.com/gorilla/websocket v1.4.2
 	github.com/oschwald/geoip2-golang v1.8.0
 	github.com/oschwald/geoip2-golang v1.8.0
+	github.com/syndtr/goleveldb v1.0.0 // indirect
 	golang.org/x/sys v0.6.0 // indirect
 	golang.org/x/sys v0.6.0 // indirect
 )
 )

+ 17 - 0
go.sum

@@ -3,12 +3,20 @@ github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx2
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
 github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
 github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
 github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
 github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs=
 github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs=
 github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw=
 github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw=
 github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
 github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
@@ -21,10 +29,19 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.3/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.7.3/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
 golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 22 - 12
main.go

@@ -10,12 +10,13 @@ import (
 	"syscall"
 	"syscall"
 	"time"
 	"time"
 
 
-	"imuslab.com/arozos/ReverseProxy/mod/aroz"
-	"imuslab.com/arozos/ReverseProxy/mod/auth"
-	"imuslab.com/arozos/ReverseProxy/mod/database"
-	"imuslab.com/arozos/ReverseProxy/mod/dynamicproxy/redirection"
-	"imuslab.com/arozos/ReverseProxy/mod/geodb"
-	"imuslab.com/arozos/ReverseProxy/mod/tlscert"
+	"imuslab.com/Zoarxy/mod/aroz"
+	"imuslab.com/Zoarxy/mod/auth"
+	"imuslab.com/Zoarxy/mod/database"
+	"imuslab.com/Zoarxy/mod/dynamicproxy/redirection"
+	"imuslab.com/Zoarxy/mod/geodb"
+	"imuslab.com/Zoarxy/mod/statistic"
+	"imuslab.com/Zoarxy/mod/tlscert"
 )
 )
 
 
 //General flags
 //General flags
@@ -26,12 +27,13 @@ var (
 	name    = "Zoraxy"
 	name    = "Zoraxy"
 	version = "2.1"
 	version = "2.1"
 
 
-	handler        *aroz.ArozHandler
-	sysdb          *database.Database
-	authAgent      *auth.AuthAgent
-	tlsCertManager *tlscert.Manager
-	redirectTable  *redirection.RuleTable
-	geodbStore     *geodb.Store
+	handler            *aroz.ArozHandler
+	sysdb              *database.Database
+	authAgent          *auth.AuthAgent
+	tlsCertManager     *tlscert.Manager
+	redirectTable      *redirection.RuleTable
+	geodbStore         *geodb.Store
+	statisticCollector *statistic.Collector
 )
 )
 
 
 // Kill signal handler. Do something before the system the core terminate.
 // Kill signal handler. Do something before the system the core terminate.
@@ -107,6 +109,14 @@ func main() {
 		panic(err)
 		panic(err)
 	}
 	}
 
 
+	//Create a statistic collector
+	statisticCollector, err = statistic.NewStatisticCollector(statistic.CollectorOption{
+		Database: sysdb,
+	})
+	if err != nil {
+		panic(err)
+	}
+
 	//Initiate management interface APIs
 	//Initiate management interface APIs
 	initAPIs()
 	initAPIs()
 
 

+ 2 - 2
mod/auth/auth.go

@@ -17,8 +17,8 @@ import (
 	"log"
 	"log"
 
 
 	"github.com/gorilla/sessions"
 	"github.com/gorilla/sessions"
-	db "imuslab.com/arozos/ReverseProxy/mod/database"
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	db "imuslab.com/Zoarxy/mod/database"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 )
 
 
 type AuthAgent struct {
 type AuthAgent struct {

+ 4 - 3
mod/dynamicproxy/Server.go

@@ -5,7 +5,7 @@ import (
 	"os"
 	"os"
 	"strings"
 	"strings"
 
 
-	"imuslab.com/arozos/ReverseProxy/mod/geodb"
+	"imuslab.com/Zoarxy/mod/geodb"
 )
 )
 
 
 /*
 /*
@@ -26,13 +26,14 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		} else {
 		} else {
 			w.Write(template)
 			w.Write(template)
 		}
 		}
-
+		h.logRequest(r, false, 403, "blacklist")
 		return
 		return
 	}
 	}
 
 
 	//Check if this is a redirection url
 	//Check if this is a redirection url
 	if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) {
 	if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) {
-		h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r)
+		statusCode := h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r)
+		h.logRequest(r, statusCode != 500, statusCode, "redirect")
 		return
 		return
 	}
 	}
 
 

+ 7 - 5
mod/dynamicproxy/dynamicproxy.go

@@ -12,11 +12,12 @@ import (
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
-	"imuslab.com/arozos/ReverseProxy/mod/dynamicproxy/dpcore"
-	"imuslab.com/arozos/ReverseProxy/mod/dynamicproxy/redirection"
-	"imuslab.com/arozos/ReverseProxy/mod/geodb"
-	"imuslab.com/arozos/ReverseProxy/mod/reverseproxy"
-	"imuslab.com/arozos/ReverseProxy/mod/tlscert"
+	"imuslab.com/Zoarxy/mod/dynamicproxy/dpcore"
+	"imuslab.com/Zoarxy/mod/dynamicproxy/redirection"
+	"imuslab.com/Zoarxy/mod/geodb"
+	"imuslab.com/Zoarxy/mod/reverseproxy"
+	"imuslab.com/Zoarxy/mod/statistic"
+	"imuslab.com/Zoarxy/mod/tlscert"
 )
 )
 
 
 /*
 /*
@@ -29,6 +30,7 @@ type RouterOption struct {
 	TlsManager         *tlscert.Manager
 	TlsManager         *tlscert.Manager
 	RedirectRuleTable  *redirection.RuleTable
 	RedirectRuleTable  *redirection.RuleTable
 	GeodbStore         *geodb.Store
 	GeodbStore         *geodb.Store
+	StatisticCollector *statistic.Collector
 }
 }
 
 
 type Router struct {
 type Router struct {

+ 30 - 1
mod/dynamicproxy/proxyRequestHandler.go

@@ -2,13 +2,16 @@ package dynamicproxy
 
 
 import (
 import (
 	"errors"
 	"errors"
+	"fmt"
 	"log"
 	"log"
 	"net"
 	"net"
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
 	"strings"
 	"strings"
 
 
-	"imuslab.com/arozos/ReverseProxy/mod/websocketproxy"
+	"imuslab.com/Zoarxy/mod/geodb"
+	"imuslab.com/Zoarxy/mod/statistic"
+	"imuslab.com/Zoarxy/mod/websocketproxy"
 )
 )
 
 
 func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *ProxyEndpoint {
 func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *ProxyEndpoint {
@@ -67,6 +70,7 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request,
 		if target.RequireTLS {
 		if target.RequireTLS {
 			u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
 			u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
 		}
 		}
+		h.logRequest(r, true, 101, "subdomain-websocket")
 		wspHandler := websocketproxy.NewProxy(u)
 		wspHandler := websocketproxy.NewProxy(u)
 		wspHandler.ServeHTTP(w, r)
 		wspHandler.ServeHTTP(w, r)
 		return
 		return
@@ -79,11 +83,15 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request,
 		if errors.As(err, &dnsError) {
 		if errors.As(err, &dnsError) {
 			http.ServeFile(w, r, "./web/hosterror.html")
 			http.ServeFile(w, r, "./web/hosterror.html")
 			log.Println(err.Error())
 			log.Println(err.Error())
+			h.logRequest(r, false, 404, "subdomain-http")
 		} else {
 		} else {
 			http.ServeFile(w, r, "./web/rperror.html")
 			http.ServeFile(w, r, "./web/rperror.html")
 			log.Println(err.Error())
 			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) {
 func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
@@ -101,6 +109,7 @@ func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, targ
 		if target.RequireTLS {
 		if target.RequireTLS {
 			u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
 			u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
 		}
 		}
+		h.logRequest(r, true, 101, "vdir-websocket")
 		wspHandler := websocketproxy.NewProxy(u)
 		wspHandler := websocketproxy.NewProxy(u)
 		wspHandler.ServeHTTP(w, r)
 		wspHandler.ServeHTTP(w, r)
 		return
 		return
@@ -113,10 +122,30 @@ func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, targ
 		if errors.As(err, &dnsError) {
 		if errors.As(err, &dnsError) {
 			http.ServeFile(w, r, "./web/hosterror.html")
 			http.ServeFile(w, r, "./web/hosterror.html")
 			log.Println(err.Error())
 			log.Println(err.Error())
+			h.logRequest(r, false, 404, "vdir-http")
 		} else {
 		} else {
 			http.ServeFile(w, r, "./web/rperror.html")
 			http.ServeFile(w, r, "./web/rperror.html")
 			log.Println(err.Error())
 			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,
+			}
+			fmt.Println(requestInfo)
+			h.Parent.Option.StatisticCollector.RecordRequest(requestInfo)
+		}()
+
+	}
 }
 }

+ 4 - 1
mod/dynamicproxy/redirection/handler.go

@@ -22,7 +22,8 @@ func (t *RuleTable) IsRedirectable(r *http.Request) bool {
 
 
 //Handle the redirect request, return after calling this function to prevent
 //Handle the redirect request, return after calling this function to prevent
 //multiple write to the response writer
 //multiple write to the response writer
-func (t *RuleTable) HandleRedirect(w http.ResponseWriter, r *http.Request) {
+//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
 	requestPath := r.Host + r.URL.Path
 	rr := t.MatchRedirectRule(requestPath)
 	rr := t.MatchRedirectRule(requestPath)
 	if rr != nil {
 	if rr != nil {
@@ -41,10 +42,12 @@ func (t *RuleTable) HandleRedirect(w http.ResponseWriter, r *http.Request) {
 		}
 		}
 
 
 		http.Redirect(w, r, redirectTarget, rr.StatusCode)
 		http.Redirect(w, r, redirectTarget, rr.StatusCode)
+		return rr.StatusCode
 	} else {
 	} else {
 		//Invalid usage
 		//Invalid usage
 		w.WriteHeader(http.StatusInternalServerError)
 		w.WriteHeader(http.StatusInternalServerError)
 		w.Write([]byte("500 - Internal Server Error"))
 		w.Write([]byte("500 - Internal Server Error"))
 		log.Println("Target request URL do not have matching redirect rule. Check with IsRedirectable before calling HandleRedirect!")
 		log.Println("Target request URL do not have matching redirect rule. Check with IsRedirectable before calling HandleRedirect!")
+		return 500
 	}
 	}
 }
 }

+ 1 - 1
mod/dynamicproxy/redirection/redirection.go

@@ -9,7 +9,7 @@ import (
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 
 
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 )
 
 
 type RuleTable struct {
 type RuleTable struct {

+ 1 - 1
mod/dynamicproxy/subdomain.go

@@ -4,7 +4,7 @@ import (
 	"log"
 	"log"
 	"net/url"
 	"net/url"
 
 
-	"imuslab.com/arozos/ReverseProxy/mod/reverseproxy"
+	"imuslab.com/Zoarxy/mod/reverseproxy"
 )
 )
 
 
 /*
 /*

+ 14 - 1
mod/geodb/geodb.go

@@ -6,7 +6,7 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/oschwald/geoip2-golang"
 	"github.com/oschwald/geoip2-golang"
-	"imuslab.com/arozos/ReverseProxy/mod/database"
+	"imuslab.com/Zoarxy/mod/database"
 )
 )
 
 
 type Store struct {
 type Store struct {
@@ -176,6 +176,19 @@ func (s *Store) IsBlacklisted(ipAddr string) bool {
 	return false
 	return false
 }
 }
 
 
+func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {
+	ipAddr := GetRequesterIP(r)
+	if ipAddr == "" {
+		return ""
+	}
+	countryCode, err := s.ResolveCountryCodeFromIP(ipAddr)
+	if err != nil {
+		return ""
+	}
+
+	return countryCode.CountryIsoCode
+}
+
 //Utilities function
 //Utilities function
 func GetRequesterIP(r *http.Request) string {
 func GetRequesterIP(r *http.Request) string {
 	ip := r.Header.Get("X-Forwarded-For")
 	ip := r.Header.Get("X-Forwarded-For")

+ 20 - 0
mod/statistic/handler.go

@@ -0,0 +1,20 @@
+package statistic
+
+import (
+	"encoding/json"
+	"net/http"
+
+	"imuslab.com/Zoarxy/mod/utils"
+)
+
+/*
+	Handler.go
+
+	This script handles incoming request for loading the statistic of the day
+
+*/
+
+func (c *Collector) HandleTodayStatLoad(w http.ResponseWriter, r *http.Request) {
+	js, _ := json.Marshal(c.DailySummary)
+	utils.SendJSONResponse(w, string(js))
+}

+ 184 - 0
mod/statistic/statistic.go

@@ -0,0 +1,184 @@
+package statistic
+
+import (
+	"strings"
+	"sync"
+	"time"
+
+	"imuslab.com/Zoarxy/mod/database"
+)
+
+/*
+	Statistic Package
+
+	This packet is designed to collection information
+	and store them for future analysis
+*/
+
+//Faststat, a interval summary for all collected data and avoid
+//looping through every data everytime a overview is needed
+type DailySummary struct {
+	TotalRequest int64 //Total request of the day
+	ErrorRequest int64 //Invalid request of the day, including error or not found
+	ValidRequest int64 //Valid request of the day
+	//Type counters
+	ForwardTypes    sync.Map //Map that hold the forward types
+	RequestOrigin   sync.Map //Map that hold [country ISO code]: visitor counter
+	RequestClientIp sync.Map //Map that hold all unique request IPs
+}
+
+type RequestInfo struct {
+	IpAddr                        string
+	RequestOriginalCountryISOCode string
+	Succ                          bool
+	StatusCode                    int
+	ForwardType                   string
+}
+
+type CollectorOption struct {
+	Database *database.Database
+}
+
+type Collector struct {
+	rtdataStopChan chan bool
+	DailySummary   *DailySummary
+	Option         *CollectorOption
+}
+
+func NewStatisticCollector(option CollectorOption) (*Collector, error) {
+	option.Database.NewTable("stats")
+
+	//Create the collector object
+	thisCollector := Collector{
+		DailySummary: newDailySummary(),
+		Option:       &option,
+	}
+
+	//Load the stat if exists for today
+	//This will exists if the program was forcefully restarted
+	year, month, day := time.Now().Date()
+	summary := thisCollector.LoadSummaryOfDay(year, month, day)
+	if summary != nil {
+		thisCollector.DailySummary = summary
+	}
+
+	//Schedule the realtime statistic clearing at midnight everyday
+	rtstatStopChan := thisCollector.ScheduleResetRealtimeStats()
+	thisCollector.rtdataStopChan = rtstatStopChan
+
+	return &thisCollector, nil
+}
+
+//Write the current in-memory summary to database file
+func (c *Collector) SaveSummaryOfDay() {
+	//When it is called in 0:00am, make sure it is stored as yesterday key
+	t := time.Now().Add(-30 * time.Second)
+	summaryKey := t.Format("02_01_2006")
+	c.Option.Database.Write("stats", summaryKey, c.DailySummary)
+}
+
+//Load the summary of a day given
+func (c *Collector) LoadSummaryOfDay(year int, month time.Month, day int) *DailySummary {
+	date := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
+	summaryKey := date.Format("02_01_2006")
+	var targetSummary = newDailySummary()
+	c.Option.Database.Read("stats", summaryKey, &targetSummary)
+	return targetSummary
+}
+
+//This function gives the current slot in the 288- 5 minutes interval of the day
+func (c *Collector) GetCurrentRealtimeStatIntervalId() int {
+	now := time.Now()
+	startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).Unix()
+	secondsSinceStartOfDay := now.Unix() - startOfDay
+	interval := secondsSinceStartOfDay / (5 * 60)
+	return int(interval)
+}
+
+func (c *Collector) Close() {
+	//Stop the ticker
+	c.rtdataStopChan <- true
+
+	//Write the buffered data into database
+	c.SaveSummaryOfDay()
+
+}
+
+//Main function to record all the inbound traffics
+//Note that this function run in go routine and might have concurrent R/W issue
+//Please make sure there is no racing paramters in this function
+func (c *Collector) RecordRequest(ri RequestInfo) {
+	go func() {
+		c.DailySummary.TotalRequest++
+		if ri.Succ {
+			c.DailySummary.ValidRequest++
+		} else {
+			c.DailySummary.ErrorRequest++
+		}
+
+		//Store the request info into correct types of maps
+		ft, ok := c.DailySummary.ForwardTypes.Load(ri.ForwardType)
+		if !ok {
+			c.DailySummary.ForwardTypes.Store(ri.ForwardType, 1)
+		} else {
+			c.DailySummary.ForwardTypes.Store(ri.ForwardType, ft.(int)+1)
+		}
+
+		originISO := strings.ToLower(ri.RequestOriginalCountryISOCode)
+		fo, ok := c.DailySummary.RequestOrigin.Load(originISO)
+		if !ok {
+			c.DailySummary.RequestOrigin.Store(originISO, 1)
+		} else {
+			c.DailySummary.RequestOrigin.Store(originISO, fo.(int)+1)
+		}
+
+		fi, ok := c.DailySummary.RequestClientIp.Load(ri.IpAddr)
+		if !ok {
+			c.DailySummary.RequestClientIp.Store(ri.IpAddr, 1)
+		} else {
+			c.DailySummary.RequestClientIp.Store(ri.IpAddr, fi.(int)+1)
+		}
+	}()
+}
+
+//nightly task
+func (c *Collector) ScheduleResetRealtimeStats() chan bool {
+	doneCh := make(chan bool)
+
+	go func() {
+		defer close(doneCh)
+
+		for {
+			// calculate duration until next midnight
+			now := time.Now()
+
+			// Get midnight of the next day in the local time zone
+			midnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
+
+			// Calculate the duration until midnight
+			duration := midnight.Sub(now)
+			select {
+			case <-time.After(duration):
+				// store daily summary to database and reset summary
+				c.SaveSummaryOfDay()
+				c.DailySummary = newDailySummary()
+			case <-doneCh:
+				// stop the routine
+				return
+			}
+		}
+	}()
+
+	return doneCh
+}
+
+func newDailySummary() *DailySummary {
+	return &DailySummary{
+		TotalRequest:    0,
+		ErrorRequest:    0,
+		ValidRequest:    0,
+		ForwardTypes:    sync.Map{},
+		RequestOrigin:   sync.Map{},
+		RequestClientIp: sync.Map{},
+	}
+}

+ 1 - 1
mod/tlscert/tlscert.go

@@ -11,7 +11,7 @@ import (
 	"path/filepath"
 	"path/filepath"
 	"strings"
 	"strings"
 
 
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 )
 
 
 type Manager struct {
 type Manager struct {

+ 1 - 1
redirect.go

@@ -5,7 +5,7 @@ import (
 	"net/http"
 	"net/http"
 	"strconv"
 	"strconv"
 
 
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 )
 
 
 /*
 /*

+ 3 - 2
reverseproxy.go

@@ -10,8 +10,8 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
-	"imuslab.com/arozos/ReverseProxy/mod/dynamicproxy"
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/dynamicproxy"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 )
 
 
 var (
 var (
@@ -51,6 +51,7 @@ func ReverseProxtInit() {
 		TlsManager:         tlsCertManager,
 		TlsManager:         tlsCertManager,
 		RedirectRuleTable:  redirectTable,
 		RedirectRuleTable:  redirectTable,
 		GeodbStore:         geodbStore,
 		GeodbStore:         geodbStore,
+		StatisticCollector: statisticCollector,
 	})
 	})
 	if err != nil {
 	if err != nil {
 		log.Println(err.Error())
 		log.Println(err.Error())

+ 1 - 0
web/components/utils.html

@@ -30,6 +30,7 @@
 </div>
 </div>
   
   
 <h3><i class="ui code icon"></i> IP Address Converter</h3>
 <h3><i class="ui code icon"></i> IP Address Converter</h3>
+<p>No experience with CIDR notations? Here are some tools you can use to make setting up easier.</p>
 <div class="ui basic segment">
 <div class="ui basic segment">
     <h5><i class="chevron down icon"></i> IP Range to CIDR Conversion</h5>
     <h5><i class="chevron down icon"></i> IP Range to CIDR Conversion</h5>
     <div class="ui message">
     <div class="ui message">

+ 24 - 2
web/index.html

@@ -70,6 +70,12 @@
                 }
                 }
             }
             }
 
 
+            .menudivider{
+                font-size: 0.8em !important;
+                color: #9c9c9c !important;
+                padding-left: 0.6em;
+            }
+
         </style>
         </style>
     </head>
     </head>
     <body>
     <body>
@@ -101,7 +107,7 @@
                     <a class="item" tag="setroot">
                     <a class="item" tag="setroot">
                         <i class="home icon"></i> Set Proxy Root
                         <i class="home icon"></i> Set Proxy Root
                     </a>
                     </a>
-                    <div class="ui divider"></div>
+                    <div class="ui divider menudivider">Access & Connections</div>
                     <a class="item" tag="cert">
                     <a class="item" tag="cert">
                         <i class="orange lock icon"></i> TLS / SSL certificate
                         <i class="orange lock icon"></i> TLS / SSL certificate
                     </a>
                     </a>
@@ -111,7 +117,23 @@
                     <a class="item" tag="blacklist">
                     <a class="item" tag="blacklist">
                         <i class="red ban icon"></i> Blacklist
                         <i class="red ban icon"></i> Blacklist
                     </a>
                     </a>
-                    <div class="ui divider"></div>
+                    <div class="ui divider menudivider">Bridging</div>
+                    <a class="item" tag="">
+                        <i class="remove icon"></i> TCP Proxy
+                    </a>
+                    <a class="item" tag="">
+                        <i class="remove icon"></i> HTTP over Websocket
+                    </a>
+                    <a class="item" tag="">
+                        <i class="remove icon"></i> UDP Hole Punching
+                    </a>
+                    <div class="ui divider menudivider">Others</div>
+                    <a class="item" tag="">
+                        <i class="remove icon"></i> Uptime Monitor
+                    </a>
+                    <a class="item" tag="">
+                        <i class="remove icon"></i> Network Tools
+                    </a>
                     <a class="item" tag="utils">
                     <a class="item" tag="utils">
                         <i class="grey paperclip icon"></i> Utilities
                         <i class="grey paperclip icon"></i> Utilities
                     </a>
                     </a>