Useage
1.Client(eg:phone)
set wifi http proxy:
proxy host : {{.pproxy_host}}
proxy port : {{.pproxy_port}}
android users can use proxyDroid to manager proxys
2.Server:user interface
visit Session List Page to view all the http request through this proxy.
in the session filter form, all text input can use| to enter multiple conditions.
eg user: ,it mean user is a or b.
you can use replay to replay a request.
3.Modify Requests
you can modify http request.GET、POST paramas and http headers.
pproxy use javascript to achieve it:
function rewrite(req){
//you code start
if(req.host=="www.baidu.com"){
req.host="www.163.com"
req.host_addr="127.0.0.0:81" // send req to 127.0.0.1:81
form_get.add("a","a")
//form_post.set("d","a")
}
// you code end
return req
}
req object has these attributes (url=http://www.example.com/album/list?cid=126):
schema : http
host : www.example.com
port : 80
path : /album/list
get: {cid:[123]}
post: {}
username :
password :
method: GET
form_get : {add:function(k,v){},set:function(k,v){},get:function(k){},len:function(){}}
form_post : {add:function(k,v){},set:function(k,v){},get:function(k){},len:function(){}}
host_addr: #the real host you wish,eg 127.0.0.1:3218
4.Modify Hosts
#all port
news.baidu.com 127.0.0.1
#only 81 port match
news.baidu.com:81 127.0.0.1:82
news.163.com 127.0.0.1:8080
================================================
FILE: res/version
================================================
0.5.2
================================================
FILE: script/create_dest_zip.sh
================================================
#!/bin/bash
echo "bye bye"
exit
cd $(dirname $0)
cd ../
if [ -z "$1" ];then
gox -arch amd64 -os linux
bash build.sh windows
fi
version=$(cat res/version)
cd dest
################################################
if [ -d conf ];then
rm -rf conf
fi
rm -rf data/*
mkdir conf
cp ../res/conf/demo.conf conf/pproxy.conf
echo -e "name:admin psw:psw is_admin:admin">conf/users
cp ../conf/req_rewrite_8080.js conf/
echo -e "news.baidu.com 127.0.0.1\nnews.163.com 127.0.0.1:81">conf/hosts_8080
t=$(date +"%Y%m%d%H")
rm pproxy_*.tar.gz pproxy_*.zip
################################################
target_linux="pproxy_${version}_linux_$t.tar.gz"
mkdir -p linux/data
mkdir -p linux/file/
cp pproxy ../script/pproxy_control.sh linux/
cp -r conf linux/conf
dir_new="pproxy_${version}"
if [ -d $dir_new ];then
rm -rf $dir_new
fi
mv linux $dir_new
tar -czvf $target_linux $dir_new
rm -rf $dir_new
################################################
target_windows="pproxy_${version}_windows_$t.zip"
mkdir -p windows/data
mkdir -p windows/file/
cp pproxy.exe windows
cp ../script/windows_run.bat windows/start.bat
cp -r conf windows/conf
mv windows $dir_new
zip -r $target_windows $dir_new
rm -rf $dir_new conf
================================================
FILE: script/pproxy_control.sh
================================================
#!/bin/bash
CUR_DIR=$(dirname $0)
BIN_NAME="./pproxy"
DEFAULT_CONF="./conf/pproxy.conf"
INTRO="get more info from github.com/hidu/pproxy"
CONF_FILE=$2
if [ -z "$CONF_FILE" ];then
cd $CUR_DIR
CONF_FILE="$DEFAULT_CONF"
fi
CONF_PATH=$(readlink -f "$CONF_FILE")
cd $CUR_DIR
BIN_PATH=$(readlink -f $BIN_NAME)
if [ ! -f "$CONF_PATH" ];then
echo "conf file[${CONF_PATH}] not exists!"
exit 2
fi
RUN_CMD="$BIN_PATH -conf $CONF_PATH"
function start(){
nohup $RUN_CMD>/dev/null 2>&1 &
status=$?
if [ "$status" == "0" ];then
echo "start suc! pid="$!
else
echo "start failed!"
exit 2
fi
}
function stop(){
list=$(ps aux|grep "$RUN_CMD"|grep -v grep)
if [ -z "${list}" ];then
echo "no process to kill"
else
pid=$( echo "$list"|awk '{print $2}')
kill $pid
if [ "$?"=="0" ];then
echo "stop suc! pid=${pid}"
else
echo "stop failed! pid=${pid}"
exit 3
fi
fi
}
function restart(){
stop
start
}
function useage(){
echo "pproxy useage:"
echo $0 "start|stop|restart" [conf_path]
echo -e "$INTRO"
}
if [ $# -lt 1 ]; then
useage
exit 1
fi
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
esac
================================================
FILE: script/windows_run.bat
================================================
echo "start pproxy,don't close it"
@echo off
pproxy -conf=./conf/pproxy.conf
================================================
FILE: serve/assest.go
================================================
// generated by goassest(0.5.3 20161126)
// https://github.com/hidu/goassest/
package serve
import (
"bytes"
"compress/gzip"
"encoding/base64"
"errors"
"flag"
"io"
"mime"
"net/http"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"time"
)
// AssestFile assest file struct
type AssestFile struct {
Name string
Mtime int64
Content string
}
// AssestStruct assest files
type AssestStruct struct {
Files map[string]*AssestFile
}
var _assestDirect bool
func init() {
exeName := filepath.Base(os.Getenv("_"))
// only enable with go run
if exeName == "go" || (runtime.GOOS == "windows" && strings.Contains(os.Args[0], "go-build")) {
flag.BoolVar(&_assestDirect, "assest_direct", false, "for debug,read assest direct")
}
}
var _assestCwd, _ = os.Getwd()
// GetAssestFile get file by name
func (statics *AssestStruct) GetAssestFile(name string) (*AssestFile, error) {
name = filepath.ToSlash(name)
if name != "" && name[0] != '/' {
name = "/" + name
}
if _assestDirect {
f, err := os.Open(filepath.Join(_assestCwd, name))
if err != nil {
return nil, err
}
defer f.Close()
info, err := f.Stat()
if err != nil {
return nil, err
}
if info.Mode().IsRegular() {
content, err := io.ReadAll(f)
if err != nil {
return nil, err
}
return &AssestFile{
Content: string(content),
Name: name,
Mtime: info.ModTime().Unix(),
}, nil
}
return nil, errors.New("not file")
}
if sf, has := statics.Files[name]; has {
return sf, nil
}
return nil, errors.New("not exists")
}
// GetContent get content by name
func (statics AssestStruct) GetContent(name string) string {
s, err := statics.GetAssestFile(name)
if err != nil {
return ""
}
return s.Content
}
// GetFileNames get all file names
func (statics AssestStruct) GetFileNames(dir string) []string {
if dir == "" {
dir = "/"
}
names := make([]string, len(statics.Files))
dirRaw := dir
dir = path.Clean(dir)
if dir != "/" && strings.HasSuffix(dirRaw, "/") {
dir += string(filepath.Separator)
}
dir = filepath.ToSlash(dir)
for name := range statics.Files {
if strings.HasPrefix(name, dir) {
names = append(names, name)
}
}
return names
}
// FileHandlerFunc handler http files
func (statics *AssestStruct) FileHandlerFunc(name string) http.HandlerFunc {
if strings.Contains(name, "private") {
return http.NotFound
}
name = filepath.ToSlash(name)
static, err := statics.GetAssestFile(name)
return func(w http.ResponseWriter, r *http.Request) {
if err != nil {
http.NotFound(w, r)
return
}
modtime := time.Unix(static.Mtime, 0)
modifiedSince := r.Header.Get("If-Modified-Since")
if modifiedSince != "" {
t, err := time.Parse(http.TimeFormat, modifiedSince)
if err == nil && modtime.Before(t.Add(1*time.Second)) {
w.Header().Del("Content-Type")
w.Header().Del("Content-Length")
w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat))
w.WriteHeader(http.StatusNotModified)
return
}
}
mimeType := mime.TypeByExtension(filepath.Ext(static.Name))
if mimeType != "" {
w.Header().Set("Content-Type", mimeType)
}
w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat))
w.Write([]byte(static.Content))
}
}
// HTTPHandler handler http request
// eg:on file system is :/res/js/a.js and request is /res/js/a.js
// http.Handle("/res/",res.Assest.HttpHandler("/"))
// eg:on file system is :/res/js/a.js and request is /js/a.js
// http.Handle("/js/",res.Assest.HttpHandler("/res/"))
func (statics *AssestStruct) HTTPHandler(baseDir string) http.Handler {
return &_assestFileServer{sf: statics, pdir: baseDir}
}
type _assestFileServer struct {
sf *AssestStruct
pdir string
}
// ServeHTTP ServeHTTP
func (f *_assestFileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
rname := filepath.ToSlash(filepath.Join(f.pdir, r.URL.Path))
f.sf.FileHandlerFunc(rname).ServeHTTP(w, r)
}
func _assestGzipBase64decode(data string) string {
b, _ := base64.StdEncoding.DecodeString(data)
gr, _ := gzip.NewReader(bytes.NewBuffer(b))
bs, _ := io.ReadAll(gr)
return string(bs)
}
func _assestBase64Decode(data string) string {
b, _ := base64.StdEncoding.DecodeString(data)
return string(b)
}
// Assest export assests
var Assest = &AssestStruct{
Files: map[string]*AssestFile{
_assestBase64Decode("L3Jlcy9jb25mL2RlbW8uY29uZg=="): {
Name: _assestBase64Decode("L3Jlcy9jb25mL2RlbW8uY29uZg=="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/6RT3W7iRhS+n6c4km8rflJVinxVVXmAlbbqLfLC7GLJ8bj2UIqqSLCCLLRm7e6G/ABRS0lU2irAVUL4y8t4ZswVr1ANQ1rSXFRq58Yz53znO9/5Zqz954U0eLYcxyXfliCHDwlkif36OUIuDf2PrkjjQRgtO9GsL8Jj3m2y73uiXRV/jFjQRw5xKeiwn9pPIaSJYU+Ex6Llry5/+QuznvvRZJqKewNxNWUf/F2iaPEgTgaiM2Gh/zepkTs07ReKOYUQNamFQYc8tiyyHRnZhJpZGd1u1EeqbY15c8huzvnJUnSGbNFaz33ZYXQfTd+zxUfWaK5qTbEYinY1vhuxZRXlDGocmC7okEgk5SGJkLa6vODlCj99F81ud+nQa9PCB6arg0TLQxL9oy+7+o23xuu5n4omUz6eRPc1dnPO3g7W87okaTRZ8DvrDlbvmiwciXaVn92y8kU0b8cPnbjnKy4+qYnweD1vbPS9pMTFB0YJdEh/hpC2a2M8vIpHFX56z+YB0ohDTWJ7+nc2saVD/OxnBfjkleGZWT1PqQObLahIhrolnY0v41FL5r6QMVXxCDDskh7NZrwasEnIgrsjZBRo/suSo27Alsav3vajacfFXxewR9kH38WeQ2wPq2HUYMqFHYlgWJaU2CjzbgMAiG2VMq9cYuSyhifvnwU/rsoVVh972PNMYoNlelQ61mgqsiP02Oil8Y2U85QCIW1byepncW/Af7oW3R+UJqTBVggoJbrSEU2nLBjFv1bAdDLEzRQ8LN9GvPzIatf8pi968u2YDq+fxuWazLLwvao4QttuX5m4CLokRUjDb0AaqyeT6VQinUrsJfb0T9N7+0Dcx4RtHGLd8YqfP0Ug7V8AQPMYNgINOweOV4SiaVngGJ4HNO+Swps8ULJBOYaLbQrq/1GHF3Kv/wkAAP//AQAA//8Vmt1TlAQAAA=="),
},
_assestBase64Decode("L3Jlcy9jc3MvZmxhdC5jc3M="): {
Name: _assestBase64Decode("L3Jlcy9jc3MvZmxhdC5jc3M="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("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"),
},
_assestBase64Decode("L3Jlcy9jc3Mvc3R5bGUuY3Nz"): {
Name: _assestBase64Decode("L3Jlcy9jc3Mvc3R5bGUuY3Nz"),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/6RW3Y7bNhN9FX0xAiTASpDWdr6NhAC96hO0V0UhUOTIIpYmBWq0tkvo3YuhqD/baS+KIIaWnBkOz5w5w194w2wHGH36/bdf47dPRcIVMOv8b14ZbIYGz+qlMuLmzsyepM7TomVCSH3K02Gn2YerpUKwOVNtw76YlnGJtx/f069F+M6T70XF+PvJml6LfFfX9RKimKOuTQ6Hb7zaF9woY73DsDuD7t1FCmzyt/Tz7BaxHo3PI+qV26yuDlGyw7jDm4IYby3k2miYnCIlXa0Mw1xBjbNTlrbXojYa407+BXl2aK9FZawAG1t5ajDP2mvUGSVFZE8V+5K+0L8k+1pU5hp3DRPm4m3SKB0tXo/Hl+l/kmVfx+gX8NH+n6YFwhVjpuRJ5xw0gh2S8cSylrZDF46nNP/76dP1dxSu7PqKAI6UzBvzAdbdFeyfrSPmQqnGwq2gjZgTsmsVu+VSK6khrpTh78VYySOBvK6yBrwY+x5jFaFwH2BRcqYCKGjaYcc6KSAQYb//XJyljse/DhRtSC7Zaxr2s9ewcphXDmHlOK94rx03WgPHskOGfecmFnzb5oesUjDVgRulWNtBPn1M/OiI974/EqzKLNivSrbjnBc+VqzYzfSY1/IKorgYK+LKAnvP/W/MlApIZWn6+aGLRrrHlUE05/xIN6MDI85alEa7B0JtI6T1of72wMJw9RhNSyGLZtx6Pc4NcH+TcO2QRrh1xJZu3M+ZoY2weVm+xYz0/qfhPWumLNIlVANMjAHdv3bS5uKwBwYwhSFtC2FWbr7Hn8E1wTOKwII51sbg1Aac8yFBUXK64ZO2RlHauy0fb0iw5I8O/5Pn1lhk2hvYB7ftvlrvU7+ut1umQREn13gc4RWqWfkO1A+hEctR3Mva2HNUS1CiA3RbDKi6O6zK4OJWopm114mkRKf4bWtKEJB+1Mpcci/ZvLedsXlrpAdq508oudEIGt0qFAG/3Y18QxG9nq+L+8SeqcvWs7VTs9OsE/KjXHIvaabcZX9m14moGRChSZzCwqhOaJMOFHAEsSkBr2FI/OFhFgU6UPlW02lI+Ho2Jw1p4aSvYait0iuF/JgI/shuT8NeuWU4+hCR1CSxT0Z0cKCB+dxnQEEmDetoRMytna4IuLGIRn1cngAz1pVI74b5PPqHhMgYU42sUW4SBlLq5WEyU3Ua2UzIvhuH+oPKnCy7UWUsEI6hhyvVU0mgK1GU4f2zVJd0qJiL33FrlBoSYkhVyqcTb0iUIfJ23AJot2ZGSsHCRPPf25s/6seuEj9DZyPfe6+WBFeZjm3glmmybDwRvmD2lj7vk8lV6rbHP+hJ9YNC/LkKfzecXteZiESxDoPx93SpXHjb+LSNBkLPrZR/QbyRQoAe5PnktufEpEZ/AwAA//8BAAD//+I2kz/XCgAA"),
},
_assestBase64Decode("L3Jlcy9pbWcvZmF2aWNvbi5pY28="): {
Name: _assestBase64Decode("L3Jlcy9pbWcvZmF2aWNvbi5pY28="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("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"),
},
_assestBase64Decode("L3Jlcy9pbWcvZm9sZGVyLnBuZw=="): {
Name: _assestBase64Decode("L3Jlcy9pbWcvZm9sZGVyLnBuZw=="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/wCMAnP9iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB9sFCBErNh1XQGQAAAIMSURBVDjLjZLPS1VREMc/M/d4n7osqKBFuSwjoR+uMhemIUEQuGnXvl2tgsD/JqKINklRVBAiRgZCFrUoiIxELXq+fOa998y0uPe9rlDUbA5nmPn+YuAvNTV9j/8pmZqeeR5Fh3EQABdCmry+dX38yL+Wp6ZnkMkrN3zi7CQec1SFJAk8ePLM0zQVJEEBA1QBB5EOSePNzWunB8OP5jpb7Q0W36+jKAN7+xgdHhJDcMDccFdijJgLZgbaw8LLF4cBwsa3FZqtTYqtTRKFdx9bxDyytrbMZvM71rEmpYLoMLB/N4tzj8sMDo5c8kPHR/nwaY1EBRB+brU4N3aMC+fHMPN6ZIDRCAmWpLxdevUonDg1ytXLF2m1t1EVANyF7e2MxaUVstyQapVSBCEIEnp5euf2ePCkn9mFLxRZ1h0AUBE8gR4tQ/QKRIBoTkMU0UBwF1SAij0BYgfIIANC1ZOamcIcHIJ7+YnmCFAAShmWSLmUVYDaAXYwcwp3grtj0Smi7/Dp3ch+l9csFtFwc4KIoJqQqHUtdNiseqUGVvYdTRIUIax8bXJ/fgm3YgeT1lT4H844pH0sf14lHNi3i6Gjg8Qi7zLVpdZ7VgNOG33Mrc4TNtrZwyy3iSJ3RMC8DLKH6u4rSXkFFARwJxJp535X2HPyDEU+AgjuiiC4S9e+O4hUAqQUJ2I4Tm//7C/8V/xJTO95pgAAAABJRU5ErkJgggAAAP//AQAA///J9dz9jAIAAA=="),
},
_assestBase64Decode("L3Jlcy9pbWcvbGlzdC1hZGQucG5n"): {
Name: _assestBase64Decode("L3Jlcy9pbWcvbGlzdC1hZGQucG5n"),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/wA0Asv9iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAABuwAAAbsBOuzj4gAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAGxSURBVHjapZNNaxNRFIaf+zVJ2klMad24SBBdmLWCFUQRROg/cOci+gPEjRtbdNOVYEH/QBFc6UKQUhduxC7qBwiCVLQBFURxTDM2k3Tm3jtCRXDhREKf7ct5OLycI/I8Zy9ICri2fvnumYetwdlHR6Lld0t3KKBwg/Orc37u4DmRuiGvoxf29sn7ZqwNXn59LrwZUqpo3m690RRQGGTe8XMnQUhJ6hz/EwhAAfZPYJ0nTgcIBJl1/5pzQK4B2ivtxU7cufLsy1MdKE2gFUZJkmyIFIIdlzH7oJlXjKESGI7uP2EbtebNS4cXru6WaBbN94vHLkyjHLnyCCl+C3y8K6jrOlJqSoRE/R5x8oNscjO6d/rVjAaw1rIZddCBxpQVtVKIkYK+20ZJMLkhSRM2og/0BgOmgiozQfpXB575x++f3EJj0GC0JFCK2WaLySBg7eM6ZaMpaU011JggzqzV8yPvoLxk8lOHWkxNlFnrbPCp3RNj3YHznsxlSAGZ9xShRwm+bffIhce6sQXXZSOs55+7XdHtZ4R2nx3zmRb88enqMjA8MNHYqvnajZHPtBd+AV8ir6y+hjXrAAAAAElFTkSuQmCCAAAA//8BAAD//3j7Bz40AgAA"),
},
_assestBase64Decode("L3Jlcy9pbWcvbGlzdC5wbmc="): {
Name: _assestBase64Decode("L3Jlcy9pbWcvbGlzdC5wbmc="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/+oM8HPn5ZLiYmBg4PX0cAliYGAQAGEOZgYGBg1d/mAGBgbmYifPEA4OjtsP/R8wMDBwFnhEFjMw8F4HYUYnjdkVDAwMkiWuESXB+Wkl5YlFqQzl5eV6mXnZxcmJBal6+UXps9/ZSDEwMHgE+IS4/v//nyENhKLmQMmoOf/9/f3Ly8sZ0v4rVYG4ENLf3z85Oblw1f/y8vIHDx48evTo5cuXHz58+Pbt258/f3g6ps5lYGDgKwnyC2ZISUtLT89saNixY8ezZ9bPUv4yMDC4ero4hkhcnnpuu+ABBQaWg+wtgoeUL9cvV1L60ZLIvcOyYeaKB6+eCHS5/2v7vrM6W/K4ZsLllI3LLF9Zan1b/OpSjdSPkDy3+sDj7EvvzPm8hoGBgcHT1c9lnVNCEwAAAP//AQAA//8A3/5bNQEAAA=="),
},
_assestBase64Decode("L3Jlcy9pbWcvbG9nby5wbmc="): {
Name: _assestBase64Decode("L3Jlcy9pbWcvbG9nby5wbmc="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("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"),
},
_assestBase64Decode("L3Jlcy9pbWcvdGV4dC5wbmc="): {
Name: _assestBase64Decode("L3Jlcy9pbWcvdGV4dC5wbmc="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/wCrAlT9iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAbrwAAG68BXhqRHAAAAAd0SU1FB9sBBwINDzIP4PcAAAIrSURBVDjLlZPNSltRFIW/k3uTKAkxQXQo1KgDpaWTDCuOKxVRH8JH6LCDCsVCCzGd+w59gA4zKXZQqBQMWLAIyU1sfszPzT1ndaAR06al3cNzzlpn7bX2NgCzs7Pper3+EPD5t4qSyeSXwWDQHB080X8W8JR7P3oArw8P2d/fp1at0u50sNYi54j7Pr3BAN/zmMlmmZubu8OYW4INSR8G/T69bhfP8zDm5srEYtgoIp5I4JwDIJVOY4zZAt6P9fyuVGJre5tOu00YhkxPT9Pv9/F8H2stM5kMrVaL/PLyHWZMgY0iwjAEwPdvuI0xYAxyDklgDIlEYrKCUqnE5uYmnetrrLUk4nEc4KzF8zxyuRzNZpN8Pv9bLBsjd8MwVBRFkiTnnKy1CsNQw+FQvV5P3W53lMIzfs29WCyyt7dHEARIIpVKEfd9nEQQBKTTaVrNJg8WFyd7wK1cE4sBIOeoNxqUy2XkHNY5pqamVCgUzPz8fAH4ONZCsVjU94sLfTs/V6VS0cnJiQ4ODu7Pj+u023r86NHztbW12EQPnLWy1mo4HKrT6ej4+FiS1Gg03NfTU62urr5YWFjwRsAxD46Ojtjd3aVarZJKpXDOcXl5CaAfjYbZ3tl5FdRqL2tBYP+ewq3jQRCoXC67bq+n5ZWVN7lcLvGn7VqXpEqlIkmKokj9fl9XV1c6OzvT0tLS20wmk5wE9AGy2ewnY8w6EJvwpp3NZj+3Wq3hJIKf+1KUYNzdR+cAAAAASUVORK5CYIIAAAD//wEAAP//kff/kKsCAAA="),
},
_assestBase64Decode("L3Jlcy9pbWcvdmlldy5wbmc="): {
Name: _assestBase64Decode("L3Jlcy9pbWcvdmlldy5wbmc="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/wA4Asf9iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB9sDCwAbGenR2McAAAG4SURBVDjLjVM9b9NQFD33+RkTBkPt2ApyMpE0UVQ2xgyN+A90ZmAGJJb+hwqJja0j4icwwMRvABUydEhd4diJMKEhcfwuAy74I5Y40l3ux9E9576nDe6PKQzOmwDaAJo1YQOg/nC8imbnyIMAONPpxUvPcyxAKeyEEBfTYNHutJ8BCPMVCcDyPMfqncrRJCITXJoloGtz/PWx+zHbpEJAgFKTiEx+EbzFWugFAkMldOIeAbwFcA8AA5j3hw+js0/vWf5tZABroc+vtAKBdV1TVxu18I/pTjPOyxG7FFMWRRs2W/H67qh3qo28truXyYHA/yLVJT/5/G4SCTMzm649qIBrkj9/SVleTRb2NlRilQcNlVT1VAiE6Noc04l7BM4ZwP/OiGS3XAmAVUral0eLDyR3PyTekvi+0TSdAJQ6BIC5fxnMUsNsbOVe48f6hlY2gXRWt3nJt2x9WfFgcHAYdjre8+zkvWXoHztvWg/CmIxCM/2R1LU5BoTIp/Pop2nySqziFWrlCME3zYYm9acAzspnnPt+MGu1XKfWdg249L+FAKJKbXBwSNn33QfQr4l9AM3+cEwA8BsG1aBqp74kfgAAAABJRU5ErkJgggAAAP//AQAA//92SZjSOAIAAA=="),
},
_assestBase64Decode("L3Jlcy9qcy9iYXNlNjQuanM="): {
Name: _assestBase64Decode("L3Jlcy9qcy9iYXNlNjQuanM="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/5xUWXPaMBB+z69IeWCkMQEsOQ5ElmdIeh9J2/RMaTNUyOCGCGrkXIb/3llJHMmEkPTFmtXufrve79Oed7LNvc5YhgEvTk7l1ZHOdkutvf2nz56/ePnq9Zu37w4O33/4ePTp85ev374fd36Lrkx6/fTP6eBMDUd/s7HOzy8ur67rPqHBdrjTaHo1XqpIJYZduZvkSuh0qFCqRrnGBZQb5nqUa14qMbBEP/Mrop8R+FDI8+FD4GPMwISlvM4MBrfdVk9ynTRObBmHzi766UCiNDJmdSBVT/dxARW4vRL9TrY/7MqWRqnnYQZ1V7voChf0yAE1jgkYhCMEZpniKArwBAwSx4GJpNZJyv42jiJivTSOQ+MNAIeWQ8rSBKXjg86BCca4MKkmIgzYVA7GcnM5hNoQ691wI7WHt6H76bjq2DTdtzSCprG3wkPwyiS6MinAbLqRSZ1nynHKppWu/C/ezdV67i0fmRwNOkKi2o9fra3jztZ1favZ9tq1Nv9Z61VKpVVCMLzd+JlUdeXlYYIWPDuOseX1EcH0McHBg4ONeA15VjzQlpGWkS4ytpVWYL00jolTr/FSo8oQT8w4bwrlSGep6lWTbHi27zRuhIxBjZD7hIcBLh6UREANNi14RBrFbDrX742nbZWErAuz20Jbfv8LuY1NFVzYk9tjIZh21lYgkbYqYSOtXCdaXhpRJsMMwZXidaYil2mlw5TnWQ2LGeTSTlBmXCLySQMXDtDjd/4vzGj2lJGIfbKDy2UkIlIPGnhNMhKwNCZ+k2C2JrAcUjyBdly59cA+wRNCgnXItocF/MP7mG8KlzJn8PbCcH47bkejWxmwBywJsH8Jr8/fuUtarHw+u1ne3cs8WeSVNKWed4uqpu+oIgHGhSB3VvB8zO5DhplQ375HJAjMB2pxMiPqHlxB73aR9SXNfgCKkSvqGqDzBugSQxaNTaf/AAAA//8BAAD//7lfkqMcCAAA"),
},
_assestBase64Decode("L3Jlcy9qcy9kZWZhdWx0Lmpz"): {
Name: _assestBase64Decode("L3Jlcy9qcy9kZWZhdWx0Lmpz"),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/1TMQQuCMBiH8a8SEmzD2D3EU/UFqmMgs/1nQ3k35msp4nePAoNuD7/D4wa6sw+0iTGFcarY1FU/RMkmNWA1b9fSzpOVGWNkk2AypesPiBaTDS8Su/UkoWbvJHSL6RAsynKvZuiY8ATxEc4MHUtV8MP3ugefDTW4YmQpbix+3uF7O5Et8/zfLmwSF8uiiuUNAAD//wEAAP//UCCwb78AAAA="),
},
_assestBase64Decode("L3Jlcy9qcy9qcXVlcnkuanM="): {
Name: _assestBase64Decode("L3Jlcy9qcy9qcXVlcnkuanM="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("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"),
},
_assestBase64Decode("L3Jlcy9qcy9zZXNzaW9uLmpz"): {
Name: _assestBase64Decode("L3Jlcy9qcy9zZXNzaW9uLmpz"),
Mtime: 1478614292,
Content: _assestGzipBase64decode("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"),
},
_assestBase64Decode("L3Jlcy9qcy9zb2NrZXQuaW8uanM="): {
Name: _assestBase64Decode("L3Jlcy9qcy9zb2NrZXQuaW8uanM="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("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"),
},
_assestBase64Decode("L3Jlcy9wcml2YXRlL2NsaWVudF9jZXJ0LnBlbQ=="): {
Name: _assestBase64Decode("L3Jlcy9wcml2YXRlL2NsaWVudF9jZXJ0LnBlbQ=="),
Mtime: 1448806450,
Content: _assestGzipBase64decode("H4sIAAAJbogA/3TUyc6jOBcG4D1X8e/RL4ZMZFELGwwGAokZA7sEwhggEMCEq299X0nVXeouL189R7L0Hp3/fz2INN3+n4wcT1d1GXjoO2UsXVfaVZZB98gB1SHIdQOYcAL+wHqHbrIAr8lur7n6faMQBCHxgaVFi7wCA+Z2wEAQeeBZRJbjU0QjJSAEK2BBcRgXabjwOrJfKa4XvIL0awCCzlKb9JVqhRCVu4q5i/x805516h5pgo358Ru2fsP/YSmDi8S2PJ/aHlgtz18sxdqEX1nlU1ux6K+sgo1F3lQm37/UEDWiUEEBY0FLA4KPIKWWowVNdA3eqQyV29XZWY5E8c8BU4Fxe7s6XRrqUyQex5NYvGLsz0yC4ee+Kf6JlT/hX1bXoV4BG+Z1zxR1qR0pDwFBKgBnGRAJfAE5N2VAEOCN9/UNS+6d5bLTZw/3kWXC7dk4ZZrxNG6ZauTEYjSban1OBpfxbDmYIg/O+vYmj6ecXc3uZUi3R037RyfOjVQf8SgtYXLd5wd9HzGxjZSgGtTLM4lTFqtZLOojPvARMIjeWrhH4TDYsu1wbYrbJ8lZ7A+A7dINrspHTpiJOzdBcLm5WvkZgZocydrLU3QZzXM8y+3ehJfrprneB9iGGQ/c13poU+fiZNGFt3x+YkYWZHZSbbmnMrvK2VSDu+8+9pdh2SbzFtcJmS5tvvb4vH089wBmfBwaFaRzrIahsL0yyBCMRTwrRhOxoN9wn/4YH62qnQL4PpCqw3rvW9MaPuSD5cLYl3fLziW6AgiAnQAo41U/tw6TLVJz4l+xfe7m5cTaILjm0ymYMZvs/Rcb+RR/rbrDVxDmVO3At2X+hBXwjT0IfAoogtz6s/e/a/e/a2e+um4E81xNjmk4lZ+7SR1lgvzShsE12cj5JO/XJfNyCcUC7yHn2FVaJS3qjkRhvuMZJcrVftndw1PzGfU1Y893AZR7KhxL9jRPZieyM6hgI5EsGPqBo+mq6AWUK3YuI668M4ez5qEKXKxa2B6TD9dMrDTe9ptUG67C7q7DU+ZuxXZsvWdmB6Xop6M78vhew2KdpGfHUG2DPRre1dNH8jDXFN50U9FR7k9r5AP29KlFzlDMtsDOq1YueVXGdpmSTLlIvG7ZHCODZ8JlQg0DQcVn3yX5eitdsys3n9c7YLP2ZCN7ZZMKIpTr5QZy6c3B7Bj0ZC44yWFOc+Sp6vYOkrSbopL/iM/HNcPFi/74wXxfPWQr/76EfwEAAP//AQAA//8Ir+SsJgUAAA=="),
},
_assestBase64Decode("L3Jlcy9wcml2YXRlL3NlcnZlcl9rZXkucGVt"): {
Name: _assestBase64Decode("L3Jlcy9wcml2YXRlL3NlcnZlcl9rZXkucGVt"),
Mtime: 1448806450,
Content: _assestGzipBase64decode("H4sIAAAJbogA/2zVpxKkUAKFYc9TjKe2yEmMgAs0OTRNdDSZJmd4+q0dvcce87vvP/+bIL1U68/b4/84bzXgP9IfXYr/PZCpqtLEqwLP64B3JR7V1mgVGmQtK/Cey8IryhJLu/7d5CV6JkO7IXi96X37dLuGlCgEN4uOo7ytkinYjAp+9HHS2LT4nXMx4kfP/jhlY68wi+iKUek4sSQxaBfZ6bIkhxUZKhNc3RQGjXnNVQdTmaVwWSxgvZEhV4bOrWDFX3h4zAmlbYrK3RG7DwIn9V7NvfFyBnHuM4M9djbdTg4w0LrgREQffRdhCEuU96aHGfK38y5jBzV9dN9gvrSylkQ68fBEW4fk4Ot7Be0sF5kdpPLL3N0ZqmdWbLLoaF4o0STUWuE8EjkMMTKSNEy7cFvU+hjmZwKB7plLOLMd9kBYGbcdFXX2zf0JC8CYnpD4gLooz1VF3uUFflQF3gWsNFTgKbDQNuQfNPyqFUnoOw9xjqqjakhhn5/2m6ouftYamXv1S1H7uLq6WY2ZP4sT8P69Wcedmi0GREgv2KE/6fK2LnteB8fR67VV7nYmn8BIIn2I37Bbcz5A+plJl+0C9mbJZiIH4SIYNwr9Vq7Jac2XW6VYJvn3ra3YNVFkM5Ca3aTRJn/p4H+vmdoHVsZAh1vXOwaVGh72i7JsyE8CZ2vp8Vb8m7Q1gqisn6RhpBKLOIrKaHOyS93sFZPoaZSznneRt3TYPvBcmJ4CGpI123UNna0GPGBj3XAt/5tvE6mNrlekqlGS+h66JE74aoiEkaqOTYfjeA2sQbTeqQqFIeU0BU7x40vgneJgSzqMifWaImWyzCXSWwagn6CENfcreR7RmpPVw1gj4Tt/cVwB0VNkfkMFYQ42bHOjUM2XbE2rilvg+bpwVpWLgnqC1ThP0U3TKxGXZn7tN148LhFPOzSZ5kLpN3BgAYwm5eiJ+8HncG5ubBOo3nFMfyUpVfu+OOpFUIdPmD0oHOZfsVV8oeh8yWoPRPkBFNGi7CuBCbO2WmOFaUVkKJvPDbv3Tk2BrUZMO8rDr0eMTjj1zjg+KEgo2LSjggneu0TDTWLu2+6JJnJg1Wscy60pRAWMt6Aa89KPniKjaQDub7cWWX65XgdJBhtU4kfOhupMNi29koyJ5gJ/9U/ah1hNEOinr4HBjy8+IMN9wnCXSTonO1hDnMYA+kYOR33e91e0sk9k9tNG1Hukrt2kcmK/yU/s53qmsDa3vZJ2aReRykKMx5F3rbFELUFRxlGolAQD4x/YUMnW/sqe4kXoCDbrGK21VRT2bYq+KU+cGwtwSCKJqwYu7uIkl/egGNWUL+KfVbk9VXqM8NkR8AcFVcwvSLGTE40rMJM/w/cXPlHNwk1V1yrL9qjI6/qrhmBsBxdHzL0xwh/JbHZGWFTvtO+Oyhg3/THrGcmKR52C2X3wKcHsQVIIkGQHe292S0ESsGkCJ3Vdk++fS3iHQ2F1IDw+ay/hoTN3v+mhx+7ipq58Ig953e/aRHQr7+G6Sa4QfDFj6OpC5QJfzdZl4RxKBytrfJpD8ZLviX8vweN1RDaG9xdJiEZCZRRUzFqO80fIoP74oCEGBlROo34jVOcH11X+09+xhwxjpucKlvO63ajkOuY0J6Lxukzs+b6te3Tf5wJhdoSZARbkPiWuAD5VicF9cqKzjfdTPjVJR6GsTpmNRbwVITh0nCLF6u9f6B8rkiX+f27+CwAA//8BAAD//xldrVWPBgAA"),
},
_assestBase64Decode("L3Jlcy9zanMvcmVxX3Jld3JpdGUuanM="): {
Name: _assestBase64Decode("L3Jlcy9zanMvcmVxX3Jld3JpdGUuanM="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/4SSX6ubTBDG7/MpfPdKiZj3rhBZSulNKRRDcwotIrLGMW6y2fWMq2kw+93Lxj/Hes6hdzvz/MaZecaikQfNlXSqCtXvW4pwRa7BRXj2OoTn4AiaDlrFkF3q9KCqm9UDhfzIpSXu9854oc1Vqv4Xb5G+oFDotgyds8OlMyNKYDngo398TugrJT4na0JCs7LFhcJLOptSZae0BFEBusP8Xjhx8/EWoJV6sqkhLbgAOprj2mjHdOl144uOjzUhK1veoJhyQd1ktUb3f/+DRykpta62mw35OOpbd8ztdt+jn7/SL9H+iazJptiQ9dTLrD7/2D9F39Kv+xWCbvDhUWhWy5vNfVbZyevsEhKuUXaiEq5OlJ3goN2F3xPJEB/YJ0R2m1F8oOJz4nUMMeYJ7cOYD/73TeyNGGJoxjH79OtBZ463TDRQ2xPbiu4Ieju5LdkFRsXpwdjmEuPXS85vmbBrvEA0bplIjM/y/B2UtkzYs/HC1bcKVPFXE0pJI3MouIScvPXpYUvzTv1/lPSGv1U8RWtCEvv/vmSCqqlL64tn/BzE0o4cBGhY2CFALjlePB6UTkssrAwEyKMuDYgaBsWdf/V+jxNvhIwxfwAAAP//AQAA//9Fj23NIgQAAA=="),
},
_assestBase64Decode("L3Jlcy90cGwvYWJvdXQuaHRtbA=="): {
Name: _assestBase64Decode("L3Jlcy90cGwvYWJvdXQuaHRtbA=="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/3yQsc7aMBSFdz/FldUBJBSn6gaOB7p0RAJ1RY5jEoPxtexrKEK8e5U00O1ffb7jq+/Izt3AdQ1vu5orBrJNQjEZwXidc8OHH1zpFgtJEcc4ql+Hww5iQkKDHnTQ/pFdBkL01Qfa6mw7wADbPehkBkfWUEl2dU+OyAZoH9Cj16H/39klPFtDsAapYUj21PCBKOa1EL2jobSVwasYXFdEjAn/PDiQTr2lhh9br8OFq695KbT6XPtZUrKB4LdN2WFYgzxhIDDoMTWtL1Y9n9XtX/h6STGmUxvG+jRZsPfjTByzR+Lv36cNmRSduyk2Lzq/ZZNcJMWypYO7Wiy0OJVgyGFYLJ8M4FvVW9pP1OLjP0pUs5TDWUeYwZpLdc58uWGv1fe6rpcbJsX7xF8AAAD//wEAAP//Hz2bTdsBAAA="),
},
_assestBase64Decode("L3Jlcy90cGwvY29uZmlnL3JlcV9kZW1vLmh0bWw="): {
Name: _assestBase64Decode("L3Jlcy90cGwvY29uZmlnL3JlcV9kZW1vLmh0bWw="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/5RTwW7bOBC98ysG3AViA4EY2UA24FLa06KXHnsrCoMWRxJTiqOQVBzX0L8XlJ3GSFGglQBR4nvz5j2SUsY+Q0xHhxVvyFGQXdBHXjNPCSVT5GqmnK3HKYFqySdYWNXeTVgLYTtPAZXISA06QeoRWhtiAmc9QiI4UxbgMSrh7FlwihhuIjxGsBEMtnpy6ZbCwusc7bU7c9npZFvwlKCw8SN11s/zIqA09AHbiguXZ3m9DEroOndFYxMcaQrQTDHRAGOg1jo8i55O6M08M6bEElAY+1wzta8DPoHBgaQS+7cOj/pZxybYMcl/OZBvnG2+Vvzv1c1fY8BdwKddLrpZF4m6zuFqzevY00H01mB2xNQYEKyp+DWfvy68sXF0+ig9eeQ1m4IDCX1KoxTicDgU+KKH0WHR0CC020+DcDam/xprqnJzz2LT46AvJaynmEDCuzo2UsjTD3ds1KkHCVdKrMMk4dRYIz+Xm+2XmY0U88zM8jZ5PSBIYKOO8UDB5PcBU09Gwof/P7Hqzy4mRE4PDRmUKj9rZttVwKcie68qnr3vtTVTds7XJwYAP+AFLe+3C3aN7LQxoeLl5p/iLt/yoeQgBET0JpPysXgFS/lQ5tqWwrDrMBXamBXX/JZrvs6AEAuU16GImFbcXLBZibPlS4oN/DJCr6ncbN9lmIKr+GVvv/XW6Lekgv+kvv199SniLp/wFbfe4EvRp4GvlyRTPP9+MelkG8gksB6MDcDzx1VbJcaQhz6IWu2DqL8DAAD//wEAAP//KQqcVCAEAAA="),
},
_assestBase64Decode("L3Jlcy90cGwvY29uZmlnL3JlcV9mb3JtLmh0bWw="): {
Name: _assestBase64Decode("L3Jlcy90cGwvY29uZmlnL3JlcV9mb3JtLmh0bWw="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/3ySwYrbMBCG73qKQaTQwm58aqFZWedS+g5BiUf2uLLkSOPsBqN3L3Ls7NLDXgwa//N/vzSj0mg8nJ1JqZbdD6kjXiDiayRGVZWfWsxzNL5F2NHTrk+Het+nlLNQNsQBYEDuQlPLMSSWYM5MwdeyOgdvqZVgJg7nMIwOGWsZrJXAJrbItSQbj8VDaqEsoWsSshbKYYu+0fO869OeiR3mrKq1KuzkF8QW8mvEy7dZKMY3NhENJL45rOUrNdwdfn7/8tIhtR0f7n59+rUccx7fJHgzYC37JFfa6vk7FeLmqEVEnmIhXl7UKVZaZKHIjxMD30asZUdNg36zKzUJV+Om1fszcfk+xPcQpZRzaWvoul2nhHk2jlp/OKNnjFILMc9kYben9Ce05PN/qdJ0GojlNtwTezixfx4jDSbeHtBkrijLkNElzFnAgl2bFix5G6R2hQE2RLgbq6qh69Lnm4K+H4UK7sPG9E+7KeHRksNDXS63nRaQIy0AAJSBLqJdHmAT7Kfocn5fluPJGf93GdRDcn8pVRktQFXF7T1NiSFU9WGvqrJrD8k/AAAA//8BAAD//+1fByb5AgAA"),
},
_assestBase64Decode("L3Jlcy90cGwvY29uZmlnLmh0bWw="): {
Name: _assestBase64Decode("L3Jlcy90cGwvY29uZmlnLmh0bWw="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/5yRQW7CMBBF9z6FZYTULopDl+D4FN1HTsZJBsWOOx4oEeLuVQJV6a7t2v/Ns/43uSFMLDM1pdLksz5kDb51x4E3h6ys0beEFSbzNHgrVs0YW+zeXD142Y4ULskBYOx22yKd98FRh/GFsOt5J1+LdL4Ko++sqUlbYQBPEqFUNRTKCsPLqQ8E7ku1LYq1Wl4fPEuKrGGwl3WYKozNcAQv7xlN/r2av7LpOQxKSrm+Gs1gjWb6BQo+jP9A+zFzfvT+FX3w/kT1UokVwmBLLngZXfClwpYWnZJLn6UCzGlw0y6O0e/nsW5xK6UwGvA0X/jaLyUaz1PFrq7yMT2p1dz+s/ge+BMAAP//AQAA///wACsmDAIAAA=="),
},
_assestBase64Decode("L3Jlcy90cGwvZXJyb3IuaHRtbA=="): {
Name: _assestBase64Decode("L3Jlcy90cGwvZXJyb3IuaHRtbA=="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/yzLwQ6CMAwA0Pu+YukdMR7n2M0PmbSSJkshbYMo4d8Niff3MvIaGQd4IkTzT6MBnDbvauNJ0kjipPfXLN4ZfyndrssGJTxUZ00h21Iljq2a/du7qrBMUPb9Qic6jtyfqoTcI6/lBwAA//8BAAD//7dAZHVxAAAA"),
},
_assestBase64Decode("L3Jlcy90cGwvZmlsZS5odG1s"): {
Name: _assestBase64Decode("L3Jlcy90cGwvZmlsZS5odG1s"),
Mtime: 1478614292,
Content: _assestGzipBase64decode("H4sIAAAJbogA/2xTwY7bIBC9+ysQivbUenYPvbRAL71UqnrZD4iwGdsoDraATbal/HsFxk6T+jTJzHjmvXkPpvSFaMVpo6iomJfNiKQdpXOc+ub4kpKtnL2ejKhycymO2HkqqifTuPlLBR2EULdv1qLx37SNkZQKk2Sw2HEKnR7x6zRzg9cnpS1/+IAKps89cbblFCw60OceRu38R6lUPZueCgZSVAyUvqRwg+UHlEpUhHkrKkKYH8hVKz9w+ul5fqfCTAz8UEophKA7Umv3+tbk3aniZmnuyT1AN/KM96D/oGvljDFCXQMVdQ0JIYM0almDRuXpP+UZ05IbjBXhy3OG+Kp/Y6kySDTSn8yK+WZSv0QVgpWmR3I4fTgkPJ95nYKLsdp4q026Y0tFCIdTjAy8KtUUCNlohZAH1T+0OaX752o+zZL/7hYld3TpplGhXVQJAUeHMe60eXz3W1M5RVqxzE9HKamsbPpRXLOPJKfJHYcdV92Gb/pQsUdix1zL3oXP7ja4B0+Jl7ZHz+mxGaU57Zr4ovH6/45yjn/lWWcnM6zCLW5Y+xkUNzDIT3V9Dn8BAAD//wEAAP//rhRf0ccDAAA="),
},
_assestBase64Decode("L3Jlcy90cGwvZmlsZV9lZGl0Lmh0bWw="): {
Name: _assestBase64Decode("L3Jlcy90cGwvZmlsZV9lZGl0Lmh0bWw="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/3xTTY8bKRC98ytQrXY1I+009szOatdDs4eVcooSKdc4suim7MahgUC1PzTxf4+w2844SuaCGnj1XterhzR2w62poTGg2HHXOp1zDd3foNBY4kvrUApjN4rJZUg975G6YGqIIRNw3ZINvgZRcMBJpxVSDXaZFgVdWK2PA3HaR6yhs8agB+51jzWECHyj3YA1ZL1BUOw1dFnfJ7uy/lL1/FwV3eqd7vFrR707HAoJk0436FS5K1czKU4H1/SEO3pJ/irt2ZjS1V0bPKXggGfaO6xhaw11s4fJJO6AR02Eydfwcb6d383FvPr0PP1z+ngAHp1usQvOYKoBV7Pp/UNFO+Ih8fvJ9K/JP0JX6xw88IRfBpvQcD1QWIZ2yJwsFa1+yMT7gdqOjwJnesX+8E2OT6eVSc27hMtxNv8d+7zqDHOrIx4OoqpASduveE5tDSJhFrZfCWczVdGvQK0Cf2Md8rc2kxT610LipUBx7RyIReO0//xTmY3F7UmmfB3pj1FUTJYJ6YT62ud/H39/Ap7CNl9G9X/whJ4+hG0uquNQ29MpqGvUOFIpzvSKnRMeEyq2zsV+vk2WcMZkGwyqIeOiUNzADx3eSnEEMCmOxbJFT5jUVdLy0PSWrsLOy37MVEOeN+TvYrK9TntQUowsTIoSuGOmx1/MbbKRXni4zsLgUg+OqnUutSfEBapYjCns9gvSzSIP8QZ+awzcsu9AJu0y6R5H2y6P92y7sTk6vZ/54PGpKJzgivNvAAAA//8BAAD//yR4z29BBAAA"),
},
_assestBase64Decode("L3Jlcy90cGwvZmlsZV9uZXcuaHRtbA=="): {
Name: _assestBase64Decode("L3Jlcy90cGwvZmlsZV9uZXcuaHRtbA=="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/3SSb47TMBDFv/sUo0FIINGm3WURdGMfgQtQVDnxZOMqsY096R9VvTtymwQqxBdLjn7z3rznlMYewBqJlUElxO1adzolie0XVI6O0NiOysLYgxJl42MPPXHrjcTgEyPomq13EovMIbCOb8QSbRN3mUYlSuvCwMDnQBJbaww5BKd7kugDwkF3A0l0dLxt8H/Y2DjTl8vS2Hi93kY6XVGnsv933dOmLO4fxAQVD6JMJ54k8zlr4pQ8772ovePoO4TE544kHq3hdvO8WoUTQtDMFJ3EH9vjdrEttsufl/Wn9csVIXS6ptZ3hqJEetusn56XfGLwEZ5W68+rr4Ve7pN3CJF+DTaSAT2wb3w9JGDL2asfEkM/cN3CPwbjK6m/UkPelRzPyafnup85sI6kH5N8e3n/ihD9MUlcrxDGRkYpVGUxzSkx6omyJscU1UOfaah6y3OLSR8I8n0ss2IHFbtFiLbX8ZyFRxVRFrlpJcRkUKY62sCQYi2xiJSKfSoMNXroeLlPefZOzKgSIUR/Ou9YV7s0hA/4rjL4UfwBRWmbqHsa883/5dSGsSl0+rxx3tFrdrjjCuA3AAAA//8BAAD//0Es6wUcAwAA"),
},
_assestBase64Decode("L3Jlcy90cGwvbGF5b3V0Lmh0bWw="): {
Name: _assestBase64Decode("L3Jlcy90cGwvbGF5b3V0Lmh0bWw="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/4xVMXP0NBDt71cIFXwNsaBjwFYDdAyhSEN1o5PX9gZZMtqVw43xf2cs+/I5d5dMrvBa0r63T2+lc/nNr4+/PP3152+i497pQ3kJYGp9KHtgI2xnIgFXMnHz8KPUh9Kh/1t0EZpKqgiksG9VY0a0wRdogxQigqskdSGyTSyWBSnUhdCbHio5IrwMIbIUNngGz5V8wZq7qoYRLTzkwXcCPTIa90DWOKh+KL6Xb2lM4i7EHUmdXgCvkijaXUbHPNBPSrXIXToVNvSqwzqpYYjh3/MCZWQHepoKSqen5X2ep6ng9U38J9ZMMU3FCJEw+Hku1Qo6lGQjDiwo2s2eZ1LP/ySI5+KZpC7VmnDxcbWKzw6oA2C5N9YSqcYZLiyR/CwgL10QW63RxE30cVNcyb16+fNOltq6fwr1WR/KGkeBdSW9GeVu2INPUh/E+iuTy7MOGj5SOr1dzRkOhXWGqJKnEGuIxwYjsdSluexAagJaBJXK6FI5vCbY5drgG2ylXuNdwDRhIwqk30OLfp7f52rQgdTL8x0e8PVHeHMKiaXO4RPSE4FpQeo13gBKldzr4HVysX2170vEtuMv+wLlDvFq9n23p6nwgdHCcmSvZK46aTA+99IG78HykdhwIqm/9ScaloMyGH+7xytwIohHn/pj8A495MLL3GMePgU2blGQucSycEv4Qf+2chtn8YfpL/sRd9K+Wu9Cm1u1xru9yv12BHcLvmFCn4nw/nG9PTa5sWI/rnHUdzosrQMTl7+KnHAV3s/Lz2kqlos7z58DrLc2tMcax13F7e6r/D34HwAA//8BAAD//1aN3LcmBgAA"),
},
_assestBase64Decode("L3Jlcy90cGwvbG9naW4uaHRtbA=="): {
Name: _assestBase64Decode("L3Jlcy90cGwvbG9naW4uaHRtbA=="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/6SSzW6rQAxG9zyF5X3EXd8ySF10l0XfoBoYh1iav3pMUt6+moRESZt2UxaDhI8/HzCd4wOwMzi4f9g33SBt/+hoTuDobSkGfZo4bsooRLE27ZIECKT75AzmVBTBjsopGmxPLIJamUgN8k7eKn5qY/KukPZN52mi6PpnFzjCtrZ07fqsgcv1XWFNAnjI1OpmkjTnewag45hnBV0yGVT6UIRoAxmsJ0L2dqR98o7E4EtUEljSLHCuHqyfySDejRlTVEkeQeh9ZiF3b9U6PvTNH0WzLeWYxCGstrkcv8i+XpFfLR/J/TS1zENgvQZu0wR1oWvsoBEGjZssHKwst8lrbtfe7LmtGvV+/h4XhHdiA61vdf1FoOjiyaDjkr1d/scU6Qn7rj3jPcAnAAAA//8BAAD//xNoF86+AgAA"),
},
_assestBase64Decode("L3Jlcy90cGwvbmV0d29yay5odG1s"): {
Name: _assestBase64Decode("L3Jlcy90cGwvbmV0d29yay5odG1s"),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/7RV227zKBC+jp9ixCry7kXrdHvQbmr7VRCGSUxCAAHO4e1/gQ85KK3a/vp9EQh88803wzCUnjtpA3jHK1I49MXGFw3z+PbyuPGkLoseUGd3kd7wLYZHab4ERu+l0TfQrAysUQhSVERjOBi3fQgNqbMyuPgj0g7zUmBcFHJ/CaVK+kCF3I97PpwUVvmOubXUy2d7zOusXBm3u7JaSRXQ0bgeDVcSlfAYhStcoxb1oBV6YFkMyxkAzAbBBylCW5GnxWJO6mwGkASnsR033xb2SGquJOqwLIvQDgAxAl6icSm17QKEk8WKBDwGAlwx7ysSBT5wo4MzioyxJdPl/6/zPMaU9+xU2hw022FFpgUCVjGOrVEC3bgO0pIayiKI+2I7j+5bUnun0exT2WSSfSOrYV5yYF1ooYtcBIIM0SBWyaiymHI75bjGI1edwCuxdTaLE8UaVNdieYt825jjKLiNBQV7pjqscrlja8whYVAMYBSkThtl0fP9jHvj7xFv/O+xcn+XlvszL6Svz+EwD+Kc0/QPPjzQ6I12Tk0e8x+dLtMnsMwFMCuIbJ+fKWhzcMzWnVPXNciN8pbp6uWTCnSK7ljg7ZfK8L/vCk1jvPj99KJlFNFPHIXcx34W29CgwDKNij6NzWlYFXJPGypvJP2brt9fA01rfPiC0fMHl/I62MWcjO2PKsOZopE+hph8WRbaP+gr0k++7rgBJ9dtIHBN9JrSUTIAo7mSfFsRa407nujQmylXyPTf/xBoHa4qsmF71j8qy3cCddotCzY5TsMMMpjekOg/NPTyKUmd/Pwi5eftqfxDkw60L1hkYmzLLcYwlk/2+G726FbKHJbQSiFQ5+MFhKHa4fK76LHPfdAXV/YO5nX+EeR2eazcYR7VDsIbI04RncYY8lTbV+nKsp4xddYpb+m8aLxYqM9VNCCTy4nuFwAAAP//AQAA//9PV68ZYAgAAA=="),
},
_assestBase64Decode("L3Jlcy90cGwvcmVwbGF5Lmh0bWw="): {
Name: _assestBase64Decode("L3Jlcy90cGwvcmVwbGF5Lmh0bWw="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/7yWzW7jNhCAz9FTEFy3kpDEToEeClsSsLfeWrQ99BBAoMSRxYSmtNTYG8PVuxdDSv6L481ui704Cjl/nPlmyESqDVMy5YXkWZAUdpYFSdXYFRMlqsakfGah1WLL2QqwbmTK26ZD7nT8Tk7inKGwS8CU54UW5pmzUouuSzlt5g+ciTU2ZbNqNSCkvKkqckfOB7n6Z555e8lMqg1FoUDLDjALEg1LMDIrRKfKZDb8FyQoCg3012YJ1pmPmK2tniczrLMEZZYo066R4baFlCO8IGdGrCDlzlbuVfK11ZxthF5Dyne76WG1708Ocl82Bm2jeZbMyPgM7cG9z8+7XXvxY7cWPk396ltu2SW/vzYdvttr7Yp36pPW3nXQ2ZjwYx+1khLMqZc/YNUgfJTSnvsqtQKDuWr7nr/D0LqDVyZozWvPDohcwqUGIcEy4tkgKxvd2HRpxTaLGqO3bN0Bwxo6YJ479llhzdrWNi/bOJmRUsZ+NEXXLoJEsNpClfIPnDWm1Kp8TvkkCj9gkXs3YTzFZrnUEMU86+rm86xWEpKZoDhPiXW9s1fkrMOthpRL1VEYc9MY4Fmw21lhlsAmz3eTzTz1lXIafe9qH1Dxd7vJc9/78gdU/4CxvaYyEl7uJvlmnk42fR8wxhj13NuQeAe5t3qU+Em++afG1bV+cE3Ldjswklw5bgKWoBwVtOiQ05Jv70sZtYBra5iQMkcbYa26u3AIKYx5dkvpvFaRQd8XIqdcj3ZizlAhpdlvMqk62ufZ30ONfFRD1I738SwH7r8A3BIwb4UVq+49x3PwuPG4BAzvQtJ2p3QmrpIzar2Bybg9gHLOCXOcvEnJdUIoym/G4yijklL4Co6vZGOfsu8ERnAKxhtk7HaqYvCJHQ10xvjvv/35F6ezv+aGbtRvAocUw7vQ6X8NOu4Kv8JO626Ei/AQO+xb4XGB/k/0/Gd4Dln7TvS8Y6rsN/1DjN5Hw+1AabwXWi3NvASDYBfX78/x1XZ4p+0z7q+4c/VuXawUcjYmtUDDCjT3rVUrYbecjeqvb8uzO9ELhPF0I3QUehHKc5B0rTDjgVbCLpW511Dh/KeHh/aF6kwCXxvXaVhSWSjxakRexEc01oeA279/3e+4FyRdaVWLWVCtjX9hnoHQFE/xLriZuI9pKywY7CKOlsfTShkZcXceHk9BlHU0molI6UZV0cSjNBWINuIDQZLHbp8xNu5bWDUb+HgmtQhubnrQHZxJn1m7O1cJbnr6GNiuhO5gEfTB4ZRDszTF011rId7R82EjLKNWTTnNhkdaogFxXLCQSA2HaoXHjRwOpQ+PWLZqWePis5JYz395+CFkjXmG7bpNQ7T5Rui8rI1Ygm/ZR85vWwu3/JHHIWu1KKFutASbhm7wOfTDzA+qITh5KbgxFen4cTncCx4cbKF/FY8eRmUaQ2E2TpDwQ7iHMByLMsLxyNE+8rGkUbwYy6BFB4swk6Bpfhy5odnBF/SpqojO0lSsKZ7SlDfFExHvC8QuUigqBBtR4WJnYuTlSN5CC0Z6GSdCP2/DcVqdAyM3RIiBF8xd1tMrTYGSDX2x8GobodPJFK1aRYMatWtM26qKaDfl3DXFwcEXMN83xisNQoVT0Lcboa/0Vh/QqB5HwL8AAAD//wEAAP//O8CrWS8PAAA="),
},
_assestBase64Decode("L3Jlcy90cGwvcmVwbGF5X2RpcmVjdC5odG1s"): {
Name: _assestBase64Decode("L3Jlcy90cGwvcmVwbGF5X2RpcmVjdC5odG1s"),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/8xRy27DIBA8l69A1KoSKTI9J2t+xcKGxih+IFijRpR/rzZucqjSey+wj9mZgQXjEnemEZ0RikEX5OP8WMLEdY9umRuRc0153eno+nqrtmsYSxF8sjgs5jdmq1K/H3WMjaBe+y5uarc4WD/qa2tcsD2SOuputIpBrz3xq+eE/G3uoj/97QjknYDlHPR8try6HKp0bLaJs8VS2Muj114OVZuOTZWozDkHDApwUDlXl1KOIHFQgEaBm/2KHK/eNgLtJwo+68nSywkoeNLjuqVt+hpwou9RIGlUYlCkaWdTCrvfzw36Jf4bhyB/tsJAkjkKYh+cR8WqnXh9ssl9HdducrjbnxjIO5iBNC6pbwAAAP//AQAA///vqKAEcQIAAA=="),
},
_assestBase64Decode("L3Jlcy90cGwvdXNlYWdlLmh0bWw="): {
Name: _assestBase64Decode("L3Jlcy90cGwvdXNlYWdlLmh0bWw="),
Mtime: 1448806296,
Content: _assestGzipBase64decode("H4sIAAAJbogA/6xUwW7jNhA9L79iwL3EQCBZzmJrEJJ6aBfbQ4sumvRUFAElji1uJVIhR3YMr4H21gL9gv2W/E0/IL9QkLIcJ21vRQ6RZx4fZ948Tq70BrQqeKXmvGR55dKS5T3UrfS+4M0bXv7oUa4xT/tnibe8zJKvWo2GHh9+x7XoG2vw8eGPI9BhyTwSbPVKQ0PUQ+/s/U6w+A8a6wkeHz5DXpX7fdLH6G2IHg55WpVHWG/dv8BC9AhjeRqvyn0n27aURjmrFQwenYdamvAVDseDX4dcOAZkoZNGrtGNZfk8HQnYix4XyTW6DToRGEEbQreSNZ41udFeE+QSGoergqe8vEbvtTXwrfYEH6J2Ml650bgF2bZADY6aOLwb0BNQ4+ywboAa7ceKEqZNxPkj20q3hA5W1nWXIwneE2jTDzQ1mq+sIahta13hUJWf8jRE4t0YSoduaEn3LUJtjdKkrfEJw3XUS+QjGe16LHhg57CR7YAFl58qDp52LRZ8qxU14s28v+flpSbocLzcgfYgwTqoErazw7n6DvtW7iblx18gp+6TaYgvtL9KvrNKr3bww4jzo+pKb8qJvxsB51om79/d/PXrbx++v76BXjrZSQ/SqBHToFTofJKngWXkGk0VS/0oN9LXTvcU6pR1o3GDoElM+FjmajB1kA4cbp0mvHB4N9szAIA0jZVZheBJOsp9L82kXByMqNoBeRnRehWOJsH1RcG3221SSa2GpLYdn+3ZKwA45WM6e3sVk89St1IpV/Bs8UUyD39imXFIU/BoVACFVqZkJpZZPBxsdLtGSqRSF1zySy75LGbSNOZ66ynxSBdcTckDe5WnoaHy2CucmkWjYswhDS4Ic8cOp6cZpIsmuANbfcSaoJE+eNsjSCKnq4HQB3dcDK4twqBEmoZ+8V52fYuh51S21dClrfb0Za1VkS3ezh4fPj8bjK8b7CSIOGsWV4yAFzwsrhQByznrJTUg4IyZrZEE7GutxE/Z4urnAwsyCNgfWHC4kR2CANZL77fWqfDdITVWCXj/7oZNogII2EulxOSUi18uN7P94dIj/SO2fhYLkRbNU2S2PxyAnUbyPzMzdnKQgNdh3TiU7bidw3C32jePD3/i+sxBV4ts+R8v9s30Yr+xp+caca/DwgrKM4Nb/2TzJ1rGXlvT7mCZjUu/k1Q3L9BimZ07ecHG/PFVnKfmy/nJftEhfwMAAP//AQAA//+CM/4q6AYAAA=="),
},
_assestBase64Decode("L3Jlcy92ZXJzaW9u"): {
Name: _assestBase64Decode("L3Jlcy92ZXJzaW9u"),
Mtime: 1478614292,
Content: _assestGzipBase64decode("H4sIAAAJbogA/zLQM9UzAgAAAP//AQAA//9B6J7GBQAAAA=="),
},
},
}
================================================
FILE: serve/auth.go
================================================
package serve
import (
"encoding/base64"
"net/http"
"strings"
"github.com/hidu/goutils/str_util"
)
var proxyAuthorizatonHeader = "Proxy-Authorization"
func getAuthorInfo(req *http.Request) *User {
defaultInfo := new(User)
authheader := strings.SplitN(req.Header.Get(proxyAuthorizatonHeader), " ", 2)
if len(authheader) != 2 || authheader[0] != "Basic" {
return defaultInfo
}
userpassraw, err := base64.StdEncoding.DecodeString(authheader[1])
if err != nil {
return defaultInfo
}
userpass := strings.SplitN(string(userpassraw), ":", 2)
if len(userpass) != 2 {
return defaultInfo
}
return &User{Name: userpass[0], PswMd5: str_util.StrMd5(userpass[1]), Psw: userpass[1]}
}
func (ser *ProxyServe) checkUserLogin(userInfo *User) bool {
if userInfo == nil || ser.Users == nil {
return false
}
if userInfo.SkipCheckPsw {
return true
}
if user, has := ser.Users[userInfo.Name]; has {
return user.PswMd5 == userInfo.PswMd5
}
return false
}
// (ser.conf.AuthType == AuthType_Basic && !ser.CheckUserLogin(reqCtx.User))
func (ser *ProxyServe) checkHTTPAuth(reqCtx *requestCtx) bool {
switch ser.conf.AuthType {
case authTypeNO:
return true
case authTypeBasic:
return ser.checkUserLogin(reqCtx.User)
case authTypeBasicWithAny:
return reqCtx.User.Name != ""
case authTypeBasicTry:
if reqCtx.ClientSession.RequestNum == 1 {
return reqCtx.User.Name != ""
}
return true
default:
return false
}
return false
}
================================================
FILE: serve/broadcast.go
================================================
package serve
import (
"strconv"
)
// broadcastReq broadcast request to user's browser
func (ser *ProxyServe) broadcastReq(reqCtx *requestCtx) bool {
req := reqCtx.Req
data := make(map[string]any)
data["docid"] = strconv.Itoa(reqCtx.Docid)
data["sid"] = reqCtx.SessionID % 10000
data["host"] = req.Host
data["client_ip"] = req.RemoteAddr
urlPath := req.URL.Path
if req.URL.RawQuery != "" {
urlPath += "?" + req.URL.RawQuery
}
data["path"] = urlPath
data["url"] = req.URL.String()
if req.Method == "CONNECT" && !ser.conf.SslOn {
data["path"] = "https req,unknow path"
}
data["method"] = req.Method
data["replay"] = reqCtx.IsRePlay
hasSend := ser.wsSer.broadcastReq(req, reqCtx, data)
return hasSend
}
================================================
FILE: serve/certs.go
================================================
package serve
import (
"crypto/tls"
"crypto/x509"
"log"
"os"
)
func newCaCert(caCert []byte, caKey []byte) (tls.Certificate, error) {
ca, err := tls.X509KeyPair(caCert, caKey)
if err != nil {
log.Println("NewCaCert error:", err)
return ca, err
}
if ca.Leaf, err = x509.ParseCertificate(ca.Certificate[0]); err != nil {
log.Println("NewCaCert error:", err)
return ca, err
}
log.Println("NewCaCert Ok")
return ca, nil
}
// getSslCert get user's caCert or use the default buildin
func getSslCert(caCertPath string, caKeyPath string) (ca tls.Certificate, err error) {
if caCertPath == "" {
caCert := Assest.GetContent("/res/private/client_cert.pem")
caKey := Assest.GetContent("/res/private/server_key.pem")
return newCaCert([]byte(caCert), []byte(caKey))
}
cert, err := os.ReadFile(caCertPath)
if err != nil {
return ca, err
}
key, err := os.ReadFile(caKeyPath)
if err != nil {
return ca, err
}
return newCaCert(cert, key)
}
================================================
FILE: serve/config.go
================================================
package serve
import (
"crypto/tls"
"errors"
"fmt"
"log"
"net/http"
"net/url"
"strings"
"github.com/Unknwon/goconfig"
"github.com/hidu/goutils/fs"
"github.com/hidu/goutils/str_util"
)
// Config pproxy's config
type Config struct {
Port int
AdminPort int
Title string
Notice string
AuthType int
DataDir string
FileDir string
ResponseSave int
SessionView int
DataStoreDay float64
ParentProxy *url.URL
SslOn bool
SslCert tls.Certificate
ModifyRequest bool
}
const (
authTypeNO = 0
authTypeBasic = 1
authTypeBasicWithAny = 2
authTypeBasicTry = 3
responseSaveAll = 0
responseSaveHasBroad = 1 // has show
sessionViewALL = 0
sessionViewIPOrUser = 1
)
// User user struct
type User struct {
Name string
Psw string
PswMd5 string
IsAdmin bool
SkipCheckPsw bool
}
// String string format
func (u *User) String() string {
return fmt.Sprintf("Name:%s,Psw:%s,isAdmin:%v,SkipCheckPsw:%v", u.Name, u.Psw, u.IsAdmin, u.SkipCheckPsw)
}
// ConfigString one line in file
func (u *User) ConfigString() string {
return fmt.Sprintf("name:%s\tpsw:%s\tis_admin:%v\tpsw_md5:%s", u.Name, u.Psw, u.IsAdmin, u.PswMd5)
}
const (
contentEncoding = "Content-Encoding"
)
// "0:no auth | 1:basic auth | 2:basic auth with any name"
// GetVersion get current version
func GetVersion() string {
return Assest.GetContent("res/version")
}
// GetDemoConf get the demo config
func GetDemoConf() string {
return strings.TrimSpace(Assest.GetContent("res/conf/demo.conf"))
}
func (u *User) isPswEq(psw string) bool {
return u.PswMd5 == str_util.StrMd5(psw)
}
// LoadConfig load the pproxy's config
func LoadConfig(confPath string) (*Config, error) {
gconf, err := goconfig.LoadConfigFile(confPath)
if err != nil {
log.Println("load config", confPath, "failed,err:", err)
return nil, err
}
config := new(Config)
config.Port = gconf.MustInt(goconfig.DEFAULT_SECTION, "port", 8080)
config.AdminPort = gconf.MustInt(goconfig.DEFAULT_SECTION, "adminPort", 0)
if config.AdminPort == 0 {
config.AdminPort = config.Port
}
config.DataStoreDay = gconf.MustFloat64(goconfig.DEFAULT_SECTION, "dataStoreDay", 0)
if config.DataStoreDay < 0 {
log.Println("wrong DataStoreDay,skip")
config.DataStoreDay = 0
}
config.Title = gconf.MustValue(goconfig.DEFAULT_SECTION, "title")
config.Notice = gconf.MustValue(goconfig.DEFAULT_SECTION, "notice")
config.DataDir = gconf.MustValue(goconfig.DEFAULT_SECTION, "dataDir", "../data/")
config.FileDir = gconf.MustValue(goconfig.DEFAULT_SECTION, "fileDir", "../file/")
_authType := strings.ToLower(gconf.MustValue(goconfig.DEFAULT_SECTION, "authType", "none"))
authTypes := map[string]int{"none": 0, "basic": 1, "basic_any": 2, "basic_try": 3, "try_basic": 3}
hasError := false
if authType, has := authTypes[_authType]; has {
config.AuthType = authType
} else {
hasError = true
log.Println("conf error,unknow value authType:", _authType)
}
_responseSave := strings.ToLower(gconf.MustValue(goconfig.DEFAULT_SECTION, "responseSave", "all"))
responseSaveMap := map[string]int{"all": 0, "only_broadcast": 1}
if responseSave, has := responseSaveMap[_responseSave]; has {
config.ResponseSave = responseSave
} else {
hasError = true
log.Println("conf error,unknow value responseSave:", _authType)
}
_sessionView := strings.ToLower(gconf.MustValue(goconfig.DEFAULT_SECTION, "sessionView", "all"))
sessionViewMap := map[string]int{"all": 0, "ip_or_user": 1}
if sessionView, has := sessionViewMap[_sessionView]; has {
config.SessionView = sessionView
} else {
hasError = true
log.Println("conf error,unknow value responseSave:", _authType)
}
parentProxy := gconf.MustValue(goconfig.DEFAULT_SECTION, "parentProxy", "")
if parentProxy != "" {
_urlObj, err := url.Parse(parentProxy)
if err != nil || _urlObj.Scheme != "http" {
hasError = true
log.Println("parentProxy wrong,must http proxy")
} else {
config.ParentProxy = _urlObj
}
}
config.SslOn = gconf.MustValue(goconfig.DEFAULT_SECTION, "ssl", "off") == "on"
if config.SslOn {
_sslClientCert := gconf.MustValue(goconfig.DEFAULT_SECTION, "ssl_client_cert", "")
_sslServerKey := gconf.MustValue(goconfig.DEFAULT_SECTION, "ssl_server_key", "")
cert, err := getSslCert(_sslClientCert, _sslServerKey)
if err != nil {
hasError = true
log.Println("ssl ca config error:", err)
} else {
config.SslCert = cert
}
}
config.ModifyRequest = gconf.MustValue(goconfig.DEFAULT_SECTION, "modifyRequest", "on") == "on"
if hasError {
return config, errors.New("config error")
}
return config, nil
}
type configHosts map[string]string
// loadHosts 读取host配置文件
func loadHosts(confPath string) (hosts configHosts, err error) {
hosts = make(configHosts)
if !fs.FileExists(confPath) {
return
}
hostsByte, err := fs.FileGetContents(confPath)
if err != nil {
log.Println("load hosts_file failed:", confPath, err)
return nil, err
}
hostsArr := str_util.LoadText2Slice(string(hostsByte))
for _, v := range hostsArr {
if len(v) != 2 {
log.Println("hosts file line wrong,ignore,", v)
continue
}
hosts[v[0]] = v[1]
}
return
}
func loadUsers(confPath string) (users map[string]*User, err error) {
users = make(map[string]*User)
if !fs.FileExists(confPath) {
return
}
userInfoByte, err := fs.FileGetContents(confPath)
if err != nil {
log.Println("load user file failed:", confPath, err)
return
}
lines := str_util.LoadText2SliceMap(string(userInfoByte))
for _, line := range lines {
name, has := line["name"]
if !has || name == "" {
continue
}
if _, has := users[name]; has {
log.Println("dup name in users:", name, line)
continue
}
user := new(User)
user.Name = name
if val, has := line["is_admin"]; has && (val == "admin" || val == "true") {
user.IsAdmin = true
}
if val, has := line["psw_md5"]; has {
user.PswMd5 = val
}
if user.PswMd5 == "" {
if val, has := line["psw"]; has {
user.Psw = val
user.PswMd5 = str_util.StrMd5(val)
}
}
users[user.Name] = user
}
return
}
func (config *Config) getTransport() *http.Transport {
if config.ParentProxy == nil {
return nil
}
tr := &http.Transport{
Proxy: func(req *http.Request) (*url.URL, error) {
if config.ParentProxy.User.Username() == "pass" {
user := getAuthorInfo(req)
urlTmp, err := url.Parse(config.ParentProxy.String())
if err != nil {
return nil, err
}
urlTmp.User = url.UserPassword(user.Name, user.Psw)
return urlTmp, nil
}
return config.ParentProxy, nil
},
}
return tr
}
================================================
FILE: serve/init.go
================================================
package serve
// 系统版本
var PproxyVersion string
func init() {
PproxyVersion = GetVersion()
}
================================================
FILE: serve/kvStore.go
================================================
package serve
import (
"time"
"github.com/boltdb/bolt"
"github.com/hidu/goutils/time_util"
)
type KV_TBALE_NAME_TYPE string
const (
KV_TABLE_REQ KV_TBALE_NAME_TYPE = "req"
KV_TABLE_RES KV_TBALE_NAME_TYPE = "res"
)
type kvStore struct {
dbPath string
db *bolt.DB
tables map[KV_TBALE_NAME_TYPE]*kvStoreTable
}
type kvStoreTable struct {
name KV_TBALE_NAME_TYPE
kv *kvStore
}
type StoreType struct {
Now int64 `json:"now"`
Data KvType `json:"data"`
}
func newStoreType(data map[string]any) *StoreType {
return &StoreType{Now: time.Now().Unix(), Data: data}
}
func newKvStore(dbPath string) (kv *kvStore, err error) {
kv = &kvStore{
dbPath: dbPath,
}
kv.db, err = bolt.Open(kv.dbPath, 0600, nil)
if err != nil {
return
}
kv.tables = make(map[KV_TBALE_NAME_TYPE]*kvStoreTable)
kv.initTable(KV_TABLE_REQ)
kv.initTable(KV_TABLE_RES)
return
}
func (kv *kvStore) initTable(name KV_TBALE_NAME_TYPE) {
kv.tables[name] = newkvStoreTable(name, kv)
}
func (kv *kvStore) GetkvStoreTable(name KV_TBALE_NAME_TYPE) (tb *kvStoreTable) {
if tb, has := kv.tables[name]; has {
return tb
}
return nil
}
func (kv *kvStore) Gc(max_life int64) {
for _, tb := range kv.tables {
tb.Gc(max_life)
}
}
func (kv *kvStore) StartGcTimer(sec int64, max_life int64) {
if max_life < 1 {
return
}
time_util.SetInterval(func() {
kv.Gc(max_life)
}, sec)
}
func newkvStoreTable(name KV_TBALE_NAME_TYPE, kv *kvStore) *kvStoreTable {
tb := &kvStoreTable{
name: name,
kv: kv,
}
tb.kv.db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(name))
return err
})
return tb
}
func (tb *kvStoreTable) Save(key []byte, val *StoreType) error {
err := tb.kv.db.Update(func(tx *bolt.Tx) error {
bk, _ := tx.CreateBucketIfNotExists([]byte(tb.name))
return bk.Put(key, dataEncode(val))
})
return err
}
func (tb *kvStoreTable) Get(key []byte) (val *StoreType, err error) {
err = tb.kv.db.View(func(tx *bolt.Tx) error {
bk := tx.Bucket([]byte(tb.name))
bs := bk.Get(key)
if len(bs) > 0 {
return dataDecode(bs, &val)
}
return nil
})
return
}
func (tb *kvStoreTable) Del(key []byte) (err error) {
err = tb.kv.db.Update(func(tx *bolt.Tx) error {
bk := tx.Bucket([]byte(tb.name))
return bk.Delete(key)
})
return
}
func (tb *kvStoreTable) Gc(gc_life int64) {
if gc_life < 1 {
return
}
max_time := time.Now().Unix() - gc_life
var val *StoreType
tb.kv.db.View(func(tx *bolt.Tx) error {
bk := tx.Bucket([]byte(tb.name))
bk.ForEach(func(k, v []byte) error {
dataDecode(v, &val)
if val != nil && val.Now < max_time {
tb.Del(k)
}
return nil
})
return nil
})
}
================================================
FILE: serve/proxy.go
================================================
package serve
import (
"io"
"log"
"net"
"net/http"
"net/http/httputil"
"strconv"
"sync"
"time"
"github.com/elazarl/goproxy"
)
type HttpProxy struct {
GoProxy *goproxy.ProxyHttpServer
ser *ProxyServe
ctxs map[string]*requestCtx
mu sync.RWMutex
goproxyMitmConnect *goproxy.ConnectAction
}
func NewHttpProxy(ser *ProxyServe) *HttpProxy {
proxy := new(HttpProxy)
proxy.ser = ser
proxy.GoProxy = goproxy.NewProxyHttpServer()
tr := ser.conf.getTransport()
if tr != nil {
proxy.GoProxy.Tr = tr
}
proxy.ctxs = make(map[string]*requestCtx)
if proxy.ser.conf.SslOn {
proxy.goproxyMitmConnect = &goproxy.ConnectAction{
Action: goproxy.ConnectMitm,
TLSConfig: goproxy.TLSConfigFromCA(&proxy.ser.conf.SslCert),
}
proxy.GoProxy.OnRequest().HandleConnectFunc(proxy.httpsHandle)
}
proxy.GoProxy.OnRequest().DoFunc(my_requestHanderFunc)
proxy.GoProxy.OnResponse().DoFunc(proxy.onResponse)
return proxy
}
func my_requestHanderFunc(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
log.Println("trace_my_requestHanderFunc call url:", r.URL.String())
return r, nil
}
const PROXY_CTX_NAME = "X-PPROXY-CTX-ID"
func (proxy *HttpProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// fmt.Println("call url:",req.URL.String())
proxy.GoProxy.ServeHTTP(rw, req)
}
func (proxy *HttpProxy) httpsHandle(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
log.Println("https conn", host, ctx.Req.URL.String())
return proxy.goproxyMitmConnect, host
}
func (proxy *HttpProxy) RoundTrip(ctx *requestCtx) {
sid := strconv.FormatInt(ctx.SessionID, 10)
ctx.Req.Header.Set(PROXY_CTX_NAME, sid)
func() {
proxy.mu.Lock()
defer proxy.mu.Unlock()
proxy.ctxs[sid] = ctx
}()
defer func() {
proxy.mu.Lock()
defer proxy.mu.Unlock()
if _, has := proxy.ctxs[sid]; has {
delete(proxy.ctxs, sid)
}
}()
if ctx.Req.Header.Get("Upgrade") != "" {
proxy.roundTripUpgrade(ctx)
return
}
proxy.ServeHTTP(ctx.Rw, ctx.Req)
}
func (proxy *HttpProxy) getReqCtx(req *http.Request) *requestCtx {
sid := req.Header.Get(PROXY_CTX_NAME)
if sid == "" {
return nil
}
proxy.mu.RLock()
defer proxy.mu.RUnlock()
if ctx, has := proxy.ctxs[sid]; has {
return ctx
}
return nil
}
func (proxy *HttpProxy) onResponse(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
if resp == nil || resp.Request == nil {
return resp
}
reqCtx := proxy.getReqCtx(resp.Request)
if reqCtx != nil {
reqCtx.saveResponse(resp)
}
return resp
}
func (proxy *HttpProxy) roundTripUpgrade(ctx *requestCtx) (err error) {
// save it,so we know it has been closed
defer func() {
resp := &http.Response{
Request: ctx.Req,
Header: make(http.Header),
Body: nil,
}
ctx.saveResponse(resp)
}()
reqDump, err := httputil.DumpRequest(ctx.Req, false)
if err != nil {
ctx.Msg = "dump req failed:" + err.Error()
return
}
ctx.SetTimePoint("startDial")
dia, err := net.Dial("tcp", ctx.DestAddr())
if err != nil {
ctx.Msg = "dia connect " + ctx.DestAddr() + " failed!" + err.Error()
return
}
defer dia.Close()
_, err = dia.Write(reqDump)
if err != nil {
return
}
hijack, _ := ctx.Rw.(http.Hijacker)
conn, _, _ := hijack.Hijack()
errc := make(chan error, 2)
cp := func(dst io.Writer, src io.Reader) {
_, err := io.Copy(dst, src)
errc <- err
time.AfterFunc(3*time.Second, func() {
dia.Close()
conn.Close()
})
}
go cp(dia, conn)
go cp(conn, dia)
<-errc
return
}
================================================
FILE: serve/reqCtx.go
================================================
package serve
import (
"encoding/base64"
"errors"
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"strings"
"time"
)
// requestCtx 一次http请求的数据结构
type requestCtx struct {
RemoteAddr string
Req *http.Request
Rw http.ResponseWriter
Host string // eg www.baidu.com
Port int // eg 80
User *User
Docid int
IsRePlay bool
SessionID int64
HasBroadcast bool
FormPost *url.Values
ClientSession *clientSession
OriginURL string
logData map[any]any
Msg string
ser *ProxyServe
startTime time.Time
timeDurations map[string]time.Duration
hasPrint bool
}
// NewRequestCtx 构建一个新的请求
func NewRequestCtx(ser *ProxyServe, rw http.ResponseWriter, req *http.Request) *requestCtx {
ctx := &requestCtx{}
ctx.Req = req
ctx.ser = ser
ctx.Rw = rw
ctx.SessionID = ser.reqNum
ctx.logData = make(map[any]any)
ctx.timeDurations = make(map[string]time.Duration)
ctx.FormPost = &url.Values{}
ctx.init()
ctx.startTime = time.Now()
return ctx
}
func (ctx *requestCtx) init() {
if ctx.Req == nil {
return
}
fixRequest(ctx.Req)
req := ctx.Req
ctx.Host, ctx.Port, _ = getHostPortFromReq(req)
ctx.User = getAuthorInfo(req)
ctx.FormPost = getPostData(req)
ctx.OriginURL = req.URL.String()
ctx.IsRePlay = len(req.Header.Get(REPLAY_FLAG)) > 0
ctx.SetLog("url", req.URL.String())
ctx.RemoteAddr = req.RemoteAddr
if _replayAddr := req.Header.Get(REPLAY_REMOTEADDR); _replayAddr != "" {
ctx.RemoteAddr = _replayAddr
}
if _replayUser := req.Header.Get(REPLAY_USER_NAME); _replayUser != "" {
ctx.User = &User{Name: _replayUser, SkipCheckPsw: true}
}
ctx.Docid = ctx.getNewDocid()
ctx.ser.regirestReq(ctx)
}
func fixRequest(req *http.Request) {
if req.Method != "CONNECT" && !req.URL.IsAbs() {
urlOrigin := req.URL.String()
urlStr := "http://" + req.Host + req.URL.Path
if req.URL.RawQuery != "" {
urlStr += "?" + req.URL.RawQuery
}
var err error
req.URL, err = url.Parse(urlStr)
if err != nil {
log.Println("fix url failed,originUrl:", urlOrigin, "err:", err)
return
}
}
}
func (ctx *requestCtx) IsLocalRequest() bool {
isLocalReq := ctx.Port == ctx.ser.conf.Port
if isLocalReq {
isLocalReq = IsLocalIP(ctx.Host)
}
return isLocalReq
}
func (ctx *requestCtx) GetIp() string {
hostInfo := strings.Split(ctx.RemoteAddr, ":")
return hostInfo[0]
}
func (ctx *requestCtx) PrintLog() {
reqID := 0
if ctx.ClientSession != nil {
reqID = ctx.ClientSession.RequestNum
}
log.Println(
"session_id:", ctx.SessionID,
"remote:", ctx.RemoteAddr,
"reqId:", reqID,
"docid:", ctx.Docid,
"uname:", ctx.User.Name,
"broadcast:", ctx.HasBroadcast,
"startTime:", ctx.startTime.Unix(),
"timeUsed:", fmt.Sprintf("%.3fs", time.Since(ctx.startTime).Seconds()),
"data:", ctx.logData,
"times:", ctx.timeDurations,
)
}
func (ctx *requestCtx) RoundTrip() {
defer func() {
ctx.hasPrint = true
ctx.SetLog("logType", "defer")
ctx.PrintLog()
}()
time.AfterFunc(10*time.Second, func() {
if !ctx.hasPrint {
ctx.SetLog("logType", "timeout10")
ctx.PrintLog()
}
})
removeHeader(ctx.Req)
rewriteCode := ctx.ser.reqRewrite(ctx)
ctx.HasBroadcast = ctx.ser.broadcastReq(ctx)
// reqDump, _ := httputil.DumpRequest(ctx.Req, true)
// fmt.Println("req dump3:\n",string(reqDump))
ctx.SetLog("js_rewrite_code", rewriteCode)
ctx.saveRequestData()
// 异步的会导致req.body dump不了,先暂时这样,对接口会有一些影响
// time.AfterFunc(1*time.Second, ctx.saveRequestData)
if rewriteCode != 200 && rewriteCode != 304 {
ctx.badGateway(errors.New("rewrite failed"))
return
}
ctx.ser.proxy.RoundTrip(ctx)
}
func (ctx *requestCtx) badGateway(err error) {
ctx.SetLog("errMsg", fmt.Sprintf("%s", err))
ctx.Rw.WriteHeader(http.StatusBadGateway)
ctx.Rw.Write([]byte("pproxy error"))
}
func (ctx *requestCtx) DestAddr() string {
return fmt.Sprintf("%s:%d", ctx.Host, ctx.Port)
}
func (ctx *requestCtx) saveRequestData() {
if ctx.ser.conf.ResponseSave == responseSaveAll ||
(ctx.ser.conf.ResponseSave == responseSaveHasBroad && ctx.HasBroadcast) {
logdata := KvType{}
logdata["host"] = ctx.Req.Host
logdata["schema"] = ctx.Req.URL.Scheme
logdata["header"] = map[string][]string(ctx.Req.Header)
logdata["url"] = ctx.Req.URL.String()
logdata["url_origin"] = ctx.OriginURL
logdata["path"] = ctx.Req.URL.Path
// logdata["cookies"] = ctx.Req.Cookies()
// logdata["now"] = time.Now().Unix()
logdata["user"] = ctx.User.Name
logdata["client_ip"] = ctx.RemoteAddr
logdata["method"] = ctx.Req.Method
logdata["form_get"] = ctx.Req.URL.Query()
logdata["replay"] = ctx.IsRePlay
logdata["msg"] = ctx.Msg
logdata["id"] = strconv.Itoa(ctx.Docid)
// 当无普通form post表单数据的时候,比如可能body也有数据
// 比如request 的content-type=application/json
dumpBody := len(*ctx.FormPost) == 0
// fmt.Println("dumpBody",dumpBody)
// dumpBody=false
reqDump, errDump := httputil.DumpRequest(ctx.Req, dumpBody)
if errDump != nil {
ctx.SetLog("dumpMsg", fmt.Sprintf("dump request failed,%s", errDump.Error()))
reqDump = []byte(fmt.Sprintf("dump failed,%s", errDump.Error()))
}
logdata["dump"] = base64.StdEncoding.EncodeToString(reqDump)
// buf := forgetRead(&ctx.Req.Body)
// logdata["dump"] = base64.StdEncoding.EncodeToString(buf.Bytes())
logdata["form_post"] = ctx.FormPost
tb := ctx.ser.mydb.GetkvStoreTable(KV_TABLE_REQ)
data := newStoreType(logdata)
err := tb.Save(IntToBytes(ctx.Docid), data)
if err != nil {
log.Println("save req failed:", err)
}
}
}
func (ctx *requestCtx) saveResponse(res *http.Response) {
if ctx.Docid < 1 || res == nil {
return
}
data := KvType{}
data["now"] = time.Now().Unix()
data["header"] = map[string][]string(res.Header)
data["status"] = res.StatusCode
data["content_length"] = res.ContentLength
data["msg"] = ctx.Msg
data["id"] = strconv.Itoa(ctx.Docid)
resDump, dumpErr := httputil.DumpResponse(res, false)
if dumpErr != nil {
log.Println("dump res err", dumpErr)
resDump = []byte("dump res failed")
}
data["dump"] = base64.StdEncoding.EncodeToString(resDump)
// data["cookies"]=res.Cookies()
body := []byte("pproxy skip")
if res.Body != nil && res.ContentLength <= ctx.ser.MaxResSaveLength {
buf := forgetRead(&res.Body)
if res.Header.Get(contentEncoding) == "gzip" {
body = []byte(gzipDocode(buf))
} else {
body = buf.Bytes()
}
l := int64(len(body))
if l > ctx.ser.MaxResSaveLength {
body = []byte(fmt.Sprintf("pproxy skip,body too large,[len=%d]", l))
}
}
data["body"] = base64.StdEncoding.EncodeToString(body)
tb := ctx.ser.mydb.GetkvStoreTable(KV_TABLE_RES)
storeData := newStoreType(data)
err := tb.Save(IntToBytes(ctx.Docid), storeData)
log.Println("save_res", ctx.SessionID, "docid=", ctx.Docid, "body_len=", len(data["body"].(string)), err)
}
func (ctx *requestCtx) SetLog(k, v any) {
ctx.logData[k] = v
}
func (ctx *requestCtx) SetTimePoint(key string) {
ctx.timeDurations[key] = time.Since(ctx.startTime)
}
func (ctx *requestCtx) getNewDocid() int {
idStr := fmt.Sprintf("%s%d", time.Now().Format("200601021504"), ctx.ser.reqNum)
id, err := parseDocID(idStr)
if err == nil {
return id
}
log.Println("GetNewDocid failed", idStr, err)
return int(time.Now().UnixNano() + ctx.ser.reqNum)
}
================================================
FILE: serve/req_modifer.go
================================================
package serve
import (
"errors"
"fmt"
"log"
"os"
"strings"
"sync"
"github.com/hidu/goutils/fs"
"github.com/robertkrimen/otto"
)
var rewriteJsTpl = Assest.GetContent("res/sjs/req_rewrite.js")
/*
* request动态修改引擎
* 使用javascript 来对请求进行修改
*/
type requestModifier struct {
mu sync.RWMutex
jsVm *otto.Otto
jsFns map[string]*otto.Value
canMod bool
ser *ProxyServe
}
func NewRequestModifier(ser *ProxyServe) *requestModifier {
reqMod := &requestModifier{
jsVm: otto.New(),
jsFns: make(map[string]*otto.Value),
ser: ser,
}
return reqMod
}
func (reqMod *requestModifier) getJsPath(name string) string {
baseName := fmt.Sprintf("%s/req_rewrite_%d", reqMod.ser.configDir, reqMod.ser.conf.Port)
if name == "" {
return fmt.Sprintf("%s.js", baseName)
}
return fmt.Sprintf("%s_%s.js", baseName, name)
}
func (reqMod *requestModifier) tryLoadJs(name string) (err error) {
jsContent, err := reqMod.getJsContent(name)
if jsContent != "" && err == nil {
err = reqMod.parseJs(jsContent, name, false)
if err != nil {
log.Println("load rewrite js failed:", err)
return err
}
log.Println("load rewrite js[", name, "] suc")
}
return nil
}
func (reqMod *requestModifier) loadAllJs() error {
if !reqMod.ser.conf.ModifyRequest {
log.Println("ignore requestModifier loadAllJs")
return nil
}
names := []string{""}
for _, user := range reqMod.ser.Users {
names = append(names, user.Name)
}
for _, name := range names {
err := reqMod.tryLoadJs(name)
if err != nil {
return err
}
}
return nil
}
func (reqMod *requestModifier) getJsContent(name string) (content string, err error) {
jsPath := reqMod.getJsPath(name)
if fs.FileExists(jsPath) {
script, err := os.ReadFile(jsPath)
if err == nil {
return string(script), nil
}
return "", err
}
return "", nil
}
func (reqMod *requestModifier) CanMod() bool {
return reqMod.canMod
}
func (reqMod *requestModifier) parseJs(jsStr string, name string, save2File bool) error {
jsStr = strings.TrimSpace(jsStr)
rewriteJs := strings.Replace(rewriteJsTpl, "CUSTOM_JS", jsStr, 1)
rewriteJs = strings.Replace(rewriteJs, "PPROXY_HOST", fmt.Sprintf("127.0.0.1:%d", reqMod.ser.conf.Port), 1)
reqMod.mu.Lock()
defer reqMod.mu.Unlock()
if reqMod.ser.Debug {
log.Println("jsvm_execute:", rewriteJs)
}
reqMod.jsVm.Run(rewriteJs)
jsFn, err := reqMod.jsVm.Get("pproxy_rewrite")
if err != nil {
log.Println("rewrite js init error:", err)
return err
}
if strings.HasPrefix(jsStr, "//ignore") {
if _, has := reqMod.jsFns[name]; has {
delete(reqMod.jsFns, name)
}
log.Println("req_mod [", name, "] ignore")
} else {
reqMod.jsFns[name] = &jsFn
log.Println("req_mod [", name, "] register suc")
}
reqMod.canMod = true
if save2File {
jsPath := reqMod.getJsPath(name)
err = fs.FilePutContents(jsPath, []byte(jsStr))
log.Println("save rewritejs ", jsPath, err)
}
return err
}
func (reqMod *requestModifier) getJsFnByName(name string) (*otto.Value, error) {
names := []string{name, ""}
for _, name := range names {
if jsFn, has := reqMod.jsFns[name]; has {
return jsFn, nil
}
}
return nil, errors.New("no rewrite rules")
}
func (reqMod *requestModifier) rewrite(data map[string]any, name string) (map[string]any, error) {
reqMod.mu.Lock()
defer reqMod.mu.Unlock()
reqJsObj, _ := reqMod.jsVm.Object(`req={}`)
reqJsObj.Set("origin", data)
jsFn, err := reqMod.getJsFnByName(name)
if err != nil {
return nil, err
}
defer func() {
if caught := recover(); caught != nil {
log.Println("fatal:requestModifer recover:", caught)
}
}()
js_ret, err_js := (*jsFn).Call(*jsFn, reqJsObj)
if err_js != nil {
log.Println("parse js error:", err_js)
return nil, err_js
}
if !js_ret.IsObject() {
log.Println("wrong req_rewirte return value,not object:", js_ret)
return nil, fmt.Errorf("wrong req_rewirte return value,not object.%t", js_ret)
}
obj, export_err := js_ret.Export()
if export_err != nil {
return nil, export_err
}
reqObjNew := obj.(map[string]any)
return reqObjNew, nil
}
================================================
FILE: serve/req_replay.go
================================================
package serve
import (
"fmt"
"net/http"
"net/url"
"strings"
)
const (
REPLAY_FLAG = "Proxy-pproxy_replay"
REPLAY_REMOTEADDR = "Proxy-pproxy_remoteaddr"
REPLAY_USER_NAME = "Proxy-pproxy_user"
)
func (ctx *webRequestCtx) handleReplay() {
if ctx.req.Method == "POST" {
ctx.reqReplayPost()
return
}
docidStr := strings.TrimSpace(ctx.req.FormValue("id"))
if docidStr == "" {
ctx.w.WriteHeader(http.StatusBadRequest)
ctx.w.Write([]byte("empty id param"))
return
}
docid, errInt := parseDocID(docidStr)
if errInt != nil {
ctx.w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(ctx.w, "param id[%s] error:\n%s", docidStr, errInt)
return
}
reqDoc, _ := ctx.ser.getRequestByDocid(docid)
if reqDoc == nil {
ctx.w.WriteHeader(http.StatusNotFound)
ctx.w.Write([]byte("request doc not found!"))
return
}
_url := fmt.Sprintf("%s", reqDoc.Data["url"])
u, err := url.Parse(_url)
if err != nil {
ctx.w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(ctx.w, "parse url[%s] error\n%s", _url, err)
return
}
u.RawQuery = ""
ctx.values["req"] = reqDoc
ctx.values["action_url"] = u.String()
ctx.render("replay.html", true)
}
var replaySkipHeaders = map[string]int{"Content-Length": 1}
func (ctx *webRequestCtx) reqReplayPost() {
replay := ctx.req.FormValue("replay")
basic := make(map[string]string)
basic["action_url"] = strings.TrimSpace(ctx.req.FormValue("basic_action_url"))
method := strings.TrimSpace(strings.ToUpper(ctx.req.FormValue("basic_method")))
basic["method"] = method
host := strings.TrimSpace(ctx.req.FormValue("basic_host"))
basicRemoteAddr := ctx.req.FormValue("basic_RemoteAddr")
basicUser := ctx.req.FormValue("basic_user")
header := getFormValuesWithPrefix(ctx.req.Form, "header_")
get := getFormValuesWithPrefix(ctx.req.Form, "get_")
post := getFormValuesWithPrefix(ctx.req.Form, "post_")
formData := make(map[string]any)
formData["basic"] = basic
formData["header"] = header
formData["get"] = get
formData["post"] = post
ctx.values["form"] = formData
if replay == "direct" {
ctx.render("replay_direct.html", true)
return
}
reqBd := ""
_url := basic["action_url"]
if len(get) > 0 {
formValues := make(url.Values)
for k, v := range get {
for _, _v := range v {
formValues.Add(k, _v)
}
}
if strings.Contains(_url, "?") {
_url += "&"
} else {
_url += "?"
}
_url += formValues.Encode()
}
if len(post) > 0 {
formValues := make(url.Values)
for k, v := range post {
for _, _v := range v {
formValues.Add(k, _v)
}
}
reqBd = formValues.Encode()
}
replayReq, err := http.NewRequest(method, _url, strings.NewReader(reqBd))
if err != nil {
ctx.w.Write([]byte("build request failed\n" + err.Error()))
return
}
if host != "" {
replayReq.Host = host
}
replayReq.Header.Set(REPLAY_FLAG, "replay")
replayReq.Header.Set(REPLAY_REMOTEADDR, basicRemoteAddr)
replayReq.Header.Set(REPLAY_USER_NAME, basicUser)
for k, v := range header {
if _, has := replaySkipHeaders[k]; has {
continue
}
replayReq.Header.Set(k, strings.Join(v, ";"))
}
ctx.ser.ServeHTTPProxy(ctx.w, replayReq)
}
================================================
FILE: serve/req_rewrite.go
================================================
package serve
import (
"bytes"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strconv"
"strings"
)
func (ser *ProxyServe) reqRewriteByjs(reqCtx *requestCtx) int {
modifer := ser.reqMod
if !modifer.CanMod() {
return 304
}
req := reqCtx.Req
schema := req.URL.Scheme
originURL := req.URL.String()
originGetQuery := req.URL.Query()
// /================================================================
headerKv := make(map[string]string)
headerKv["method"] = req.Method
headerKv["schema"] = schema
headerKv["path"] = req.URL.Path
_host, portInt, _ := getHostPortFromReq(req)
headerKv["host"] = _host
headerKv["port"] = strconv.Itoa(portInt)
username := ""
psw := ""
if req.URL.User != nil {
username = req.URL.User.Username()
psw, _ = req.URL.User.Password()
}
headerKv["username"] = username
headerKv["proxy_user"] = reqCtx.User.Name
headerKv["password"] = psw
// ===================================================================
rewriteData := make(map[string]any)
rewriteData["header"] = headerKv
rewriteData["get"] = originGetQuery
rewriteData["post"] = *reqCtx.FormPost
_buf := forgetRead(&reqCtx.Req.Body)
var rawBody string
// 暂时只考虑gip的,其他的压缩就不支持了
if req.Header.Get(contentEncoding) == "gzip" {
rawBody = gzipDocode(_buf)
} else {
rawBody = _buf.String()
}
rewriteData["body"] = rawBody
reqObjNew, rErr := modifer.rewrite(rewriteData, reqCtx.User.Name)
if rErr != nil {
log.Println("rewrite failed:", rErr)
}
headerKvNew := make(map[string]string)
isHeaderChange := false
skipHeader := false
var err error
urlStrNew := getMapValStr(reqObjNew, "url")
if urlStrNew != "" {
req.URL, err = url.Parse(urlStrNew)
if err != nil || req.URL.Scheme != "http" {
log.Println("new url wrong!url is:", urlStrNew, err)
return 500
}
req.Host = req.URL.Host
skipHeader = true
}
if !skipHeader {
for k, v := range headerKv {
_newVal := getMapValStr(reqObjNew, k)
headerKvNew[k] = _newVal
if _newVal != v {
isHeaderChange = true
}
}
}
// -------------------------------------------------------
var getNew url.Values
isGetChange := false
if _get, has := reqObjNew["get"]; has {
getNew = _reqMapToURLValue(_get)
isGetChange = checkURLValuesChange(originGetQuery, getNew)
}
// -------------------------------------------------------
var postNew url.Values
isPostChange := false
if schema == "http" {
if _post, has := reqObjNew["post"]; has {
postNew = _reqMapToURLValue(_post)
isPostChange = checkURLValuesChange(*reqCtx.FormPost, postNew)
}
}
isBodyChange := false
bodyNew := ""
if _bodyNew, has := reqObjNew["body"]; has {
bodyNew = _bodyNew.(string)
isBodyChange = rawBody != bodyNew
}
hostAddr := getMapValStr(reqObjNew, "hostAddr")
isHostAddrChange := hostAddr != ""
if ser.Debug {
fmt.Println("rewriteChange:", "is_get_change:", isGetChange, "new_get:", getNew,
"isPostChange:", isPostChange, "new_post:", postNew,
"isHostAddrChange:", isHostAddrChange, "newHostAddr:", hostAddr,
"isBodyChange:", isBodyChange,
)
}
// /===============================================================================
if !isHeaderChange && !isGetChange && !isPostChange && !isHostAddrChange && !isBodyChange {
return 304
}
// /===============================================================================
var urlBase string
if isHeaderChange {
// schema := headerKvNew["schema"]
urlBase = schema + "://"
if headerKvNew["username"] != "" {
urlBase += fmt.Sprintf("%s:%s@", headerKvNew["username"], headerKvNew["password"])
}
urlBase += headerKvNew["host"]
if headerKvNew["port"] != "" && headerKvNew["port"] != "80" {
urlBase += ":" + headerKvNew["port"]
}
urlBase += headerKvNew["path"]
} else {
if req.URL.RawQuery == "" {
urlBase = originURL
} else {
urlBase = originURL[:len(originURL)-len(req.URL.RawQuery)-1]
}
}
if isGetChange {
urlBase += "?" + getNew.Encode()
} else {
urlBase += "?" + req.URL.RawQuery
}
if isHeaderChange || isGetChange {
var urlErr error
req.URL, urlErr = url.Parse(urlBase)
if ser.Debug {
log.Println("DEBUG req_rewrite,url_new:", urlBase, "req_new:", req.URL)
}
if urlErr != nil {
return 502
}
req.Host = req.URL.Host
}
// ////////////////////////////////////////////////////////////////////////////
if isPostChange || isBodyChange {
buf := bytes.NewBuffer([]byte{})
var bodyData string
if isPostChange {
bodyData = postNew.Encode()
} else if isBodyChange {
bodyData = bodyNew
}
req.Header.Del("Content-Length")
if req.Header.Get(contentEncoding) == "gzip" {
tmp := gzipEncode([]byte(bodyData)).Bytes()
buf.Write(tmp)
} else {
buf.WriteString(bodyData)
}
req.ContentLength = int64(buf.Len())
req.Body = io.NopCloser(buf).(io.ReadCloser)
}
// //////////////////////////////////////////////////////////////////////////
if isHostAddrChange {
req.URL.Host = hostAddr
if ser.Debug {
log.Println("rewrite host addr:", req.URL.Host, "==>", hostAddr)
}
}
return 200
}
func (ser *ProxyServe) reqRewrite(reqCtx *requestCtx) int {
if !ser.conf.ModifyRequest {
return 304
}
if reqCtx.Req.Method == "CONNECT" {
return 304
}
originHost := reqCtx.Req.Host + "#" + reqCtx.Req.URL.Host
statusCode1 := ser.reqRewriteByjs(reqCtx)
newHost := reqCtx.Req.Host + "#" + reqCtx.Req.URL.Host
if ser.Debug {
log.Println("rewrte_debug:\n", "originHost:", originHost, "\nnewHost:", newHost, "\n")
}
statusCode2 := 304
if originHost == newHost {
statusCode2 = ser.reqRewriteByHosts(reqCtx.Req)
}
if statusCode1 == 200 || statusCode2 == 200 {
return 200
}
if statusCode1 >= 500 || statusCode2 >= 500 {
return 502
}
return 304
}
func (ser *ProxyServe) reqRewriteByHosts(req *http.Request) int {
if ser.hosts == nil {
return 304
}
if host, has := ser.hosts[req.URL.Host]; has {
log.Println("rewrite host:", req.URL.Host, "==>", host)
req.URL.Host = host
return 200
}
hostInfo := strings.Split(req.URL.Host, ":")
if len(hostInfo) == 1 {
if req.URL.Scheme == "http" {
hostInfo = append(hostInfo, "80")
}
}
reqHost := strings.Join(hostInfo, ":")
if host, has := ser.hosts[reqHost]; has {
log.Println("rewrite host:", req.Host, "==>", host)
req.URL.Host = host
return 200
}
if host, has := ser.hosts[hostInfo[0]]; has {
log.Println("rewrite host:", req.Host, "==>", host)
req.URL.Host = host
if !strings.Contains(host, ":") {
req.URL.Host += ":" + hostInfo[1]
}
return 200
}
return 304
}
func _reqMapToURLValue(values any) url.Values {
uValues := make(url.Values)
if values == nil {
return uValues
}
vs := values.(map[string]any)
for k, arr := range vs {
switch value := arr.(type) {
case []any:
for _, v := range value {
uValues.Add(k, fmt.Sprintf("%v", v))
}
case any:
uValues.Set(k, fmt.Sprintf("%v", value))
default:
log.Println("unkonw type:", value)
}
}
return uValues
}
================================================
FILE: serve/serve.go
================================================
package serve
import (
"fmt"
"log"
"math/rand"
"net/http"
"net/http/httputil"
"os"
"path/filepath"
"sync"
"sync/atomic"
"time"
"github.com/hidu/goutils/fs"
"github.com/hidu/goutils/time_util"
)
type ProxyServe struct {
mydb *kvStore
proxy *HttpProxy
wsSer *wsServer
startTime time.Time
MaxResSaveLength int64
mu sync.RWMutex
Debug bool
conf *Config
configDir string
hosts configHosts
Users map[string]*User
ProxyClients map[string]*clientSession
reqNum int64
reqMod *requestModifier
}
type KvType map[string]any
func (ser *ProxyServe) ServeHTTP(w http.ResponseWriter, req *http.Request) {
atomic.AddInt64(&ser.reqNum, 1)
// reqDump, _ := httputil.DumpRequest(req, true)
// fmt.Println("req dump:\n",string(reqDump))
ctx := NewRequestCtx(ser, w, req)
if ctx.Host == "p.info" || ctx.Host == "pproxy.info" {
ser.handleUserInfo(w, req)
return
}
if ctx.Host == "pproxy.man" || ctx.Host == "pproxy.com" || ctx.IsLocalRequest() {
ser.handleLocalReq(w, req)
} else {
if ser.Debug {
reqDumpDebug, _ := httputil.DumpRequest(req, req.Method == "GET")
log.Println("DEBUG req BEFORE:\nurl_full:", req.URL.String(), "\nschema:", req.URL.Scheme, "\n", string(reqDumpDebug), "\n\n")
}
if !ser.checkHTTPAuth(ctx) {
ctx.SetLog("msg", "login required")
ctx.Rw.Header().Set("Proxy-Authenticate", "Basic realm=auth required")
ctx.Rw.WriteHeader(http.StatusProxyAuthRequired)
ctx.Rw.Write([]byte("auth required"))
return
}
ctx.RoundTrip()
}
}
// for replay
func (ser *ProxyServe) ServeHTTPProxy(w http.ResponseWriter, req *http.Request) {
atomic.AddInt64(&ser.reqNum, 1)
ctx := NewRequestCtx(ser, w, req)
ctx.RoundTrip()
}
func (ser *ProxyServe) Start() {
addr := fmt.Sprintf("%s:%d", "", ser.conf.Port)
fmt.Println("proxy listen at ", addr)
defer log.Println("pproxy exit")
ser.wsInit()
wg := new(sync.WaitGroup)
wg.Add(1)
go func() {
err := http.ListenAndServe(addr, ser)
log.Println(err)
fmt.Println(err)
wg.Done()
}()
wg.Add(1)
go func() {
ser.startAdmin()
wg.Done()
}()
wg.Wait()
}
func (ser *ProxyServe) startAdmin() {
if ser.conf.Port == ser.conf.AdminPort {
return
}
addr := fmt.Sprintf(":%d", ser.conf.AdminPort)
fmt.Println("admin http service listen at ", addr)
httpSer := http.NewServeMux()
httpSer.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
ser.handleLocalReq(w, req)
})
http.ListenAndServe(addr, httpSer)
}
func (ser *ProxyServe) getResponseByDocid(docid int) (resData *StoreType, err error) {
tb := ser.mydb.GetkvStoreTable(KV_TABLE_RES)
return tb.Get(IntToBytes(docid))
}
func (ser *ProxyServe) getRequestByDocid(docid int) (reqData *StoreType, err error) {
tb := ser.mydb.GetkvStoreTable(KV_TABLE_REQ)
return tb.Get(IntToBytes(docid))
}
func (ser *ProxyServe) getHostsFilePath() string {
return fmt.Sprintf("%s/hosts_%d", ser.configDir, ser.conf.Port)
}
func (ser *ProxyServe) loadHosts() {
ser.mu.Lock()
defer ser.mu.Unlock()
hostsPath := ser.getHostsFilePath()
log.Println("load hosts:", hostsPath)
ser.hosts, _ = loadHosts(hostsPath)
}
func NewProxyServe(confPath string, port int) (*ProxyServe, error) {
conf, err := LoadConfig(confPath)
if err != nil {
log.Println("load config faield", err)
return nil, err
}
if port > 0 && port < 65535 {
conf.Port = port
}
absPath, err := filepath.Abs(confPath)
if err != nil {
log.Println("get config path failed", confPath)
return nil, err
}
GetVersion()
os.Chdir(filepath.Dir(absPath))
setupLog(conf.DataDir, conf.Port)
proxy := new(ProxyServe)
proxy.configDir = filepath.Dir(absPath)
proxy.Users, _ = loadUsers(proxy.configDir + "/users")
conf.FileDir, _ = filepath.Abs(conf.FileDir)
proxy.conf = conf
proxy.reqMod = NewRequestModifier(proxy)
err = proxy.reqMod.loadAllJs()
if err != nil {
return nil, err
}
proxy.loadHosts()
dbPath := fmt.Sprintf("%s/%d.db", conf.DataDir, conf.Port)
// proxy.mydb = NewTieDb(fmt.Sprintf("%s/%d/", conf.DataDir, conf.Port), conf.DataStoreDay)
proxy.mydb, err = newKvStore(dbPath)
if err != nil {
log.Fatalln("init db failed", err)
}
proxy.startTime = time.Now()
proxy.MaxResSaveLength = 2 * 1024 * 1024
rand.Seed(time.Now().UnixNano())
proxy.ProxyClients = make(map[string]*clientSession)
proxy.proxy = NewHttpProxy(proxy)
time_util.SetInterval(func() {
proxy.cleanExpiredSession()
}, 60)
proxy.mydb.StartGcTimer(60, int64(conf.DataStoreDay*86400))
return proxy, nil
}
func setupLog(dataDir string, port int) {
logPath := fmt.Sprintf("%s/%d.log", dataDir, port)
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
if err != nil {
log.Println("create log file failed [", logPath, "]", err)
os.Exit(2)
}
log.SetOutput(logFile)
time_util.SetInterval(func() {
if !fs.FileExists(logPath) {
logFile.Close()
logFile, _ = os.OpenFile(logPath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
log.SetOutput(logFile)
}
}, 30)
}
================================================
FILE: serve/sessions.go
================================================
package serve
import (
"log"
"time"
)
type clientSession struct {
Ip string
Port string
RequestNum int
FirstRequestTime time.Time
LastRequestTime time.Time
User *User
}
func (ser *ProxyServe) regirestReq(reqCtx *requestCtx) {
ip := reqCtx.GetIp()
now := time.Now()
ser.mu.Lock()
defer ser.mu.Unlock()
var session *clientSession
client, has := ser.ProxyClients[ip]
if has {
session = client
} else {
session = &clientSession{
Ip: ip,
RequestNum: 0,
FirstRequestTime: now,
LastRequestTime: now,
}
}
if reqCtx.User.Name == "" && session.User != nil {
reqCtx.User = session.User
} else if reqCtx.User.Name != "" {
session.User = reqCtx.User
}
session.LastRequestTime = now
session.RequestNum++
if ser.Debug {
log.Println("session_debug:", session)
}
ser.ProxyClients[ip] = session
reqCtx.ClientSession = session
if !has {
ser.wsSer.broadProxyClientNum()
}
}
func (ser *ProxyServe) cleanExpiredSession() {
ser.mu.Lock()
defer ser.mu.Unlock()
now := time.Now()
deleteIps := []string{}
for ip, session := range ser.ProxyClients {
t := now.Sub(session.LastRequestTime)
if t.Minutes() > 10 {
deleteIps = append(deleteIps, ip)
}
}
for _, ip := range deleteIps {
delete(ser.ProxyClients, ip)
log.Println("session expired:ip=", ip)
}
ser.wsSer.broadProxyClientNum()
}
================================================
FILE: serve/util.go
================================================
package serve
import (
"bytes"
"compress/gzip"
// "encoding/base64"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"strconv"
"strings"
// "gopkg.in/vmihailenco/msgpack.v2"
)
// Int64ToBytes int64转换为byte
func Int64ToBytes(i int64) []byte {
var buf = make([]byte, 8)
binary.BigEndian.PutUint64(buf, uint64(i))
return buf
}
// IntToBytes int转换为byte
func IntToBytes(i int) []byte {
var buf = make([]byte, 8)
binary.BigEndian.PutUint64(buf, uint64(i))
return buf
}
// IsLocalIP 判断一个host是否本地ip
func IsLocalIP(host string) bool {
ips, _ := net.LookupIP(host)
for _, ip := range ips {
if ip.IsLoopback() {
return true
}
}
if addrs, err := net.InterfaceAddrs(); err == nil {
for _, addr := range addrs {
_, ipG, err := net.ParseCIDR(addr.String())
if err == nil {
for _, ip := range ips {
if ipG.Contains(ip) {
return true
}
}
}
}
}
return false
}
func forgetRead(reader *io.ReadCloser) *bytes.Buffer {
buf := bytes.NewBuffer([]byte{})
io.Copy(buf, *reader)
*reader = io.NopCloser(buf).(io.ReadCloser)
return bytes.NewBuffer(buf.Bytes())
}
func dataEncode(data any) []byte {
bf, err := json.Marshal(data)
if err != nil {
log.Println("data_encode_err", err)
return bf
}
return bf
}
func dataDecode(dataInput []byte, out any) error {
if len(dataInput) == 0 {
return errors.New("empty dataInput")
}
err := json.Unmarshal(dataInput, &out)
if err != nil {
log.Println("json_decode_err:", err, "dataInput:", string(dataInput))
return err
}
return err
}
func getMapValStr(m map[string]any, k string) string {
if val, has := m[k]; has {
return fmt.Sprintf("%s", val)
}
return ""
}
func gzipDocode(buf *bytes.Buffer) string {
if buf.Len() < 1 {
return ""
}
gr, err := gzip.NewReader(buf)
defer gr.Close()
if err == nil {
bdBt, _ := io.ReadAll(gr)
return string(bdBt)
}
log.Println("unzip body failed", err)
return ""
}
func gzipEncode(data []byte) *bytes.Buffer {
buf := bytes.NewBuffer([]byte{})
gw := gzip.NewWriter(buf)
defer gw.Close()
gw.Write(data)
return buf
}
func parseURLInputAsSlice(input string) []string {
arr := strings.Split(input, "|")
var result []string
for _, val := range arr {
val = strings.TrimSpace(val)
if val != "" {
result = append(result, val)
}
}
return result
}
func getFormValuesWithPrefix(values url.Values, prefix string) map[string][]string {
result := make(map[string][]string)
for k, v := range values {
if strings.HasPrefix(k, prefix) {
k1 := strings.TrimPrefix(k, prefix)
result[k1] = v
}
}
return result
}
func getTextAreaHeightByString(mystr string, minHeight int) int {
height := (len(strings.Split(mystr, "\n")) + 1) * 25
if height < minHeight {
height = minHeight
}
return height
}
func getHostPortFromReq(req *http.Request) (host string, port int, err error) {
host, port, err = parseHostPort(req.Host)
if err == nil && port == 0 {
switch req.URL.Scheme {
case "http":
port = 80
break
case "https":
port = 443
break
default:
break
}
}
return
}
func parseHostPort(hostPortstr string) (host string, port int, err error) {
var portStr string
if !strings.Contains(hostPortstr, ":") {
hostPortstr += ":0"
}
host, portStr, err = net.SplitHostPort(hostPortstr)
if err != nil {
return
}
port, err = strconv.Atoi(portStr)
if err != nil {
return
}
return
}
func checkURLValuesChange(first url.Values, second url.Values) (change bool) {
for k, v := range first {
secV, has := second[k]
if !has {
return true
}
if len(v) != len(secV) || fmt.Sprintf("%v", v) != fmt.Sprintf("%v", secV) {
return true
}
}
for k, v := range second {
firstV, has := first[k]
if !has {
return true
}
if len(v) != len(firstV) || fmt.Sprintf("%v", v) != fmt.Sprintf("%v", firstV) {
return true
}
}
return false
}
func parseDocID(strid string) (docid int, err error) {
docid64, parseErr := strconv.ParseUint(strid, 10, 64)
if parseErr == nil {
return int(docid64), nil
}
return 0, parseErr
}
func removeHeader(req *http.Request) {
for k := range req.Header {
if len(k) > 5 && k[:6] == "Proxy-" {
req.Header.Del(k)
}
}
}
func getPostData(req *http.Request) (post *url.Values) {
post = new(url.Values)
if strings.Contains(req.Header.Get("Content-Type"), "x-www-form-urlencoded") {
buf := forgetRead(&req.Body)
var bodyStr string
if req.Header.Get(contentEncoding) == "gzip" {
bodyStr = gzipDocode(buf)
} else {
bodyStr = buf.String()
}
var err error
*post, err = url.ParseQuery(bodyStr)
if err != nil {
log.Println("parse post err", err, "url=", req.URL.String())
}
}
return post
}
func headerEncode(data []byte) []byte {
t := bytes.Replace(data, []byte("\r"), []byte("\\r"), -1)
t = bytes.Replace(t, []byte("\n"), []byte("\\n"), -1)
return t
}
================================================
FILE: serve/web.go
================================================
package serve
import (
"bytes"
"encoding/base64"
"fmt"
"html"
"log"
"net"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"text/template"
"time"
"github.com/hidu/goutils/fs"
"github.com/hidu/goutils/html_util"
"github.com/hidu/goutils/object"
)
type webRequestCtx struct {
values map[string]any
user *User
isLogin bool
isAdmin bool
req *http.Request
w http.ResponseWriter
ser *ProxyServe
}
var cookieName = "pproxy"
func (ser *ProxyServe) handleLocalReq(w http.ResponseWriter, req *http.Request) {
accessLogStr := "web_access " + req.Method + " " + req.URL.String() + " " + req.RemoteAddr + " refer:" + req.Referer()
defer (func() {
log.Println(accessLogStr)
})()
if strings.HasPrefix(req.URL.Path, "/socket.io/") {
ser.wsSer.server.ServeHTTP(w, req)
return
}
if strings.HasPrefix(req.URL.Path, "/f/") {
req.URL.Path = req.URL.Path[3:]
http.FileServer(http.Dir(ser.conf.FileDir)).ServeHTTP(w, req)
return
}
if strings.HasPrefix(req.URL.Path, "/res/") {
Assest.HTTPHandler("/").ServeHTTP(w, req)
return
}
values := make(map[string]any)
values["title"] = ser.conf.Title
values["subTitle"] = ""
values["version"] = PproxyVersion
values["notice"] = ser.conf.Notice
values["port"] = strconv.Itoa(ser.conf.Port)
values["userOnlineTotal"] = len(ser.ProxyClients)
_host, _, _ := getHostPortFromReq(req)
values["pproxy_host"] = _host
values["pproxy_port"] = ser.conf.Port
ctx := &webRequestCtx{
values: values,
w: w,
req: req,
ser: ser,
}
ctx.checkLogin()
funcMap := make(map[string]func())
funcMap["/"] = ctx.handle_index
funcMap["/about"] = ctx.handle_about
funcMap["/config"] = ctx.handleConfig
funcMap["/useage"] = ctx.handle_useage
funcMap["/replay"] = ctx.handleReplay
funcMap["/login"] = ctx.handle_login
funcMap["/logout"] = ctx.handle_logout
funcMap["/response"] = ctx.handle_response
funcMap["/file"] = ctx.handle_file
if fn, has := funcMap[req.URL.Path]; has {
if len(req.URL.Path) > 1 {
ctx.values["subTitle"] = req.URL.Path[1:] + " |"
}
fn()
} else {
ctx.showError("404")
}
}
func (ser *ProxyServe) web_checkLogin(req *http.Request) (user *User, isLogin bool) {
if req == nil {
return
}
cookie, err := req.Cookie(cookieName)
if err != nil {
return
}
info := strings.SplitN(cookie.Value, ":", 2)
if len(info) != 2 {
return
}
if user, has := ser.Users[info[0]]; has {
if user.PswMd5 == info[1] {
return user, true
}
}
return
}
func (ctx *webRequestCtx) checkLogin() {
user, isLogin := ctx.ser.web_checkLogin(ctx.req)
if isLogin {
ctx.user = user
ctx.isLogin = true
ctx.isAdmin = user.IsAdmin
}
ctx.values["isLogin"] = ctx.isLogin
ctx.values["user"] = ctx.user
ctx.values["isAdmin"] = ctx.isAdmin
}
func (ctx *webRequestCtx) handle_index() {
ctx.render("network.html", true)
}
func (ctx *webRequestCtx) handle_useage() {
ctx.render("useage.html", true)
}
func (ctx *webRequestCtx) getRewriteJsInfo(name string, title string) map[string]any {
info := make(map[string]any)
jsStr, _ := ctx.ser.reqMod.getJsContent(name)
re := regexp.MustCompile(`use_file\(["'](.+)["']\)`)
matches := re.FindAllStringSubmatch(jsStr, -1)
// fmt.Println(matches)
var useFile []map[string]any
tmpNames := make(map[string]int)
for _, subMatch := range matches {
if len(subMatch) != 2 {
continue
}
use := make(map[string]any)
fileName := strings.TrimSpace(subMatch[1])
use["name"] = subMatch[0]
use["file"] = fileName
if _, has := tmpNames[fileName]; has {
continue
}
tmpNames[fileName] = 1
isURL := strings.HasPrefix(fileName, "http://")
use["isUrl"] = isURL
if isURL {
use["url"] = subMatch[1]
} else {
webFile, err := newWebFileInfo(ctx.ser.conf.FileDir, fileName)
if err != nil {
continue
}
use["url"] = webFile.link()
defer webFile.Close()
}
useFile = append(useFile, use)
}
info["name"] = name
info["use_file"] = useFile
info["title"] = title
info["rewriteJs"] = html.EscapeString(jsStr)
info["jsHeight"] = getTextAreaHeightByString(jsStr, 100)
return info
}
func (ctx *webRequestCtx) handleConfig() {
if ctx.req.Method == "GET" {
jsDataArr := make([]any, 0, 2)
jsDataArr = append(jsDataArr, ctx.getRewriteJsInfo("", "global config"))
if ctx.isLogin {
jsDataArr = append(jsDataArr, ctx.getRewriteJsInfo(ctx.user.Name, ctx.user.Name+"'s config"))
}
ctx.values["jss"] = jsDataArr
hostsByte, _ := fs.FileGetContents(ctx.ser.getHostsFilePath())
ctx.values["hosts"] = html.EscapeString(string(hostsByte))
ctx.values["hostsHeight"] = getTextAreaHeightByString("", 100)
ctx.render("config.html", true)
} else if ctx.req.Method == "POST" {
if !ctx.isLogin {
ctx.jsAlert("login first")
return
}
do := ctx.req.PostFormValue("type")
var err error
if do == "js" {
name := strings.TrimSpace(ctx.req.PostFormValue("name"))
if !ctx.isAdmin && name != ctx.user.Name {
ctx.jsAlert("you are not admin")
return
}
jsStr := strings.TrimSpace(ctx.req.PostFormValue("js"))
err = ctx.ser.reqMod.parseJs(jsStr, name, true)
} else if do == "hosts" {
if !ctx.isAdmin {
ctx.jsAlert("you are not admin")
return
}
hosts := strings.TrimSpace(ctx.req.PostFormValue("hosts"))
log.Println("hosts_update", hosts)
err = fs.FilePutContents(ctx.ser.getHostsFilePath(), []byte(hosts))
ctx.ser.loadHosts()
}
if err != nil {
ctx.jsAlert("save failed,err:" + err.Error())
} else {
ctx.w.Write([]byte(""))
}
}
}
func (ctx *webRequestCtx) handle_response() {
docid, uintParseErr := parseDocID(ctx.req.FormValue("id"))
if uintParseErr == nil {
responseData, _ := ctx.ser.getResponseByDocid(docid)
if responseData == nil {
ctx.showError("response not found")
} else {
walker := object.NewInterfaceWalker(map[string]any(responseData.Data))
var contentType string
if typeHeader, has := walker.GetStringSlice("/header/Content-Type"); has {
contentType = strings.Join(typeHeader, ";")
}
customContentType := ctx.req.FormValue("type")
// set custom content type
if customContentType != "" {
switch customContentType {
case "json":
contentType = "application/json"
case "html":
contentType = "text/html;charset=utf-8"
default:
contentType = customContentType
}
}
if contentType != "" {
ctx.w.Header().Set("Content-Type", contentType)
}
if statusCode, has := walker.GetInt("/status"); has {
ctx.w.WriteHeader(statusCode)
}
if bodyStr, has := walker.GetString("/body"); has {
bodyByte, err := base64.StdEncoding.DecodeString(bodyStr)
if err == nil {
ctx.w.Write(bodyByte)
} else {
log.Println("decode body failed", err)
}
} else {
ctx.showError("response body not found")
}
}
} else {
ctx.showError("param err")
}
}
func (ctx *webRequestCtx) jsAlert(msg string) {
fmt.Fprintf(ctx.w, "", html.EscapeString(msg))
}
func (ctx *webRequestCtx) jsAlertJump(msg string, urlStr string) {
fmt.Fprintf(ctx.w, "", html.EscapeString(msg), urlStr)
}
func (ctx *webRequestCtx) handle_about() {
ctx.render("about.html", true)
}
func (ctx *webRequestCtx) handle_logout() {
cookie := &http.Cookie{Name: cookieName, Value: "", Path: "/"}
http.SetCookie(ctx.w, cookie)
http.Redirect(ctx.w, ctx.req, "/", 302)
}
func (ctx *webRequestCtx) handle_login() {
if ctx.req.Method == "GET" {
ctx.render("login.html", true)
} else {
name := strings.TrimSpace(ctx.req.FormValue("name"))
psw := strings.TrimSpace(ctx.req.FormValue("psw"))
if name == "" {
ctx.jsAlert("empty name")
return
}
if user, has := ctx.ser.Users[name]; has {
if user.isPswEq(psw) {
log.Println("login suc,name=", name)
cookie := &http.Cookie{
Name: cookieName,
Value: fmt.Sprintf("%s:%s", name, user.PswMd5),
Path: "/",
Expires: time.Now().Add(86400 * time.Second),
}
http.SetCookie(ctx.w, cookie)
ctx.w.Write([]byte(""))
} else {
log.Println("login failed psw incorrect,name=", name, "psw=", psw)
ctx.jsAlert("password incorrect")
}
return
}
log.Println("login failed not exists,name=", name, "psw=", psw)
ctx.jsAlert("user not exists")
}
}
func (ctx *webRequestCtx) render(name string, layout bool) {
html := render_html(name, ctx.values, layout)
ctx.w.Write([]byte(html))
}
func (ctx *webRequestCtx) showError(msg string) {
ctx.values["error"] = msg
ctx.values["subTitle"] = "Error Page |"
ctx.render("error.html", true)
}
func (ctx *webRequestCtx) showErrorOrAlert(msg string) {
if ctx.req.Method == "POST" {
ctx.jsAlert(msg)
} else {
ctx.showError(msg)
}
}
func reader_html_include(fileName string) string {
html := Assest.GetContent("/res/tpl/" + fileName)
myfn := template.FuncMap{
"my_include": func(name string) string {
return reader_html_include(name)
},
}
tpl, _ := template.New("page_include").Delims("{%", "%}").Funcs(myfn).Parse(html)
var bf []byte
w := bytes.NewBuffer(bf)
tpl.Execute(w, make(map[string]string))
body := w.String()
return body
}
func render_html(fileName string, values map[string]any, layout bool) string {
html := reader_html_include(fileName)
funcs := template.FuncMap{
"escape": func(str string) string {
return url.QueryEscape(str)
},
"my_include": func(fileName string) string {
return "include (" + fileName + ") with Delims {%my_include %}"
},
}
tpl, _ := template.New("page").Funcs(funcs).Parse(html)
var bf []byte
w := bytes.NewBuffer(bf)
tpl.Execute(w, values)
body := w.String()
if layout {
values["body"] = body
return render_html("layout.html", values, false)
}
return html_util.Html_reduceSpace(body)
}
func (ser *ProxyServe) handleUserInfo(w http.ResponseWriter, req *http.Request) {
host, _, _ := net.SplitHostPort(req.RemoteAddr)
data := "client ip:" + host
w.Write([]byte(data))
}
================================================
FILE: serve/web_file.go
================================================
package serve
import (
"fmt"
"io"
"log"
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/hidu/goutils/fs"
)
type webFileInfo struct {
Name string
RootDir string
IsDir bool
Size int64
Link string
fullPath string
file *os.File
subFileInfos []*webFileInfo
}
func newWebFileInfo(rootDir, name string) (*webFileInfo, error) {
rootDir = filepath.Clean(rootDir + "/")
fullPath := filepath.Clean(fmt.Sprintf("%s/%s", rootDir, name))
if !strings.HasPrefix(fullPath, rootDir) {
return nil, fmt.Errorf("unsafe path:%s", name)
}
f, err := os.Open(fullPath)
if err != nil {
return nil, err
}
stat, err := f.Stat()
if err != nil {
return nil, err
}
info := &webFileInfo{
Name: strings.TrimLeft(fullPath[len(rootDir):], "/"),
RootDir: rootDir,
IsDir: stat.IsDir(),
Size: stat.Size(),
fullPath: fullPath,
file: f,
}
info.Link = info.link()
return info, nil
}
func (f *webFileInfo) String() string {
return fmt.Sprintf("Name:%s\nRootDir:%s\nisDir:%v\nSize:%d\nfullPath:%s\n", f.Name, f.RootDir, f.IsDir, f.Size, f.fullPath)
}
func (f *webFileInfo) link() string {
values := make(url.Values)
values.Set("name", f.Name)
if !f.IsDir {
values.Set("op", "edit")
}
return "/file?" + values.Encode()
}
func (f *webFileInfo) getContent() string {
if f.IsDir {
return ""
}
data, err := io.ReadAll(f.file)
if err != nil {
log.Println("read file failed:", err)
return ""
}
return string(data)
}
func (f *webFileInfo) Close() {
f.file.Close()
if f.subFileInfos != nil {
for _, info := range f.subFileInfos {
info.Close()
}
}
}
func (f *webFileInfo) subFiles() ([]*webFileInfo, error) {
names, err := f.file.Readdirnames(0)
if err != nil {
return nil, err
}
fileInfos := make([]*webFileInfo, 0)
for _, name := range names {
info, err := newWebFileInfo(f.RootDir, fmt.Sprintf("%s/%s", f.Name, name))
if err != nil {
log.Println("read file err,skip.name=", name, err)
} else {
fileInfos = append(fileInfos, info)
}
}
f.subFileInfos = fileInfos
return fileInfos, nil
}
func (ser *ProxyServe) getWebFilePath(name string) (fullPath string, nameNew string, err error) {
rootDir := filepath.Clean(ser.conf.FileDir + "/")
fullPath = filepath.Clean(fmt.Sprintf("%s/%s", rootDir, name))
if !strings.HasPrefix(fullPath, rootDir) {
return "", "", fmt.Errorf("unsafe path:%s", name)
}
nameNew = fullPath[len(rootDir):]
re := regexp.MustCompile(`^[\w/\-\.]*$`)
if !re.MatchString(nameNew) {
err = fmt.Errorf("illegal path:%s", nameNew)
}
return fullPath, nameNew, err
}
func (ctx *webRequestCtx) handle_file() {
if !ctx.isLogin {
ctx.showError("need login")
return
}
opMap := make(map[string]func())
opMap["edit"] = ctx.handle_file_edit
opMap["new"] = ctx.handle_file_new
opMap["del"] = ctx.handle_file_del
opMap["save"] = ctx.handle_file_save
op := ctx.req.FormValue("op")
if fn, has := opMap[op]; has {
fn()
return
}
name := ctx.req.FormValue("name")
if !ctx.isAdmin && name == "" {
name = ctx.user.Name
dirFullPath, _, err := ctx.ser.getWebFilePath(name)
if err != nil {
ctx.showError("file dir wrong")
return
}
if !fs.FileExists(dirFullPath) {
os.MkdirAll(dirFullPath, os.ModePerm)
}
}
dirInfo, err := newWebFileInfo(ctx.ser.conf.FileDir, name)
if err != nil {
ctx.showError("open file dir failed:" + name)
return
}
defer dirInfo.Close()
ctx.values["currentDir"] = dirInfo.Name
ctx.values["isSubDir"] = dirInfo.Name != ""
if !ctx.isAdmin && !strings.Contains(dirInfo.Name, "/") {
ctx.values["isSubDir"] = false
}
files, err := dirInfo.subFiles()
if err != nil {
ctx.showError(err.Error())
return
}
ctx.values["files"] = files
ctx.render("file.html", true)
}
func (ctx *webRequestCtx) handle_file_edit() {
name := ctx.req.FormValue("name")
if name == "" {
ctx.showError("params wrong")
return
}
info, err := newWebFileInfo(ctx.ser.conf.FileDir, name)
if err != nil {
ctx.showError("read file info failed:" + err.Error())
return
}
defer info.Close()
if info.IsDir {
ctx.showError("params wrong,only file can view")
return
}
ctx.values["file"] = info
fileContent := info.getContent()
ctx.values["fileContent"] = fileContent
ctx.values["fileContentRows"] = len(strings.Split(fileContent, "\n")) + 8
ctx.render("file_edit.html", true)
}
func (ctx *webRequestCtx) handle_file_del() {}
func (ctx *webRequestCtx) handle_file_new() {
dirFullPath, dirNew, err := ctx.ser.getWebFilePath(ctx.req.FormValue("dir"))
if err != nil {
ctx.showErrorOrAlert("params err:" + err.Error())
return
}
ctx.values["dir"] = dirNew
if ctx.req.Method == "GET" {
finfo, fErr := os.Stat(dirFullPath)
if fErr != nil || !finfo.IsDir() {
ctx.showErrorOrAlert("open dir failed")
return
}
ctx.render("file_new.html", true)
} else if ctx.req.Method == "POST" {
name := strings.TrimSpace(ctx.req.FormValue("name"))
if name == "" {
ctx.jsAlert("empty filename")
return
}
fpath := ctx.req.FormValue("dir") + "/" + ctx.req.FormValue("name")
fileFullPath, fileName, err := ctx.ser.getWebFilePath(fpath)
if err != nil {
ctx.jsAlert("wrong fileName")
return
}
if fileName == "" || strings.HasSuffix(fileName, "/") {
ctx.jsAlert("wrong file name")
return
}
if fs.FileExists(fileFullPath) {
ctx.jsAlert("file already exists")
return
}
if !strings.HasPrefix(fileFullPath, dirFullPath) {
ctx.jsAlert("file name wrong")
return
}
if !ctx.user.IsAdmin && !strings.HasPrefix(fileName+"/", "/"+ctx.user.Name+"/") {
ctx.jsAlert("file path wrong:" + fileName)
return
}
dirName := filepath.Dir(fileFullPath)
if !fs.FileExists(dirName) {
os.MkdirAll(dirName, os.ModePerm)
}
content := ctx.req.FormValue("content")
wErr := fs.FilePutContents(fileFullPath, []byte(content))
if wErr != nil {
ctx.jsAlert("write file failed")
return
}
finfo, _ := newWebFileInfo(ctx.ser.conf.FileDir, fpath)
defer finfo.Close()
ctx.jsAlertJump("save suc", finfo.link())
}
}
func (ctx *webRequestCtx) handle_file_save() {
nameOrigin := ctx.req.PostFormValue("nameOrigin")
name := ctx.req.PostFormValue("name")
content := ctx.req.PostFormValue("content")
fullPath, nameFix, err := ctx.ser.getWebFilePath(name)
if err != nil {
ctx.jsAlert("file path wrong:" + err.Error())
return
}
if name == "" || nameFix == "" || strings.HasSuffix(nameFix, "/") {
ctx.jsAlert("wrong file name")
return
}
fullPathOrigin, _, err := ctx.ser.getWebFilePath(nameOrigin)
if fullPathOrigin == "" && err != nil {
ctx.jsAlert("origin file path wrong:" + err.Error())
return
}
dirName := filepath.Dir(fullPath)
if !fs.FileExists(dirName) {
os.MkdirAll(dirName, os.ModePerm)
}
errWrite := fs.FilePutContents(fullPath, []byte(content))
if errWrite != nil {
ctx.jsAlert("save failed:" + errWrite.Error())
return
}
if fullPath != fullPathOrigin {
os.Remove(fullPathOrigin)
}
info, _ := newWebFileInfo(ctx.ser.conf.FileDir, name)
ctx.jsAlertJump("save suc", info.link())
}
================================================
FILE: serve/wsClient.go
================================================
package serve
import (
"net/http"
"path/filepath"
"strings"
"github.com/googollee/go-socket.io"
)
type wsClient struct {
ns *socketio.NameSpace
user string
filterUser []string
filterIP []string
filterHideExt []string
filterURL []string
filterURLHide []string
LoginUser *User
}
var extTypes = map[string][]string{
"js": {"js"},
"css": {"css"},
"image": {"jpg", "jpeg", "png", "gif", "bmp", "tiff", "jpe", "tif", "webp", "ico", "webp"},
}
func (client *wsClient) checkFilter(req *http.Request, reqCtx *requestCtx) bool {
if len(client.filterUser) > 0 {
userInList := false
for _, name := range client.filterUser {
if name == "any" && client.LoginUser != nil && client.LoginUser.IsAdmin {
userInList = true
break
}
if name != "" && name == reqCtx.User.Name {
userInList = true
break
}
}
if !userInList {
return false
}
}
if len(client.filterIP) > 0 {
addrInfo := strings.Split(reqCtx.RemoteAddr, ":")
ipInList := false
for _, ip := range client.filterIP {
if ip == "*" {
ipInList = true
break
}
if ip != "" && addrInfo[0] == ip {
ipInList = true
break
}
}
if !ipInList {
return false
}
}
if len(client.filterURL) > 0 {
url := req.URL.String()
hasKw := false
for _, subURL := range client.filterURL {
if strings.Contains(url, subURL) {
hasKw = true
break
}
}
if !hasKw {
return false
}
}
if len(client.filterHideExt) > 0 {
ext := strings.ToLower(strings.Trim(filepath.Ext(req.URL.Path), "."))
for _, hideType := range client.filterHideExt {
for _, hideExt := range extTypes[hideType] {
if ext == hideExt {
return false
}
}
}
}
if len(client.filterURLHide) > 0 {
_url := req.URL.String()
for _, hideKw := range client.filterURLHide {
if hideKw != "" && strings.Contains(_url, hideKw) {
return false
}
}
}
return true
}
================================================
FILE: serve/wsServer.go
================================================
package serve
import (
"fmt"
"log"
"net/http"
"net/url"
"sync"
"github.com/googollee/go-socket.io"
"github.com/hidu/goutils/time_util"
)
type wsServer struct {
clients map[string]*wsClient
server *socketio.SocketIOServer
mu sync.RWMutex
proxySer *ProxyServe
}
func (ser *ProxyServe) wsInit() {
ser.wsSer = newWsServer(ser)
}
func newWsServer(ser *ProxyServe) *wsServer {
wsSer := &wsServer{
clients: make(map[string]*wsClient),
proxySer: ser,
}
var err error
wsSer.server = socketio.NewSocketIOServer(&socketio.Config{})
if err != nil {
log.Fatal(err)
}
wsSer.init()
return wsSer
}
func (wsSer *wsServer) init() {
wsSer.server.On("connect", func(ns *socketio.NameSpace) {
wsSer.mu.Lock()
defer wsSer.mu.Unlock()
wsSer.clients[ns.Id()] = &wsClient{ns: ns, user: "guest"}
log.Println("ws connected", ns.Id(), "ws_client_num:", len(wsSer.clients))
})
wsSer.server.On("disconnect", func(ns *socketio.NameSpace) {
wsSer.remove(ns.Id())
log.Println("ws disconnect", ns.Id(), "ws_client_num:", len(wsSer.clients))
})
wsSer.server.On("error", func(ns *socketio.NameSpace, err error) {
log.Println("ws error:", err)
})
wsSer.server.On("get_response", wsSer.getResponse)
wsSer.server.On("client_filter", wsSer.saveFilter)
time_util.SetInterval(func() {
wsSer.broadcast("hello", "hello", false)
}, 120)
}
func (wsSer *wsServer) remove(id string) {
wsSer.mu.Lock()
defer wsSer.mu.Unlock()
if _, has := wsSer.clients[id]; has {
delete(wsSer.clients, id)
}
}
func (wsSer *wsServer) broadProxyClientNum() {
wsSer.broadcast("user_num", len(wsSer.proxySer.ProxyClients), false)
}
/*
* https://github.com/googollee/go-socket.io
*/
func (wsSer *wsServer) getResponse(ns *socketio.NameSpace, docidStr string) {
docid, uintParseErr := parseDocID(docidStr)
if uintParseErr != nil {
log.Println("parse str2int failed", docidStr, uintParseErr)
return
}
log.Println("receive docid", docid)
req, _ := wsSer.proxySer.getRequestByDocid(docid)
res, _ := wsSer.proxySer.getResponseByDocid(docid)
if wsSer.proxySer.Debug {
fmt.Println("req:\n", req, "\n==========\n")
fmt.Println("res:\n", res, "\n==========\n")
}
// delete(req,"header")
data := make(map[string]any)
data["req"] = nil
data["res"] = nil
if req != nil {
data["req"] = req.Data
}
if res != nil {
data["res"] = res.Data
}
wsSer.send(ns, "res", data, true)
}
func (wsSer *wsServer) saveFilter(ns *socketio.NameSpace, formData string) {
m, err := url.ParseQuery(formData)
if err != nil {
log.Println("parse filter data err", err)
return
}
wsSer.mu.Lock()
defer wsSer.mu.Unlock()
if nsClient, has := wsSer.clients[ns.Id()]; has {
nsClient.filterIP = parseURLInputAsSlice(m.Get("client_ip"))
nsClient.filterHideExt = m["hide"]
nsClient.filterURL = parseURLInputAsSlice(m.Get("url_match"))
nsClient.filterURLHide = parseURLInputAsSlice(m.Get("hide_url"))
nsClient.filterUser = parseURLInputAsSlice(m.Get("user"))
loginUser, isLogin := wsSer.proxySer.web_checkLogin(ns.Session.Request)
if isLogin {
nsClient.LoginUser = loginUser
}
} else {
log.Println("ws_saveFilter failed,ws not exists")
}
}
var nnnn int
func (wsSer *wsServer) send(ns *socketio.NameSpace, msgName string, data any, encode bool) {
wsSer.mu.Lock()
defer func(ns *socketio.NameSpace) {
wsSer.mu.Unlock()
if e := recover(); e != nil {
log.Println("ws_send failed", e, ns.Session.Request.RemoteAddr, "msgName:", msgName, "client:", len(wsSer.clients))
wsSer.remove(ns.Id())
}
}(ns)
var err error
encode = false
if encode {
err = ns.Emit(msgName, dataEncode(data))
} else {
err = ns.Emit(msgName, data)
}
if err != nil {
log.Println("emit_failed", msgName, err)
}
}
func (wsSer *wsServer) broadcastReq(req *http.Request, reqCtx *requestCtx, data any) bool {
wsSer.mu.RLock()
defer wsSer.mu.RUnlock()
hasSend := false
for _, client := range wsSer.clients {
if wsSer.proxySer.conf.SessionView == sessionViewIPOrUser && len(client.filterIP) == 0 && len(client.filterUser) == 0 {
continue
}
if reqCtx.User.Name != "" && len(client.filterUser) < 1 {
continue
}
if client.checkFilter(req, reqCtx) {
go wsSer.send(client.ns, "req", data, true)
hasSend = true
}
}
return hasSend
}
func (wsSer *wsServer) broadcast(name string, data any, encode bool) {
wsSer.mu.RLock()
defer wsSer.mu.RUnlock()
for _, client := range wsSer.clients {
go wsSer.send(client.ns, name, data, encode)
}
}