Browse Source

more e2e test cases (#2450)

fatedier 3 years ago
parent
commit
900454e58b
45 changed files with 1734 additions and 1951 deletions
  1. 4 4
      Makefile
  2. 11 8
      go.mod
  3. 79 21
      go.sum
  4. 6 1
      hack/run-e2e.sh
  5. 9 8
      pkg/config/server.go
  6. 88 7
      test/e2e/basic/basic.go
  7. 65 0
      test/e2e/basic/chaos.go
  8. 78 0
      test/e2e/basic/client.go
  9. 1 1
      test/e2e/basic/client_server.go
  10. 113 0
      test/e2e/basic/cmd.go
  11. 83 0
      test/e2e/basic/config.go
  12. 238 0
      test/e2e/basic/group.go
  13. 326 0
      test/e2e/basic/http.go
  14. 52 5
      test/e2e/basic/server.go
  15. 4 5
      test/e2e/examples.go
  16. 20 0
      test/e2e/framework/expect.go
  17. 38 7
      test/e2e/framework/framework.go
  18. 24 9
      test/e2e/framework/mockservers.go
  19. 35 6
      test/e2e/framework/process.go
  20. 50 19
      test/e2e/framework/request.go
  21. 1 2
      test/e2e/framework/util.go
  22. 86 0
      test/e2e/mock/server/httpserver/server.go
  23. 8 0
      test/e2e/mock/server/interface.go
  24. 7 7
      test/e2e/mock/server/streamserver/server.go
  25. 2 1
      test/e2e/pkg/port/port.go
  26. 12 0
      test/e2e/pkg/process/process.go
  27. 116 31
      test/e2e/pkg/request/request.go
  28. 133 0
      test/e2e/pkg/sdk/client/client.go
  29. 10 0
      test/e2e/pkg/utils/utils.go
  30. 35 5
      test/e2e/plugin/client_plugins.go
  31. 0 204
      tests/ci/auto_test_frpc.ini
  32. 0 34
      tests/ci/auto_test_frpc_visitor.ini
  33. 0 9
      tests/ci/auto_test_frps.ini
  34. 0 85
      tests/ci/cmd_test.go
  35. 0 309
      tests/ci/health/health_test.go
  36. 0 250
      tests/ci/normal_test.go
  37. 0 115
      tests/ci/reconnect_test.go
  38. 0 150
      tests/ci/reload_test.go
  39. 0 72
      tests/ci/template_test.go
  40. 0 13
      tests/config/config.go
  41. 0 76
      tests/consts/consts.go
  42. 0 120
      tests/mock/echo_server.go
  43. 0 110
      tests/mock/http_server.go
  44. 0 47
      tests/util/process.go
  45. 0 210
      tests/util/util.go

+ 4 - 4
Makefile

@@ -34,13 +34,13 @@ gotest:
 	go test -v --cover ./server/...
 	go test -v --cover ./pkg/...
 
-ci:
-	go test -count=1 -p=1 -v ./tests/...
-
 e2e:
 	./hack/run-e2e.sh
 
-alltest: gotest ci e2e
+e2e-trace:
+	DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh
+
+alltest: gotest e2e
 	
 clean:
 	rm -f ./bin/frpc

+ 11 - 8
go.mod

@@ -8,16 +8,18 @@ require (
 	github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
 	github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185
 	github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
-	github.com/google/uuid v1.1.1
-	github.com/gorilla/mux v1.7.3
-	github.com/gorilla/websocket v1.4.0
+	github.com/go-playground/validator/v10 v10.6.1
+	github.com/google/uuid v1.2.0
+	github.com/gorilla/mux v1.8.0
+	github.com/gorilla/websocket v1.4.2
 	github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/klauspost/cpuid v1.2.0 // indirect
 	github.com/klauspost/reedsolomon v1.9.1 // indirect
+	github.com/leodido/go-urn v1.2.1 // indirect
 	github.com/mattn/go-runewidth v0.0.4 // indirect
-	github.com/onsi/ginkgo v1.12.3
-	github.com/onsi/gomega v1.10.1
+	github.com/onsi/ginkgo v1.16.4
+	github.com/onsi/gomega v1.13.0
 	github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc
 	github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
 	github.com/prometheus/client_golang v1.4.1
@@ -25,15 +27,16 @@ require (
 	github.com/rodaine/table v1.0.0
 	github.com/smartystreets/goconvey v1.6.4 // indirect
 	github.com/spf13/cobra v0.0.3
-	github.com/stretchr/testify v1.4.0
+	github.com/stretchr/testify v1.6.1
 	github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
 	github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
 	github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
 	github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
-	golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7
+	golang.org/x/net v0.0.0-20210428140749-89ef3d95e781
 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
-	golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect
+	golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
 	golang.org/x/time v0.0.0-20191024005414-555d28b269f0
+	golang.org/x/tools v0.1.3 // indirect
 	gopkg.in/ini.v1 v1.62.0
 	gopkg.in/square/go-jose.v2 v2.4.1 // indirect
 	k8s.io/apimachinery v0.18.3

+ 79 - 21
go.sum

@@ -44,7 +44,17 @@ github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+
 github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
 github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
 github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
+github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/validator/v10 v10.6.1 h1:W6TRDXt4WcWp4c4nf/G+6BkGdhiIo0k417gfr+V6u4I=
+github.com/go-playground/validator/v10 v10.6.1/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -57,26 +67,30 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x
 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
 github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
+github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
 github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
-github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
-github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 h1:Y4V+SFe7d3iH+9pJCoeWIOS5/xBJIFsltS7E+KJSsJY=
 github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
@@ -102,6 +116,9 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
+github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
 github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
@@ -114,19 +131,22 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
 github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
-github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.12.3 h1:+RYp9QczoWz9zfUyLP/5SLXQVhfr6gZOoKGfQqHuLZQ=
-github.com/onsi/ginkgo v1.12.3/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
+github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
-github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
+github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
 github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8=
 github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -170,8 +190,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 h1:K+jtWCOuZgCra7eXZ/VWn2FbJmrA/D058mTXhh2rq+8=
 github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
 github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoMZLO+ce8KHtdHGhst4cs6rw3gmk=
@@ -180,10 +202,16 @@ github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt
 github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
 github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
 github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -191,21 +219,30 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -213,21 +250,37 @@ golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
-golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
@@ -235,8 +288,10 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
 google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
 google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -255,8 +310,11 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 k8s.io/apimachinery v0.18.3 h1:pOGcbVAhxADgUYnjS08EFXs9QMl8qaH5U4fr5LGUrSk=
 k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
 k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=

+ 6 - 1
hack/run-e2e.sh

@@ -12,4 +12,9 @@ debug=false
 if [ x${DEBUG} == x"true" ]; then
     debug=true
 fi
-ginkgo -nodes=4 ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=debug -debug=${debug}
+logLevel=debug
+if [ x${LOG_LEVEL} != x"" ]; then
+    logLevel=${LOG_LEVEL}
+fi
+
+ginkgo -nodes=5 -slowSpecThreshold=10 ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=${logLevel} -debug=${debug}

+ 9 - 8
pkg/config/server.go

@@ -22,6 +22,7 @@ import (
 	plugin "github.com/fatedier/frp/pkg/plugin/server"
 	"github.com/fatedier/frp/pkg/util/util"
 
+	"github.com/go-playground/validator/v10"
 	"gopkg.in/ini.v1"
 )
 
@@ -36,31 +37,31 @@ type ServerCommonConf struct {
 	BindAddr string `ini:"bind_addr" json:"bind_addr"`
 	// BindPort specifies the port that the server listens on. By default, this
 	// value is 7000.
-	BindPort int `ini:"bind_port" json:"bind_port"`
+	BindPort int `ini:"bind_port" json:"bind_port" validate:"gte=0,lte=65535"`
 	// BindUDPPort specifies the UDP port that the server listens on. If this
 	// value is 0, the server will not listen for UDP connections. By default,
 	// this value is 0
-	BindUDPPort int `ini:"bind_udp_port" json:"bind_udp_port"`
+	BindUDPPort int `ini:"bind_udp_port" json:"bind_udp_port" validate:"gte=0,lte=65535"`
 	// KCPBindPort specifies the KCP port that the server listens on. If this
 	// value is 0, the server will not listen for KCP connections. By default,
 	// this value is 0.
-	KCPBindPort int `ini:"kcp_bind_port" json:"kcp_bind_port"`
+	KCPBindPort int `ini:"kcp_bind_port" json:"kcp_bind_port" validate:"gte=0,lte=65535"`
 	// ProxyBindAddr specifies the address that the proxy binds to. This value
 	// may be the same as BindAddr.
 	ProxyBindAddr string `ini:"proxy_bind_addr" json:"proxy_bind_addr"`
 	// VhostHTTPPort specifies the port that the server listens for HTTP Vhost
 	// requests. If this value is 0, the server will not listen for HTTP
 	// requests. By default, this value is 0.
-	VhostHTTPPort int `ini:"vhost_http_port" json:"vhost_http_port"`
+	VhostHTTPPort int `ini:"vhost_http_port" json:"vhost_http_port" validate:"gte=0,lte=65535"`
 	// VhostHTTPSPort specifies the port that the server listens for HTTPS
 	// Vhost requests. If this value is 0, the server will not listen for HTTPS
 	// requests. By default, this value is 0.
-	VhostHTTPSPort int `ini:"vhost_https_port" json:"vhost_https_port"`
+	VhostHTTPSPort int `ini:"vhost_https_port" json:"vhost_https_port" validate:"gte=0,lte=65535"`
 	// TCPMuxHTTPConnectPort specifies the port that the server listens for TCP
 	// HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
 	// requests on one single port. If it's not - it will listen on this value for
 	// HTTP CONNECT requests. By default, this value is 0.
-	TCPMuxHTTPConnectPort int `ini:"tcpmux_httpconnect_port" json:"tcpmux_httpconnect_port"`
+	TCPMuxHTTPConnectPort int `ini:"tcpmux_httpconnect_port" json:"tcpmux_httpconnect_port" validate:"gte=0,lte=65535"`
 	// VhostHTTPTimeout specifies the response header timeout for the Vhost
 	// HTTP server, in seconds. By default, this value is 60.
 	VhostHTTPTimeout int64 `ini:"vhost_http_timeout" json:"vhost_http_timeout"`
@@ -70,7 +71,7 @@ type ServerCommonConf struct {
 	// DashboardPort specifies the port that the dashboard listens on. If this
 	// value is 0, the dashboard will not be started. By default, this value is
 	// 0.
-	DashboardPort int `ini:"dashboard_port" json:"dashboard_port"`
+	DashboardPort int `ini:"dashboard_port" json:"dashboard_port" validate:"gte=0,lte=65535"`
 	// DashboardUser specifies the username that the dashboard will use for
 	// login.
 	DashboardUser string `ini:"dashboard_user" json:"dashboard_user"`
@@ -281,7 +282,7 @@ func (cfg *ServerCommonConf) Complete() {
 }
 
 func (cfg *ServerCommonConf) Validate() error {
-	return nil
+	return validator.New().Struct(cfg)
 }
 
 func loadHTTPPluginOpt(section *ini.Section) (*plugin.HTTPPluginOptions, error) {

+ 88 - 7
test/e2e/basic/basic.go

@@ -6,7 +6,7 @@ import (
 
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework/consts"
-	"github.com/fatedier/frp/test/e2e/mock/server"
+	"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
 	"github.com/fatedier/frp/test/e2e/pkg/port"
 	"github.com/fatedier/frp/test/e2e/pkg/request"
 
@@ -81,7 +81,7 @@ var _ = Describe("[Feature: Basic]", func() {
 
 				for _, test := range tests {
 					framework.NewRequestExpect(f).
-						RequestModify(framework.SetRequestProtocol(protocol)).
+						Protocol(protocol).
 						PortName(test.portName).
 						Explain(test.proxyName).
 						Ensure()
@@ -90,6 +90,88 @@ var _ = Describe("[Feature: Basic]", func() {
 		}
 	})
 
+	Describe("HTTP", func() {
+		It("proxy to HTTP server", func() {
+			serverConf := consts.DefaultServerConfig
+			vhostHTTPPort := f.AllocPort()
+			serverConf += fmt.Sprintf(`
+			vhost_http_port = %d
+			`, vhostHTTPPort)
+
+			clientConf := consts.DefaultClientConfig
+
+			getProxyConf := func(proxyName string, customDomains string, extra string) string {
+				return fmt.Sprintf(`
+				[%s]
+				type = http
+				local_port = {{ .%s }}
+				custom_domains = %s
+				`+extra, proxyName, framework.HTTPSimpleServerPort, customDomains)
+			}
+
+			tests := []struct {
+				proxyName     string
+				customDomains string
+				extraConfig   string
+			}{
+				{
+					proxyName: "normal",
+				},
+				{
+					proxyName:   "with-encryption",
+					extraConfig: "use_encryption = true",
+				},
+				{
+					proxyName:   "with-compression",
+					extraConfig: "use_compression = true",
+				},
+				{
+					proxyName: "with-encryption-and-compression",
+					extraConfig: `
+						use_encryption = true
+						use_compression = true
+						`,
+				},
+				{
+					proxyName:     "multiple-custom-domains",
+					customDomains: "a.example.com, b.example.com",
+				},
+			}
+
+			// build all client config
+			for i, test := range tests {
+				if tests[i].customDomains == "" {
+					tests[i].customDomains = test.proxyName + ".example.com"
+				}
+				clientConf += getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n"
+			}
+			// run frps and frpc
+			f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+			for _, test := range tests {
+				for _, domain := range strings.Split(test.customDomains, ",") {
+					domain = strings.TrimSpace(domain)
+					framework.NewRequestExpect(f).
+						Explain(test.proxyName + "-" + domain).
+						Port(vhostHTTPPort).
+						RequestModify(func(r *request.Request) {
+							r.HTTP().HTTPHost(domain)
+						}).
+						Ensure()
+				}
+			}
+
+			// not exist host
+			framework.NewRequestExpect(f).
+				Explain("not exist host").
+				Port(vhostHTTPPort).
+				RequestModify(func(r *request.Request) {
+					r.HTTP().HTTPHost("not-exist.example.com")
+				}).
+				Ensure(framework.ExpectResponseCode(404))
+		})
+	})
+
 	Describe("STCP && SUDP", func() {
 		types := []string{"stcp", "sudp"}
 		for _, t := range types {
@@ -186,12 +268,11 @@ var _ = Describe("[Feature: Basic]", func() {
 
 				for _, test := range tests {
 					framework.NewRequestExpect(f).
-						RequestModify(framework.SetRequestProtocol(protocol)).
+						Protocol(protocol).
 						PortName(test.bindPortName).
 						Explain(test.proxyName).
 						ExpectError(test.expectError).
 						Ensure()
-
 				}
 			})
 		}
@@ -245,7 +326,7 @@ var _ = Describe("[Feature: Basic]", func() {
 			for _, test := range tests {
 				clientConf += getProxyConf(test.proxyName, test.extraConfig) + "\n"
 
-				localServer := server.New(server.TCP, server.WithBindPort(f.AllocPort()), server.WithRespContent([]byte(test.proxyName)))
+				localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(f.AllocPort()), streamserver.WithRespContent([]byte(test.proxyName)))
 				f.RunServer(port.GenName(test.proxyName), localServer)
 			}
 
@@ -262,13 +343,13 @@ var _ = Describe("[Feature: Basic]", func() {
 			proxyURL := fmt.Sprintf("http://127.0.0.1:%d", f.PortByName(tcpmuxHTTPConnectPortName))
 			// Request with incorrect connect hostname
 			framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
-				r.Proxy(proxyURL, "invalid")
+				r.Addr("invalid").Proxy(proxyURL)
 			}).ExpectError(true).Explain("request without HTTP connect expect error").Ensure()
 
 			// Request with correct connect hostname
 			for _, test := range tests {
 				framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
-					r.Proxy(proxyURL, test.proxyName)
+					r.Addr(test.proxyName).Proxy(proxyURL)
 				}).ExpectResp([]byte(test.proxyName)).Explain(test.proxyName).Ensure()
 			}
 		})

+ 65 - 0
test/e2e/basic/chaos.go

@@ -0,0 +1,65 @@
+package basic
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/fatedier/frp/test/e2e/framework"
+
+	. "github.com/onsi/ginkgo"
+)
+
+var _ = Describe("[Feature: Chaos]", func() {
+	f := framework.NewDefaultFramework()
+
+	It("reconnect after frps restart", func() {
+		serverPort := f.AllocPort()
+		serverConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
+		[common]
+		bind_addr = 0.0.0.0
+		bind_port = %d
+		`, serverPort))
+
+		remotePort := f.AllocPort()
+		clientConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
+		[common]
+		server_port = %d
+		log_level = trace
+
+		[tcp]
+		type = tcp
+		local_port = %d
+		remote_port = %d
+		`, serverPort, f.PortByName(framework.TCPEchoServerPort), remotePort))
+
+		// 1. start frps and frpc, expect request success
+		ps, _, err := f.RunFrps("-c", serverConfigPath)
+		framework.ExpectNoError(err)
+
+		pc, _, err := f.RunFrpc("-c", clientConfigPath)
+		framework.ExpectNoError(err)
+		framework.NewRequestExpect(f).Port(remotePort).Ensure()
+
+		// 2. stop frps, expect request failed
+		ps.Stop()
+		time.Sleep(200 * time.Millisecond)
+		framework.NewRequestExpect(f).Port(remotePort).ExpectError(true).Ensure()
+
+		// 3. restart frps, expect request success
+		_, _, err = f.RunFrps("-c", serverConfigPath)
+		framework.ExpectNoError(err)
+		time.Sleep(2 * time.Second)
+		framework.NewRequestExpect(f).Port(remotePort).Ensure()
+
+		// 4. stop frpc, expect request failed
+		pc.Stop()
+		time.Sleep(200 * time.Millisecond)
+		framework.NewRequestExpect(f).Port(remotePort).ExpectError(true).Ensure()
+
+		// 5. restart frpc, expect request success
+		_, _, err = f.RunFrpc("-c", clientConfigPath)
+		framework.ExpectNoError(err)
+		time.Sleep(time.Second)
+		framework.NewRequestExpect(f).Port(remotePort).Ensure()
+	})
+})

+ 78 - 0
test/e2e/basic/client.go

@@ -0,0 +1,78 @@
+package basic
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/fatedier/frp/test/e2e/framework"
+	"github.com/fatedier/frp/test/e2e/framework/consts"
+	clientsdk "github.com/fatedier/frp/test/e2e/pkg/sdk/client"
+
+	. "github.com/onsi/ginkgo"
+)
+
+var _ = Describe("[Feature: ClientManage]", func() {
+	f := framework.NewDefaultFramework()
+
+	It("Update && Reload API", func() {
+		serverConf := consts.DefaultServerConfig
+
+		adminPort := f.AllocPort()
+
+		p1Port := f.AllocPort()
+		p2Port := f.AllocPort()
+		p3Port := f.AllocPort()
+
+		clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
+		admin_port = %d
+
+		[p1]
+		type = tcp
+		local_port = {{ .%s }}
+		remote_port = %d
+
+		[p2]
+		type = tcp
+		local_port = {{ .%s }}
+		remote_port = %d
+
+		[p3]
+		type = tcp
+		local_port = {{ .%s }}
+		remote_port = %d
+		`, adminPort,
+			framework.TCPEchoServerPort, p1Port,
+			framework.TCPEchoServerPort, p2Port,
+			framework.TCPEchoServerPort, p3Port)
+
+		f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+		framework.NewRequestExpect(f).Port(p1Port).Ensure()
+		framework.NewRequestExpect(f).Port(p2Port).Ensure()
+		framework.NewRequestExpect(f).Port(p3Port).Ensure()
+
+		client := clientsdk.New("127.0.0.1", adminPort)
+		conf, err := client.GetConfig()
+		framework.ExpectNoError(err)
+
+		newP2Port := f.AllocPort()
+		// change p2 port and remove p3 proxy
+		newClientConf := strings.ReplaceAll(conf, strconv.Itoa(p2Port), strconv.Itoa(newP2Port))
+		p3Index := strings.Index(newClientConf, "[p3]")
+		newClientConf = newClientConf[:p3Index]
+
+		err = client.UpdateConfig(newClientConf)
+		framework.ExpectNoError(err)
+
+		err = client.Reload()
+		framework.ExpectNoError(err)
+		time.Sleep(time.Second)
+
+		framework.NewRequestExpect(f).Port(p1Port).Explain("p1 port").Ensure()
+		framework.NewRequestExpect(f).Port(p2Port).Explain("original p2 port").ExpectError(true).Ensure()
+		framework.NewRequestExpect(f).Port(newP2Port).Explain("new p2 port").Ensure()
+		framework.NewRequestExpect(f).Port(p3Port).Explain("p3 port").ExpectError(true).Ensure()
+	})
+})

+ 1 - 1
test/e2e/basic/client_server.go

@@ -49,7 +49,7 @@ func defineClientServerTest(desc string, f *framework.Framework, configures *gen
 		f.RunProcesses([]string{serverConf}, []string{clientConf})
 
 		framework.NewRequestExpect(f).PortName(tcpPortName).ExpectError(configures.expectError).Explain("tcp proxy").Ensure()
-		framework.NewRequestExpect(f).RequestModify(framework.SetRequestProtocol("udp")).
+		framework.NewRequestExpect(f).Protocol("udp").
 			PortName(udpPortName).ExpectError(configures.expectError).Explain("udp proxy").Ensure()
 	})
 }

+ 113 - 0
test/e2e/basic/cmd.go

@@ -0,0 +1,113 @@
+package basic
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/fatedier/frp/test/e2e/framework"
+	"github.com/fatedier/frp/test/e2e/pkg/request"
+
+	. "github.com/onsi/ginkgo"
+)
+
+const (
+	ConfigValidStr = "syntax is ok"
+)
+
+var _ = Describe("[Feature: Cmd]", func() {
+	f := framework.NewDefaultFramework()
+
+	Describe("Verify", func() {
+		It("frps valid", func() {
+			path := f.GenerateConfigFile(`
+			[common]
+			bind_addr = 0.0.0.0
+			bind_port = 7000
+			`)
+			_, output, err := f.RunFrps("verify", "-c", path)
+			framework.ExpectNoError(err)
+			framework.ExpectTrue(strings.Contains(output, ConfigValidStr), "output: %s", output)
+		})
+		It("frps invalid", func() {
+			path := f.GenerateConfigFile(`
+			[common]
+			bind_addr = 0.0.0.0
+			bind_port = 70000
+			`)
+			_, output, err := f.RunFrps("verify", "-c", path)
+			framework.ExpectNoError(err)
+			framework.ExpectTrue(!strings.Contains(output, ConfigValidStr), "output: %s", output)
+		})
+		It("frpc valid", func() {
+			path := f.GenerateConfigFile(`
+			[common]
+			server_addr = 0.0.0.0
+			server_port = 7000
+			`)
+			_, output, err := f.RunFrpc("verify", "-c", path)
+			framework.ExpectNoError(err)
+			framework.ExpectTrue(strings.Contains(output, ConfigValidStr), "output: %s", output)
+		})
+		It("frpc invalid", func() {
+			path := f.GenerateConfigFile(`
+			[common]
+			server_addr = 0.0.0.0
+			server_port = 7000
+			protocol = invalid
+			`)
+			_, output, err := f.RunFrpc("verify", "-c", path)
+			framework.ExpectNoError(err)
+			framework.ExpectTrue(!strings.Contains(output, ConfigValidStr), "output: %s", output)
+		})
+	})
+
+	Describe("Single proxy", func() {
+		It("TCP", func() {
+			serverPort := f.AllocPort()
+			_, _, err := f.RunFrps("-t", "123", "-p", strconv.Itoa(serverPort))
+			framework.ExpectNoError(err)
+
+			localPort := f.PortByName(framework.TCPEchoServerPort)
+			remotePort := f.AllocPort()
+			_, _, err = f.RunFrpc("tcp", "-s", fmt.Sprintf("127.0.0.1:%d", serverPort), "-t", "123", "-u", "test",
+				"-l", strconv.Itoa(localPort), "-r", strconv.Itoa(remotePort), "-n", "tcp_test")
+			framework.ExpectNoError(err)
+
+			framework.NewRequestExpect(f).Port(remotePort).Ensure()
+		})
+
+		It("UDP", func() {
+			serverPort := f.AllocPort()
+			_, _, err := f.RunFrps("-t", "123", "-p", strconv.Itoa(serverPort))
+			framework.ExpectNoError(err)
+
+			localPort := f.PortByName(framework.UDPEchoServerPort)
+			remotePort := f.AllocPort()
+			_, _, err = f.RunFrpc("udp", "-s", fmt.Sprintf("127.0.0.1:%d", serverPort), "-t", "123", "-u", "test",
+				"-l", strconv.Itoa(localPort), "-r", strconv.Itoa(remotePort), "-n", "udp_test")
+			framework.ExpectNoError(err)
+
+			framework.NewRequestExpect(f).Protocol("udp").
+				Port(remotePort).Ensure()
+		})
+
+		It("HTTP", func() {
+			serverPort := f.AllocPort()
+			vhostHTTPPort := f.AllocPort()
+			_, _, err := f.RunFrps("-t", "123", "-p", strconv.Itoa(serverPort), "--vhost_http_port", strconv.Itoa(vhostHTTPPort))
+			framework.ExpectNoError(err)
+
+			_, _, err = f.RunFrpc("http", "-s", "127.0.0.1:"+strconv.Itoa(serverPort), "-t", "123", "-u", "test",
+				"-n", "udp_test", "-l", strconv.Itoa(f.PortByName(framework.HTTPSimpleServerPort)),
+				"--custom_domain", "test.example.com")
+			framework.ExpectNoError(err)
+
+			framework.NewRequestExpect(f).Port(vhostHTTPPort).
+				RequestModify(func(r *request.Request) {
+					r.HTTP().HTTPHost("test.example.com")
+				}).
+				Ensure()
+		})
+	})
+})

+ 83 - 0
test/e2e/basic/config.go

@@ -0,0 +1,83 @@
+package basic
+
+import (
+	"fmt"
+
+	"github.com/fatedier/frp/test/e2e/framework"
+	"github.com/fatedier/frp/test/e2e/framework/consts"
+	"github.com/fatedier/frp/test/e2e/pkg/port"
+
+	. "github.com/onsi/ginkgo"
+)
+
+var _ = Describe("[Feature: Config]", func() {
+	f := framework.NewDefaultFramework()
+
+	Describe("Template", func() {
+		It("render by env", func() {
+			serverConf := consts.DefaultServerConfig
+			clientConf := consts.DefaultClientConfig
+
+			portName := port.GenName("TCP")
+			serverConf += fmt.Sprintf(`
+			token = {{ %s{{ .Envs.FRP_TOKEN }}%s }}
+			`, "`", "`")
+
+			clientConf += fmt.Sprintf(`
+			token = {{ %s{{ .Envs.FRP_TOKEN }}%s }}
+
+			[tcp]
+			type = tcp
+			local_port = {{ .%s }}
+			remote_port = {{ .%s }}
+			`, "`", "`", framework.TCPEchoServerPort, portName)
+
+			f.SetEnvs([]string{"FRP_TOKEN=123"})
+			f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+			framework.NewRequestExpect(f).PortName(portName).Ensure()
+		})
+	})
+
+	Describe("Includes", func() {
+		It("split tcp proxies into different files", func() {
+			serverPort := f.AllocPort()
+			serverConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
+			[common]
+			bind_addr = 0.0.0.0
+			bind_port = %d
+			`, serverPort))
+
+			remotePort := f.AllocPort()
+			proxyConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
+			[tcp]
+			type = tcp
+			local_port = %d
+			remote_port = %d
+			`, f.PortByName(framework.TCPEchoServerPort), remotePort))
+
+			remotePort2 := f.AllocPort()
+			proxyConfigPath2 := f.GenerateConfigFile(fmt.Sprintf(`
+			[tcp2]
+			type = tcp
+			local_port = %d
+			remote_port = %d
+			`, f.PortByName(framework.TCPEchoServerPort), remotePort2))
+
+			clientConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
+			[common]
+			server_port = %d
+			includes = %s,%s
+			`, serverPort, proxyConfigPath, proxyConfigPath2))
+
+			_, _, err := f.RunFrps("-c", serverConfigPath)
+			framework.ExpectNoError(err)
+
+			_, _, err = f.RunFrpc("-c", clientConfigPath)
+			framework.ExpectNoError(err)
+
+			framework.NewRequestExpect(f).Port(remotePort).Ensure()
+			framework.NewRequestExpect(f).Port(remotePort2).Ensure()
+		})
+	})
+})

+ 238 - 0
test/e2e/basic/group.go

@@ -0,0 +1,238 @@
+package basic
+
+import (
+	"fmt"
+	"strconv"
+	"sync"
+	"time"
+
+	"github.com/fatedier/frp/test/e2e/framework"
+	"github.com/fatedier/frp/test/e2e/framework/consts"
+	"github.com/fatedier/frp/test/e2e/mock/server/httpserver"
+	"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
+	"github.com/fatedier/frp/test/e2e/pkg/request"
+
+	. "github.com/onsi/ginkgo"
+)
+
+var _ = Describe("[Feature: Group]", func() {
+	f := framework.NewDefaultFramework()
+
+	newHTTPServer := func(port int, respContent string) *httpserver.Server {
+		return httpserver.New(
+			httpserver.WithBindPort(port),
+			httpserver.WithHandler(framework.SpecifiedHTTPBodyHandler([]byte(respContent))),
+		)
+	}
+
+	validateFooBarResponse := func(resp *request.Response) bool {
+		if string(resp.Content) == "foo" || string(resp.Content) == "bar" {
+			return true
+		}
+		return false
+	}
+
+	doFooBarHTTPRequest := func(vhostPort int, host string) []string {
+		results := []string{}
+		var wait sync.WaitGroup
+		var mu sync.Mutex
+		expectFn := func() {
+			framework.NewRequestExpect(f).Port(vhostPort).
+				RequestModify(func(r *request.Request) {
+					r.HTTP().HTTPHost(host)
+				}).
+				Ensure(validateFooBarResponse, func(resp *request.Response) bool {
+					mu.Lock()
+					defer mu.Unlock()
+					results = append(results, string(resp.Content))
+					return true
+				})
+		}
+		for i := 0; i < 10; i++ {
+			wait.Add(1)
+			go func() {
+				defer wait.Done()
+				expectFn()
+			}()
+		}
+
+		wait.Wait()
+		return results
+	}
+
+	Describe("Load Balancing", func() {
+		It("TCP", func() {
+			serverConf := consts.DefaultServerConfig
+			clientConf := consts.DefaultClientConfig
+
+			fooPort := f.AllocPort()
+			fooServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(fooPort), streamserver.WithRespContent([]byte("foo")))
+			f.RunServer("", fooServer)
+
+			barPort := f.AllocPort()
+			barServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(barPort), streamserver.WithRespContent([]byte("bar")))
+			f.RunServer("", barServer)
+
+			remotePort := f.AllocPort()
+			clientConf += fmt.Sprintf(`
+			[foo]
+			type = tcp
+			local_port = %d
+			remote_port = %d
+			group = test
+			group_key = 123
+
+			[bar]
+			type = tcp
+			local_port = %d
+			remote_port = %d
+			group = test
+			group_key = 123
+			`, fooPort, remotePort, barPort, remotePort)
+
+			f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+			fooCount := 0
+			barCount := 0
+			for i := 0; i < 10; i++ {
+				framework.NewRequestExpect(f).Explain("times " + strconv.Itoa(i)).Port(remotePort).Ensure(func(resp *request.Response) bool {
+					switch string(resp.Content) {
+					case "foo":
+						fooCount++
+					case "bar":
+						barCount++
+					default:
+						return false
+					}
+					return true
+				})
+			}
+
+			framework.ExpectTrue(fooCount > 1 && barCount > 1, "fooCount: %d, barCount: %d", fooCount, barCount)
+		})
+	})
+
+	Describe("Health Check", func() {
+		It("TCP", func() {
+			serverConf := consts.DefaultServerConfig
+			clientConf := consts.DefaultClientConfig
+
+			fooPort := f.AllocPort()
+			fooServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(fooPort), streamserver.WithRespContent([]byte("foo")))
+			f.RunServer("", fooServer)
+
+			barPort := f.AllocPort()
+			barServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(barPort), streamserver.WithRespContent([]byte("bar")))
+			f.RunServer("", barServer)
+
+			remotePort := f.AllocPort()
+			clientConf += fmt.Sprintf(`
+			[foo]
+			type = tcp
+			local_port = %d
+			remote_port = %d
+			group = test
+			group_key = 123
+			health_check_type = tcp
+			health_check_interval_s = 1
+
+			[bar]
+			type = tcp
+			local_port = %d
+			remote_port = %d
+			group = test
+			group_key = 123
+			health_check_type = tcp
+			health_check_interval_s = 1
+			`, fooPort, remotePort, barPort, remotePort)
+
+			f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+			// check foo and bar is ok
+			results := []string{}
+			for i := 0; i < 10; i++ {
+				framework.NewRequestExpect(f).Port(remotePort).Ensure(validateFooBarResponse, func(resp *request.Response) bool {
+					results = append(results, string(resp.Content))
+					return true
+				})
+			}
+			framework.ExpectContainElements(results, []string{"foo", "bar"})
+
+			// close bar server, check foo is ok
+			barServer.Close()
+			time.Sleep(2 * time.Second)
+			for i := 0; i < 10; i++ {
+				framework.NewRequestExpect(f).Port(remotePort).ExpectResp([]byte("foo")).Ensure()
+			}
+
+			// resume bar server, check foo and bar is ok
+			f.RunServer("", barServer)
+			time.Sleep(2 * time.Second)
+			results = []string{}
+			for i := 0; i < 10; i++ {
+				framework.NewRequestExpect(f).Port(remotePort).Ensure(validateFooBarResponse, func(resp *request.Response) bool {
+					results = append(results, string(resp.Content))
+					return true
+				})
+			}
+			framework.ExpectContainElements(results, []string{"foo", "bar"})
+		})
+
+		It("HTTP", func() {
+			vhostPort := f.AllocPort()
+			serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
+			vhost_http_port = %d
+			`, vhostPort)
+			clientConf := consts.DefaultClientConfig
+
+			fooPort := f.AllocPort()
+			fooServer := newHTTPServer(fooPort, "foo")
+			f.RunServer("", fooServer)
+
+			barPort := f.AllocPort()
+			barServer := newHTTPServer(barPort, "bar")
+			f.RunServer("", barServer)
+
+			clientConf += fmt.Sprintf(`
+			[foo]
+			type = http
+			local_port = %d
+			custom_domains = example.com
+			group = test
+			group_key = 123
+			health_check_type = http
+			health_check_interval_s = 1
+			health_check_url = /healthz
+
+			[bar]
+			type = http
+			local_port = %d
+			custom_domains = example.com
+			group = test
+			group_key = 123
+			health_check_type = http
+			health_check_interval_s = 1
+			health_check_url = /healthz
+			`, fooPort, barPort)
+
+			f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+			// check foo and bar is ok
+			results := doFooBarHTTPRequest(vhostPort, "example.com")
+			framework.ExpectContainElements(results, []string{"foo", "bar"})
+
+			// close bar server, check foo is ok
+			barServer.Close()
+			time.Sleep(2 * time.Second)
+			results = doFooBarHTTPRequest(vhostPort, "example.com")
+			framework.ExpectContainElements(results, []string{"foo"})
+			framework.ExpectNotContainElements(results, []string{"bar"})
+
+			// resume bar server, check foo and bar is ok
+			f.RunServer("", barServer)
+			time.Sleep(2 * time.Second)
+			results = doFooBarHTTPRequest(vhostPort, "example.com")
+			framework.ExpectContainElements(results, []string{"foo", "bar"})
+		})
+	})
+})

+ 326 - 0
test/e2e/basic/http.go

@@ -0,0 +1,326 @@
+package basic
+
+import (
+	"fmt"
+	"net/http"
+	"net/url"
+	"strconv"
+
+	"github.com/fatedier/frp/test/e2e/framework"
+	"github.com/fatedier/frp/test/e2e/framework/consts"
+	"github.com/fatedier/frp/test/e2e/mock/server/httpserver"
+	"github.com/fatedier/frp/test/e2e/pkg/request"
+	"github.com/fatedier/frp/test/e2e/pkg/utils"
+
+	"github.com/gorilla/websocket"
+	. "github.com/onsi/ginkgo"
+)
+
+var _ = Describe("[Feature: HTTP]", func() {
+	f := framework.NewDefaultFramework()
+
+	getDefaultServerConf := func(vhostHTTPPort int) string {
+		conf := consts.DefaultServerConfig + `
+		vhost_http_port = %d
+		`
+		return fmt.Sprintf(conf, vhostHTTPPort)
+	}
+	newHTTPServer := func(port int, respContent string) *httpserver.Server {
+		return httpserver.New(
+			httpserver.WithBindPort(port),
+			httpserver.WithHandler(framework.SpecifiedHTTPBodyHandler([]byte(respContent))),
+		)
+	}
+
+	It("HTTP route by locations", func() {
+		vhostHTTPPort := f.AllocPort()
+		serverConf := getDefaultServerConf(vhostHTTPPort)
+
+		fooPort := f.AllocPort()
+		f.RunServer("", newHTTPServer(fooPort, "foo"))
+
+		barPort := f.AllocPort()
+		f.RunServer("", newHTTPServer(barPort, "bar"))
+
+		clientConf := consts.DefaultClientConfig
+		clientConf += fmt.Sprintf(`
+			[foo]
+			type = http
+			local_port = %d
+			custom_domains = normal.example.com
+			locations = /,/foo
+
+			[bar]
+			type = http
+			local_port = %d
+			custom_domains = normal.example.com
+			locations = /bar
+			`, fooPort, barPort)
+
+		f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+		// foo path
+		framework.NewRequestExpect(f).Explain("foo path").Port(vhostHTTPPort).
+			RequestModify(func(r *request.Request) {
+				r.HTTP().HTTPHost("normal.example.com").HTTPPath("/foo")
+			}).
+			ExpectResp([]byte("foo")).
+			Ensure()
+
+		// bar path
+		framework.NewRequestExpect(f).Explain("bar path").Port(vhostHTTPPort).
+			RequestModify(func(r *request.Request) {
+				r.HTTP().HTTPHost("normal.example.com").HTTPPath("/bar")
+			}).
+			ExpectResp([]byte("bar")).
+			Ensure()
+
+		// other path
+		framework.NewRequestExpect(f).Explain("other path").Port(vhostHTTPPort).
+			RequestModify(func(r *request.Request) {
+				r.HTTP().HTTPHost("normal.example.com").HTTPPath("/other")
+			}).
+			ExpectResp([]byte("foo")).
+			Ensure()
+	})
+
+	It("HTTP Basic Auth", func() {
+		vhostHTTPPort := f.AllocPort()
+		serverConf := getDefaultServerConf(vhostHTTPPort)
+
+		clientConf := consts.DefaultClientConfig
+		clientConf += fmt.Sprintf(`
+			[test]
+			type = http
+			local_port = {{ .%s }}
+			custom_domains = normal.example.com
+			http_user = test
+			http_pwd = test
+			`, framework.HTTPSimpleServerPort)
+
+		f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+		// not set auth header
+		framework.NewRequestExpect(f).Port(vhostHTTPPort).
+			RequestModify(func(r *request.Request) {
+				r.HTTP().HTTPHost("normal.example.com")
+			}).
+			Ensure(framework.ExpectResponseCode(401))
+
+		// set incorrect auth header
+		framework.NewRequestExpect(f).Port(vhostHTTPPort).
+			RequestModify(func(r *request.Request) {
+				r.HTTP().HTTPHost("normal.example.com").HTTPHeaders(map[string]string{
+					"Authorization": utils.BasicAuth("test", "invalid"),
+				})
+			}).
+			Ensure(framework.ExpectResponseCode(401))
+
+		// set correct auth header
+		framework.NewRequestExpect(f).Port(vhostHTTPPort).
+			RequestModify(func(r *request.Request) {
+				r.HTTP().HTTPHost("normal.example.com").HTTPHeaders(map[string]string{
+					"Authorization": utils.BasicAuth("test", "test"),
+				})
+			}).
+			Ensure()
+	})
+
+	It("Wildcard domain", func() {
+		vhostHTTPPort := f.AllocPort()
+		serverConf := getDefaultServerConf(vhostHTTPPort)
+
+		clientConf := consts.DefaultClientConfig
+		clientConf += fmt.Sprintf(`
+			[test]
+			type = http
+			local_port = {{ .%s }}
+			custom_domains = *.example.com
+			`, framework.HTTPSimpleServerPort)
+
+		f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+		// not match host
+		framework.NewRequestExpect(f).Port(vhostHTTPPort).
+			RequestModify(func(r *request.Request) {
+				r.HTTP().HTTPHost("not-match.test.com")
+			}).
+			Ensure(framework.ExpectResponseCode(404))
+
+		// test.example.com match *.example.com
+		framework.NewRequestExpect(f).Port(vhostHTTPPort).
+			RequestModify(func(r *request.Request) {
+				r.HTTP().HTTPHost("test.example.com")
+			}).
+			Ensure()
+
+		// sub.test.example.com match *.example.com
+		framework.NewRequestExpect(f).Port(vhostHTTPPort).
+			RequestModify(func(r *request.Request) {
+				r.HTTP().HTTPHost("sub.test.example.com")
+			}).
+			Ensure()
+	})
+
+	It("Subdomain", func() {
+		vhostHTTPPort := f.AllocPort()
+		serverConf := getDefaultServerConf(vhostHTTPPort)
+		serverConf += `
+		subdomain_host = example.com
+		`
+
+		fooPort := f.AllocPort()
+		f.RunServer("", newHTTPServer(fooPort, "foo"))
+
+		barPort := f.AllocPort()
+		f.RunServer("", newHTTPServer(barPort, "bar"))
+
+		clientConf := consts.DefaultClientConfig
+		clientConf += fmt.Sprintf(`
+			[foo]
+			type = http
+			local_port = %d
+			subdomain = foo
+
+			[bar]
+			type = http
+			local_port = %d
+			subdomain = bar
+			`, fooPort, barPort)
+
+		f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+		// foo
+		framework.NewRequestExpect(f).Explain("foo subdomain").Port(vhostHTTPPort).
+			RequestModify(func(r *request.Request) {
+				r.HTTP().HTTPHost("foo.example.com")
+			}).
+			ExpectResp([]byte("foo")).
+			Ensure()
+
+		// bar
+		framework.NewRequestExpect(f).Explain("bar subdomain").Port(vhostHTTPPort).
+			RequestModify(func(r *request.Request) {
+				r.HTTP().HTTPHost("bar.example.com")
+			}).
+			ExpectResp([]byte("bar")).
+			Ensure()
+	})
+
+	It("Modify headers", func() {
+		vhostHTTPPort := f.AllocPort()
+		serverConf := getDefaultServerConf(vhostHTTPPort)
+
+		localPort := f.AllocPort()
+		localServer := httpserver.New(
+			httpserver.WithBindPort(localPort),
+			httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+				w.Write([]byte(req.Header.Get("X-From-Where")))
+			})),
+		)
+		f.RunServer("", localServer)
+
+		clientConf := consts.DefaultClientConfig
+		clientConf += fmt.Sprintf(`
+			[test]
+			type = http
+			local_port = %d
+			custom_domains = normal.example.com
+			header_X-From-Where = frp
+			`, localPort)
+
+		f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+		// not set auth header
+		framework.NewRequestExpect(f).Port(vhostHTTPPort).
+			RequestModify(func(r *request.Request) {
+				r.HTTP().HTTPHost("normal.example.com")
+			}).
+			ExpectResp([]byte("frp")). // local http server will write this X-From-Where header to response body
+			Ensure()
+	})
+
+	It("Host Header Rewrite", func() {
+		vhostHTTPPort := f.AllocPort()
+		serverConf := getDefaultServerConf(vhostHTTPPort)
+
+		localPort := f.AllocPort()
+		localServer := httpserver.New(
+			httpserver.WithBindPort(localPort),
+			httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+				w.Write([]byte(req.Host))
+			})),
+		)
+		f.RunServer("", localServer)
+
+		clientConf := consts.DefaultClientConfig
+		clientConf += fmt.Sprintf(`
+			[test]
+			type = http
+			local_port = %d
+			custom_domains = normal.example.com
+			host_header_rewrite = rewrite.example.com
+			`, localPort)
+
+		f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+		framework.NewRequestExpect(f).Port(vhostHTTPPort).
+			RequestModify(func(r *request.Request) {
+				r.HTTP().HTTPHost("normal.example.com")
+			}).
+			ExpectResp([]byte("rewrite.example.com")). // local http server will write host header to response body
+			Ensure()
+	})
+
+	It("websocket", func() {
+		vhostHTTPPort := f.AllocPort()
+		serverConf := getDefaultServerConf(vhostHTTPPort)
+
+		upgrader := websocket.Upgrader{}
+
+		localPort := f.AllocPort()
+		localServer := httpserver.New(
+			httpserver.WithBindPort(localPort),
+			httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+				c, err := upgrader.Upgrade(w, req, nil)
+				if err != nil {
+					return
+				}
+				defer c.Close()
+				for {
+					mt, message, err := c.ReadMessage()
+					if err != nil {
+						break
+					}
+					err = c.WriteMessage(mt, message)
+					if err != nil {
+						break
+					}
+				}
+			})),
+		)
+
+		f.RunServer("", localServer)
+
+		clientConf := consts.DefaultClientConfig
+		clientConf += fmt.Sprintf(`
+			[test]
+			type = http
+			local_port = %d
+			custom_domains = 127.0.0.1
+			`, localPort)
+
+		f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+		u := url.URL{Scheme: "ws", Host: "127.0.0.1:" + strconv.Itoa(vhostHTTPPort)}
+		c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
+		framework.ExpectNoError(err)
+
+		err = c.WriteMessage(websocket.TextMessage, []byte(consts.TestString))
+		framework.ExpectNoError(err)
+
+		_, msg, err := c.ReadMessage()
+		framework.ExpectNoError(err)
+		framework.ExpectEqualValues(consts.TestString, string(msg))
+	})
+})

+ 52 - 5
test/e2e/basic/server.go

@@ -2,11 +2,14 @@ package basic
 
 import (
 	"fmt"
+	"net"
+	"strconv"
 
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework/consts"
 	"github.com/fatedier/frp/test/e2e/pkg/port"
 	"github.com/fatedier/frp/test/e2e/pkg/request"
+	clientsdk "github.com/fatedier/frp/test/e2e/pkg/sdk/client"
 
 	. "github.com/onsi/ginkgo"
 )
@@ -19,10 +22,10 @@ var _ = Describe("[Feature: Server Manager]", func() {
 		clientConf := consts.DefaultClientConfig
 
 		serverConf += `
-			allow_ports = 10000-20000,20002,30000-50000
+			allow_ports = 20000-25000,25002,30000-50000
 		`
 
-		tcpPortName := port.GenName("TCP", port.WithRangePorts(10000, 20000))
+		tcpPortName := port.GenName("TCP", port.WithRangePorts(20000, 25000))
 		udpPortName := port.GenName("UDP", port.WithRangePorts(30000, 50000))
 		clientConf += fmt.Sprintf(`
 			[tcp-allowded-in-range]
@@ -62,18 +65,62 @@ var _ = Describe("[Feature: Server Manager]", func() {
 		framework.NewRequestExpect(f).PortName(tcpPortName).Ensure()
 
 		// Not Allowed
-		framework.NewRequestExpect(f).RequestModify(framework.SetRequestPort(20001)).ExpectError(true).Ensure()
+		framework.NewRequestExpect(f).Port(25003).ExpectError(true).Ensure()
 
 		// Unavailable, already bind by frps
 		framework.NewRequestExpect(f).PortName(consts.PortServerName).ExpectError(true).Ensure()
 
 		// UDP
 		// Allowed in range
-		framework.NewRequestExpect(f).RequestModify(framework.SetRequestProtocol("udp")).PortName(udpPortName).Ensure()
+		framework.NewRequestExpect(f).Protocol("udp").PortName(udpPortName).Ensure()
 
 		// Not Allowed
 		framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
-			r.UDP().Port(20003)
+			r.UDP().Port(25003)
 		}).ExpectError(true).Ensure()
 	})
+
+	It("Alloc Random Port", func() {
+		serverConf := consts.DefaultServerConfig
+		clientConf := consts.DefaultClientConfig
+
+		adminPort := f.AllocPort()
+		clientConf += fmt.Sprintf(`
+			admin_port = %d
+
+			[tcp]
+			type = tcp
+			local_port = {{ .%s }}
+
+			[udp]
+			type = udp
+			local_port = {{ .%s }}
+			`, adminPort, framework.TCPEchoServerPort, framework.UDPEchoServerPort)
+
+		f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+		client := clientsdk.New("127.0.0.1", adminPort)
+
+		// tcp random port
+		status, err := client.GetProxyStatus("tcp")
+		framework.ExpectNoError(err)
+
+		_, portStr, err := net.SplitHostPort(status.RemoteAddr)
+		framework.ExpectNoError(err)
+		port, err := strconv.Atoi(portStr)
+		framework.ExpectNoError(err)
+
+		framework.NewRequestExpect(f).Port(port).Ensure()
+
+		// udp random port
+		status, err = client.GetProxyStatus("udp")
+		framework.ExpectNoError(err)
+
+		_, portStr, err = net.SplitHostPort(status.RemoteAddr)
+		framework.ExpectNoError(err)
+		port, err = strconv.Atoi(portStr)
+		framework.ExpectNoError(err)
+
+		framework.NewRequestExpect(f).Protocol("udp").Port(port).Ensure()
+	})
 })

+ 4 - 5
test/e2e/examples.go

@@ -5,7 +5,6 @@ import (
 
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework/consts"
-	"github.com/fatedier/frp/test/e2e/pkg/port"
 
 	. "github.com/onsi/ginkgo"
 )
@@ -18,17 +17,17 @@ var _ = Describe("[Feature: Example]", func() {
 			serverConf := consts.DefaultServerConfig
 			clientConf := consts.DefaultClientConfig
 
-			portName := port.GenName("TCP")
+			remotePort := f.AllocPort()
 			clientConf += fmt.Sprintf(`
 			[tcp]
 			type = tcp
 			local_port = {{ .%s }}
-			remote_port = {{ .%s }}
-			`, framework.TCPEchoServerPort, portName)
+			remote_port = %d
+			`, framework.TCPEchoServerPort, remotePort)
 
 			f.RunProcesses([]string{serverConf}, []string{clientConf})
 
-			framework.NewRequestExpect(f).PortName(portName).Ensure()
+			framework.NewRequestExpect(f).Port(remotePort).Ensure()
 		})
 	})
 })

+ 20 - 0
test/e2e/framework/expect.go

@@ -14,6 +14,10 @@ func ExpectEqualValues(actual interface{}, extra interface{}, explain ...interfa
 	gomega.ExpectWithOffset(1, actual).To(gomega.BeEquivalentTo(extra), explain...)
 }
 
+func ExpectEqualValuesWithOffset(offset int, actual interface{}, extra interface{}, explain ...interface{}) {
+	gomega.ExpectWithOffset(1+offset, actual).To(gomega.BeEquivalentTo(extra), explain...)
+}
+
 // ExpectNotEqual expects the specified two are not the same, otherwise an exception raises
 func ExpectNotEqual(actual interface{}, extra interface{}, explain ...interface{}) {
 	gomega.ExpectWithOffset(1, actual).NotTo(gomega.Equal(extra), explain...)
@@ -24,6 +28,10 @@ func ExpectError(err error, explain ...interface{}) {
 	gomega.ExpectWithOffset(1, err).To(gomega.HaveOccurred(), explain...)
 }
 
+func ExpectErrorWithOffset(offset int, err error, explain ...interface{}) {
+	gomega.ExpectWithOffset(1+offset, err).To(gomega.HaveOccurred(), explain...)
+}
+
 // ExpectNoError checks if "err" is set, and if so, fails assertion while logging the error.
 func ExpectNoError(err error, explain ...interface{}) {
 	ExpectNoErrorWithOffset(1, err, explain...)
@@ -40,6 +48,14 @@ func ExpectConsistOf(actual interface{}, extra interface{}, explain ...interface
 	gomega.ExpectWithOffset(1, actual).To(gomega.ConsistOf(extra), explain...)
 }
 
+func ExpectContainElements(actual interface{}, extra interface{}, explain ...interface{}) {
+	gomega.ExpectWithOffset(1, actual).To(gomega.ContainElements(extra), explain...)
+}
+
+func ExpectNotContainElements(actual interface{}, extra interface{}, explain ...interface{}) {
+	gomega.ExpectWithOffset(1, actual).NotTo(gomega.ContainElements(extra), explain...)
+}
+
 // ExpectHaveKey expects the actual map has the key in the keyset
 func ExpectHaveKey(actual interface{}, key interface{}, explain ...interface{}) {
 	gomega.ExpectWithOffset(1, actual).To(gomega.HaveKey(key), explain...)
@@ -53,3 +69,7 @@ func ExpectEmpty(actual interface{}, explain ...interface{}) {
 func ExpectTrue(actual interface{}, explain ...interface{}) {
 	gomega.ExpectWithOffset(1, actual).Should(gomega.BeTrue(), explain...)
 }
+
+func ExpectTrueWithOffset(offset int, actual interface{}, explain ...interface{}) {
+	gomega.ExpectWithOffset(1+offset, actual).Should(gomega.BeTrue(), explain...)
+}

+ 38 - 7
test/e2e/framework/framework.go

@@ -30,6 +30,9 @@ type Framework struct {
 	// ports used in this framework indexed by port name.
 	usedPorts map[string]int
 
+	// record ports alloced by this framework and release them after each test
+	allocedPorts []int
+
 	// portAllocator to alloc port for this test case.
 	portAllocator *port.Allocator
 
@@ -50,7 +53,13 @@ type Framework struct {
 	clientProcesses []*process.Process
 
 	// Manual registered mock servers.
-	servers []*server.Server
+	servers []server.Server
+
+	// used to generate unique config file name.
+	configFileIndex int64
+
+	// envs used to start processes, the form is `key=value`.
+	osEnvs []string
 }
 
 func NewDefaultFramework() *Framework {
@@ -80,7 +89,7 @@ func (f *Framework) BeforeEach() {
 
 	f.cleanupHandle = AddCleanupAction(f.AfterEach)
 
-	dir, err := ioutil.TempDir(os.TempDir(), "frpe2e-test-*")
+	dir, err := ioutil.TempDir(os.TempDir(), "frp-e2e-test-*")
 	ExpectNoError(err)
 	f.TempDirectory = dir
 
@@ -88,6 +97,14 @@ func (f *Framework) BeforeEach() {
 	if err := f.mockServers.Run(); err != nil {
 		Failf("%v", err)
 	}
+
+	params := f.mockServers.GetTemplateParams()
+	for k, v := range params {
+		switch t := v.(type) {
+		case int:
+			f.usedPorts[k] = int(t)
+		}
+	}
 }
 
 func (f *Framework) AfterEach() {
@@ -126,14 +143,23 @@ func (f *Framework) AfterEach() {
 	// clean directory
 	os.RemoveAll(f.TempDirectory)
 	f.TempDirectory = ""
-	f.serverConfPaths = nil
-	f.clientConfPaths = nil
+	f.serverConfPaths = []string{}
+	f.clientConfPaths = []string{}
 
 	// release used ports
 	for _, port := range f.usedPorts {
 		f.portAllocator.Release(port)
 	}
 	f.usedPorts = make(map[string]int)
+
+	// release alloced ports
+	for _, port := range f.allocedPorts {
+		f.portAllocator.Release(port)
+	}
+	f.allocedPorts = make([]int, 0)
+
+	// clear os envs
+	f.osEnvs = make([]string, 0)
 }
 
 var portRegex = regexp.MustCompile(`{{ \.Port.*? }}`)
@@ -210,6 +236,7 @@ func (f *Framework) PortByName(name string) int {
 func (f *Framework) AllocPort() int {
 	port := f.portAllocator.Get()
 	ExpectTrue(port > 0, "alloc port failed")
+	f.allocedPorts = append(f.allocedPorts, port)
 	return port
 }
 
@@ -217,11 +244,15 @@ func (f *Framework) ReleasePort(port int) {
 	f.portAllocator.Release(port)
 }
 
-func (f *Framework) RunServer(portName string, s *server.Server) {
+func (f *Framework) RunServer(portName string, s server.Server) {
 	f.servers = append(f.servers, s)
-	if s.BindPort() > 0 {
+	if s.BindPort() > 0 && portName != "" {
 		f.usedPorts[portName] = s.BindPort()
 	}
 	err := s.Run()
-	ExpectNoError(err, portName)
+	ExpectNoError(err, "RunServer: with PortName %s", portName)
+}
+
+func (f *Framework) SetEnvs(envs []string) {
+	f.osEnvs = envs
 }

+ 24 - 9
test/e2e/framework/mockservers.go

@@ -2,35 +2,45 @@ package framework
 
 import (
 	"fmt"
+	"net/http"
 	"os"
 
+	"github.com/fatedier/frp/test/e2e/framework/consts"
 	"github.com/fatedier/frp/test/e2e/mock/server"
+	"github.com/fatedier/frp/test/e2e/mock/server/httpserver"
+	"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
 	"github.com/fatedier/frp/test/e2e/pkg/port"
 )
 
 const (
-	TCPEchoServerPort = "TCPEchoServerPort"
-	UDPEchoServerPort = "UDPEchoServerPort"
-	UDSEchoServerAddr = "UDSEchoServerAddr"
+	TCPEchoServerPort    = "TCPEchoServerPort"
+	UDPEchoServerPort    = "UDPEchoServerPort"
+	UDSEchoServerAddr    = "UDSEchoServerAddr"
+	HTTPSimpleServerPort = "HTTPSimpleServerPort"
 )
 
 type MockServers struct {
-	tcpEchoServer *server.Server
-	udpEchoServer *server.Server
-	udsEchoServer *server.Server
+	tcpEchoServer    server.Server
+	udpEchoServer    server.Server
+	udsEchoServer    server.Server
+	httpSimpleServer server.Server
 }
 
 func NewMockServers(portAllocator *port.Allocator) *MockServers {
 	s := &MockServers{}
 	tcpPort := portAllocator.Get()
 	udpPort := portAllocator.Get()
-	s.tcpEchoServer = server.New(server.TCP, server.WithBindPort(tcpPort), server.WithEchoMode(true))
-	s.udpEchoServer = server.New(server.UDP, server.WithBindPort(udpPort), server.WithEchoMode(true))
+	httpPort := portAllocator.Get()
+	s.tcpEchoServer = streamserver.New(streamserver.TCP, streamserver.WithBindPort(tcpPort), streamserver.WithEchoMode(true))
+	s.udpEchoServer = streamserver.New(streamserver.UDP, streamserver.WithBindPort(udpPort), streamserver.WithEchoMode(true))
+	s.httpSimpleServer = httpserver.New(httpserver.WithBindPort(httpPort), httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+		w.Write([]byte(consts.TestString))
+	})))
 
 	udsIndex := portAllocator.Get()
 	udsAddr := fmt.Sprintf("%s/frp_echo_server_%d.sock", os.TempDir(), udsIndex)
 	os.Remove(udsAddr)
-	s.udsEchoServer = server.New(server.Unix, server.WithBindAddr(udsAddr), server.WithEchoMode(true))
+	s.udsEchoServer = streamserver.New(streamserver.Unix, streamserver.WithBindAddr(udsAddr), streamserver.WithEchoMode(true))
 	return s
 }
 
@@ -44,6 +54,9 @@ func (m *MockServers) Run() error {
 	if err := m.udsEchoServer.Run(); err != nil {
 		return err
 	}
+	if err := m.httpSimpleServer.Run(); err != nil {
+		return err
+	}
 	return nil
 }
 
@@ -51,6 +64,7 @@ func (m *MockServers) Close() {
 	m.tcpEchoServer.Close()
 	m.udpEchoServer.Close()
 	m.udsEchoServer.Close()
+	m.httpSimpleServer.Close()
 	os.Remove(m.udsEchoServer.BindAddr())
 }
 
@@ -59,6 +73,7 @@ func (m *MockServers) GetTemplateParams() map[string]interface{} {
 	ret[TCPEchoServerPort] = m.tcpEchoServer.BindPort()
 	ret[UDPEchoServerPort] = m.udpEchoServer.BindPort()
 	ret[UDSEchoServerAddr] = m.udsEchoServer.BindAddr()
+	ret[HTTPSimpleServerPort] = m.httpSimpleServer.BindPort()
 	return ret
 }
 

+ 35 - 6
test/e2e/framework/process.go

@@ -10,10 +10,6 @@ import (
 	"github.com/fatedier/frp/test/e2e/pkg/process"
 )
 
-func GenerateConfigFile(path string, content string) error {
-	return ioutil.WriteFile(path, []byte(content), 0666)
-}
-
 // RunProcesses run multiple processes from templates.
 // The first template should always be frps.
 func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []string) {
@@ -37,7 +33,8 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
 		err = ioutil.WriteFile(path, []byte(outs[i]), 0666)
 		ExpectNoError(err)
 		flog.Trace("[%s] %s", path, outs[i])
-		p := process.New(TestContext.FRPServerPath, []string{"-c", path})
+
+		p := process.NewWithEnvs(TestContext.FRPServerPath, []string{"-c", path}, f.osEnvs)
 		f.serverConfPaths = append(f.serverConfPaths, path)
 		f.serverProcesses = append(f.serverProcesses, p)
 		err = p.Start()
@@ -51,7 +48,8 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
 		err = ioutil.WriteFile(path, []byte(outs[index]), 0666)
 		ExpectNoError(err)
 		flog.Trace("[%s] %s", path, outs[index])
-		p := process.New(TestContext.FRPClientPath, []string{"-c", path})
+
+		p := process.NewWithEnvs(TestContext.FRPClientPath, []string{"-c", path}, f.osEnvs)
 		f.clientConfPaths = append(f.clientConfPaths, path)
 		f.clientProcesses = append(f.clientProcesses, p)
 		err = p.Start()
@@ -60,3 +58,34 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
 	}
 	time.Sleep(500 * time.Millisecond)
 }
+
+func (f *Framework) RunFrps(args ...string) (*process.Process, string, error) {
+	p := process.NewWithEnvs(TestContext.FRPServerPath, args, f.osEnvs)
+	f.serverProcesses = append(f.serverProcesses, p)
+	err := p.Start()
+	if err != nil {
+		return p, p.StdOutput(), err
+	}
+	// sleep for a while to get std output
+	time.Sleep(500 * time.Millisecond)
+	return p, p.StdOutput(), nil
+}
+
+func (f *Framework) RunFrpc(args ...string) (*process.Process, string, error) {
+	p := process.NewWithEnvs(TestContext.FRPClientPath, args, f.osEnvs)
+	f.clientProcesses = append(f.clientProcesses, p)
+	err := p.Start()
+	if err != nil {
+		return p, p.StdOutput(), err
+	}
+	time.Sleep(500 * time.Millisecond)
+	return p, p.StdOutput(), nil
+}
+
+func (f *Framework) GenerateConfigFile(content string) string {
+	f.configFileIndex++
+	path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-config-%d", f.configFileIndex))
+	err := ioutil.WriteFile(path, []byte(content), 0666)
+	ExpectNoError(err)
+	return path
+}

+ 50 - 19
test/e2e/framework/request.go

@@ -1,38 +1,35 @@
 package framework
 
 import (
+	"bytes"
+	"net/http"
+
+	flog "github.com/fatedier/frp/pkg/util/log"
 	"github.com/fatedier/frp/test/e2e/framework/consts"
 	"github.com/fatedier/frp/test/e2e/pkg/request"
 )
 
-func SetRequestProtocol(protocol string) func(*request.Request) {
-	return func(r *request.Request) {
-		r.Protocol(protocol)
+func SpecifiedHTTPBodyHandler(body []byte) http.HandlerFunc {
+	return func(w http.ResponseWriter, req *http.Request) {
+		w.Write(body)
 	}
 }
 
-func SetRequestPort(port int) func(*request.Request) {
-	return func(r *request.Request) {
-		r.Port(port)
+func ExpectResponseCode(code int) EnsureFunc {
+	return func(resp *request.Response) bool {
+		return resp.Code == code
 	}
 }
 
-// NewRequest return a default TCP request with default timeout and content.
+// NewRequest return a default request with default timeout and content.
 func NewRequest() *request.Request {
 	return request.New().
 		Timeout(consts.DefaultTimeout).
 		Body([]byte(consts.TestString))
 }
 
-func ExpectResponse(req *request.Request, expectResp []byte, explain ...interface{}) {
-	ret, err := req.Do()
-	ExpectNoError(err, explain...)
-	ExpectEqualValues(expectResp, ret, explain...)
-}
-
-func ExpectResponseError(req *request.Request, explain ...interface{}) {
-	_, err := req.Do()
-	ExpectError(err, explain...)
+func NewHTTPRequest() *request.Request {
+	return request.New().HTTP().HTTPParams("GET", "", "/", nil)
 }
 
 type RequestExpect struct {
@@ -59,6 +56,11 @@ func (e *RequestExpect) RequestModify(f func(r *request.Request)) *RequestExpect
 	return e
 }
 
+func (e *RequestExpect) Protocol(protocol string) *RequestExpect {
+	e.req.Protocol(protocol)
+	return e
+}
+
 func (e *RequestExpect) PortName(name string) *RequestExpect {
 	if e.f != nil {
 		e.req.Port(e.f.PortByName(name))
@@ -66,6 +68,13 @@ func (e *RequestExpect) PortName(name string) *RequestExpect {
 	return e
 }
 
+func (e *RequestExpect) Port(port int) *RequestExpect {
+	if e.f != nil {
+		e.req.Port(port)
+	}
+	return e
+}
+
 func (e *RequestExpect) ExpectResp(resp []byte) *RequestExpect {
 	e.expectResp = resp
 	return e
@@ -81,10 +90,32 @@ func (e *RequestExpect) Explain(explain ...interface{}) *RequestExpect {
 	return e
 }
 
-func (e *RequestExpect) Ensure() {
+type EnsureFunc func(*request.Response) bool
+
+func (e *RequestExpect) Ensure(fns ...EnsureFunc) {
+	ret, err := e.req.Do()
 	if e.expectError {
-		ExpectResponseError(e.req, e.explain...)
+		ExpectErrorWithOffset(1, err, e.explain...)
+		return
+	}
+	ExpectNoErrorWithOffset(1, err, e.explain...)
+
+	if len(fns) == 0 {
+		if !bytes.Equal(e.expectResp, ret.Content) {
+			flog.Trace("Response info: %+v", ret)
+		}
+		ExpectEqualValuesWithOffset(1, e.expectResp, ret.Content, e.explain...)
 	} else {
-		ExpectResponse(e.req, e.expectResp, e.explain...)
+		for _, fn := range fns {
+			ok := fn(ret)
+			if !ok {
+				flog.Trace("Response info: %+v", ret)
+			}
+			ExpectTrueWithOffset(1, ok, e.explain...)
+		}
 	}
 }
+
+func (e *RequestExpect) Do() (*request.Response, error) {
+	return e.req.Do()
+}

+ 1 - 2
test/e2e/framework/util.go

@@ -9,6 +9,5 @@ import (
 var RunID string
 
 func init() {
-	uuid, _ := uuid.NewUUID()
-	RunID = uuid.String()
+	RunID = uuid.NewString()
 }

+ 86 - 0
test/e2e/mock/server/httpserver/server.go

@@ -0,0 +1,86 @@
+package httpserver
+
+import (
+	"fmt"
+	"net"
+	"net/http"
+	"strconv"
+)
+
+type Server struct {
+	bindAddr string
+	bindPort int
+	hanlder  http.Handler
+
+	l  net.Listener
+	hs *http.Server
+}
+
+type Option func(*Server) *Server
+
+func New(options ...Option) *Server {
+	s := &Server{
+		bindAddr: "127.0.0.1",
+	}
+
+	for _, option := range options {
+		s = option(s)
+	}
+	return s
+}
+
+func WithBindAddr(addr string) Option {
+	return func(s *Server) *Server {
+		s.bindAddr = addr
+		return s
+	}
+}
+
+func WithBindPort(port int) Option {
+	return func(s *Server) *Server {
+		s.bindPort = port
+		return s
+	}
+}
+
+func WithHandler(h http.Handler) Option {
+	return func(s *Server) *Server {
+		s.hanlder = h
+		return s
+	}
+}
+
+func (s *Server) Run() error {
+	if err := s.initListener(); err != nil {
+		return err
+	}
+
+	addr := net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort))
+	hs := &http.Server{
+		Addr:    addr,
+		Handler: s.hanlder,
+	}
+	s.hs = hs
+	go hs.Serve(s.l)
+	return nil
+}
+
+func (s *Server) Close() error {
+	if s.hs != nil {
+		return s.hs.Close()
+	}
+	return nil
+}
+
+func (s *Server) initListener() (err error) {
+	s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort))
+	return
+}
+
+func (s *Server) BindAddr() string {
+	return s.bindAddr
+}
+
+func (s *Server) BindPort() int {
+	return s.bindPort
+}

+ 8 - 0
test/e2e/mock/server/interface.go

@@ -0,0 +1,8 @@
+package server
+
+type Server interface {
+	Run() error
+	Close() error
+	BindAddr() string
+	BindPort() int
+}

+ 7 - 7
test/e2e/mock/server/server.go → test/e2e/mock/server/streamserver/server.go

@@ -1,4 +1,4 @@
-package server
+package streamserver
 
 import (
 	"fmt"
@@ -7,16 +7,16 @@ import (
 	libnet "github.com/fatedier/frp/pkg/util/net"
 )
 
-type ServerType string
+type Type string
 
 const (
-	TCP  ServerType = "tcp"
-	UDP  ServerType = "udp"
-	Unix ServerType = "unix"
+	TCP  Type = "tcp"
+	UDP  Type = "udp"
+	Unix Type = "unix"
 )
 
 type Server struct {
-	netType     ServerType
+	netType     Type
 	bindAddr    string
 	bindPort    int
 	respContent []byte
@@ -29,7 +29,7 @@ type Server struct {
 
 type Option func(*Server) *Server
 
-func New(netType ServerType, options ...Option) *Server {
+func New(netType Type, options ...Option) *Server {
 	s := &Server{
 		netType:  netType,
 		bindAddr: "127.0.0.1",

+ 2 - 1
test/e2e/pkg/port/port.go

@@ -22,6 +22,7 @@ func NewAllocator(from int, to int, mod int, index int) *Allocator {
 		reserved: sets.NewInt(),
 		used:     sets.NewInt(),
 	}
+
 	for i := from; i <= to; i++ {
 		if i%mod == index {
 			pa.reserved.Insert(i)
@@ -50,7 +51,7 @@ func (pa *Allocator) GetByName(portName string) int {
 	pa.mu.Lock()
 	defer pa.mu.Unlock()
 
-	for i := 0; i < 10; i++ {
+	for i := 0; i < 20; i++ {
 		port := pa.getByRange(builder.rangePortFrom, builder.rangePortTo)
 		if port == 0 {
 			return 0

+ 12 - 0
test/e2e/pkg/process/process.go

@@ -13,11 +13,17 @@ type Process struct {
 	stdOutput   *bytes.Buffer
 
 	beforeStopHandler func()
+	stopped           bool
 }
 
 func New(path string, params []string) *Process {
+	return NewWithEnvs(path, params, nil)
+}
+
+func NewWithEnvs(path string, params []string, envs []string) *Process {
 	ctx, cancel := context.WithCancel(context.Background())
 	cmd := exec.CommandContext(ctx, path, params...)
+	cmd.Env = envs
 	p := &Process{
 		cmd:    cmd,
 		cancel: cancel,
@@ -34,6 +40,12 @@ func (p *Process) Start() error {
 }
 
 func (p *Process) Stop() error {
+	if p.stopped {
+		return nil
+	}
+	defer func() {
+		p.stopped = true
+	}()
 	if p.beforeStopHandler != nil {
 		p.beforeStopHandler()
 	}

+ 116 - 31
test/e2e/pkg/request/request.go

@@ -1,26 +1,44 @@
 package request
 
 import (
+	"bytes"
 	"fmt"
+	"io"
+	"io/ioutil"
 	"net"
+	"net/http"
+	"net/url"
+	"strconv"
 	"time"
 
 	libnet "github.com/fatedier/golib/net"
 )
 
 type Request struct {
-	protocol  string
-	addr      string
-	port      int
-	body      []byte
-	timeout   time.Duration
-	proxyURL  string
-	proxyHost string
+	protocol string
+
+	// for all protocol
+	addr    string
+	port    int
+	body    []byte
+	timeout time.Duration
+
+	// for http
+	method  string
+	host    string
+	path    string
+	headers map[string]string
+
+	proxyURL string
 }
 
 func New() *Request {
 	return &Request{
 		protocol: "tcp",
+		addr:     "127.0.0.1",
+
+		method: "GET",
+		path:   "/",
 	}
 }
 
@@ -39,9 +57,13 @@ func (r *Request) UDP() *Request {
 	return r
 }
 
-func (r *Request) Proxy(url, host string) *Request {
+func (r *Request) HTTP() *Request {
+	r.protocol = "http"
+	return r
+}
+
+func (r *Request) Proxy(url string) *Request {
 	r.proxyURL = url
-	r.proxyHost = host
 	return r
 }
 
@@ -55,6 +77,29 @@ func (r *Request) Port(port int) *Request {
 	return r
 }
 
+func (r *Request) HTTPParams(method, host, path string, headers map[string]string) *Request {
+	r.method = method
+	r.host = host
+	r.path = path
+	r.headers = headers
+	return r
+}
+
+func (r *Request) HTTPHost(host string) *Request {
+	r.host = host
+	return r
+}
+
+func (r *Request) HTTPPath(path string) *Request {
+	r.path = path
+	return r
+}
+
+func (r *Request) HTTPHeaders(headers map[string]string) *Request {
+	r.headers = headers
+	return r
+}
+
 func (r *Request) Timeout(timeout time.Duration) *Request {
 	r.timeout = timeout
 	return r
@@ -65,28 +110,34 @@ func (r *Request) Body(content []byte) *Request {
 	return r
 }
 
-func (r *Request) Do() ([]byte, error) {
+func (r *Request) Do() (*Response, error) {
 	var (
 		conn net.Conn
 		err  error
 	)
+
+	addr := net.JoinHostPort(r.addr, strconv.Itoa(r.port))
+	// for protocol http
+	if r.protocol == "http" {
+		return sendHTTPRequest(r.method, fmt.Sprintf("http://%s%s", addr, r.path),
+			r.host, r.headers, r.proxyURL, r.body)
+	}
+
+	// for protocol tcp and udp
 	if len(r.proxyURL) > 0 {
 		if r.protocol != "tcp" {
 			return nil, fmt.Errorf("only tcp protocol is allowed for proxy")
 		}
-		conn, err = libnet.DialTcpByProxy(r.proxyURL, r.proxyHost)
+		conn, err = libnet.DialTcpByProxy(r.proxyURL, addr)
 		if err != nil {
 			return nil, err
 		}
 	} else {
-		if r.addr == "" {
-			r.addr = fmt.Sprintf("127.0.0.1:%d", r.port)
-		}
 		switch r.protocol {
 		case "tcp":
-			conn, err = net.Dial("tcp", r.addr)
+			conn, err = net.Dial("tcp", addr)
 		case "udp":
-			conn, err = net.Dial("udp", r.addr)
+			conn, err = net.Dial("udp", addr)
 		default:
 			return nil, fmt.Errorf("invalid protocol")
 		}
@@ -99,29 +150,63 @@ func (r *Request) Do() ([]byte, error) {
 	if r.timeout > 0 {
 		conn.SetDeadline(time.Now().Add(r.timeout))
 	}
-	return sendRequestByConn(conn, r.body)
-}
-
-func SendTCPRequest(port int, content []byte, timeout time.Duration) ([]byte, error) {
-	c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
+	buf, err := sendRequestByConn(conn, r.body)
 	if err != nil {
-		return nil, fmt.Errorf("connect to tcp server error: %v", err)
+		return nil, err
 	}
-	defer c.Close()
+	return &Response{Content: buf}, nil
+}
 
-	c.SetDeadline(time.Now().Add(timeout))
-	return sendRequestByConn(c, content)
+type Response struct {
+	Code    int
+	Header  http.Header
+	Content []byte
 }
 
-func SendUDPRequest(port int, content []byte, timeout time.Duration) ([]byte, error) {
-	c, err := net.Dial("udp", fmt.Sprintf("127.0.0.1:%d", port))
+func sendHTTPRequest(method, urlstr string, host string, headers map[string]string, proxy string, body []byte) (*Response, error) {
+	var inBody io.Reader
+	if len(body) != 0 {
+		inBody = bytes.NewReader(body)
+	}
+	req, err := http.NewRequest(method, urlstr, inBody)
 	if err != nil {
-		return nil, fmt.Errorf("connect to udp server error:  %v", err)
+		return nil, err
+	}
+	if host != "" {
+		req.Host = host
+	}
+	for k, v := range headers {
+		req.Header.Set(k, v)
+	}
+	tr := &http.Transport{
+		DialContext: (&net.Dialer{
+			Timeout:   time.Second,
+			KeepAlive: 30 * time.Second,
+			DualStack: true,
+		}).DialContext,
+		MaxIdleConns:          100,
+		IdleConnTimeout:       90 * time.Second,
+		TLSHandshakeTimeout:   10 * time.Second,
+		ExpectContinueTimeout: 1 * time.Second,
+	}
+	if len(proxy) != 0 {
+		tr.Proxy = func(req *http.Request) (*url.URL, error) {
+			return url.Parse(proxy)
+		}
+	}
+	client := http.Client{Transport: tr}
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
 	}
-	defer c.Close()
 
-	c.SetDeadline(time.Now().Add(timeout))
-	return sendRequestByConn(c, content)
+	ret := &Response{Code: resp.StatusCode, Header: resp.Header}
+	buf, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+	ret.Content = buf
+	return ret, nil
 }
 
 func sendRequestByConn(c net.Conn, content []byte) ([]byte, error) {

+ 133 - 0
test/e2e/pkg/sdk/client/client.go

@@ -0,0 +1,133 @@
+package client
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"github.com/fatedier/frp/client"
+	"github.com/fatedier/frp/test/e2e/pkg/utils"
+)
+
+type Client struct {
+	address  string
+	authUser string
+	authPwd  string
+}
+
+func New(host string, port int) *Client {
+	return &Client{
+		address: net.JoinHostPort(host, strconv.Itoa(port)),
+	}
+}
+
+func (c *Client) SetAuth(user, pwd string) {
+	c.authUser = user
+	c.authPwd = pwd
+}
+
+func (c *Client) GetProxyStatus(name string) (*client.ProxyStatusResp, error) {
+	req, err := http.NewRequest("GET", "http://"+c.address+"/api/status", nil)
+	if err != nil {
+		return nil, err
+	}
+	content, err := c.do(req)
+	if err != nil {
+		return nil, err
+	}
+	allStatus := &client.StatusResp{}
+	if err = json.Unmarshal([]byte(content), &allStatus); err != nil {
+		return nil, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(content))
+	}
+	for _, s := range allStatus.TCP {
+		if s.Name == name {
+			return &s, nil
+		}
+	}
+	for _, s := range allStatus.UDP {
+		if s.Name == name {
+			return &s, nil
+		}
+	}
+	for _, s := range allStatus.HTTP {
+		if s.Name == name {
+			return &s, nil
+		}
+	}
+	for _, s := range allStatus.HTTPS {
+		if s.Name == name {
+			return &s, nil
+		}
+	}
+	for _, s := range allStatus.STCP {
+		if s.Name == name {
+			return &s, nil
+		}
+	}
+	for _, s := range allStatus.XTCP {
+		if s.Name == name {
+			return &s, nil
+		}
+	}
+	for _, s := range allStatus.SUDP {
+		if s.Name == name {
+			return &s, nil
+		}
+	}
+	return nil, fmt.Errorf("no proxy status found")
+}
+
+func (c *Client) Reload() error {
+	req, err := http.NewRequest("GET", "http://"+c.address+"/api/reload", nil)
+	if err != nil {
+		return err
+	}
+	_, err = c.do(req)
+	return err
+}
+
+func (c *Client) GetConfig() (string, error) {
+	req, err := http.NewRequest("GET", "http://"+c.address+"/api/config", nil)
+	if err != nil {
+		return "", err
+	}
+	return c.do(req)
+}
+
+func (c *Client) UpdateConfig(content string) error {
+	req, err := http.NewRequest("PUT", "http://"+c.address+"/api/config", strings.NewReader(content))
+	if err != nil {
+		return err
+	}
+	_, err = c.do(req)
+	return err
+}
+
+func (c *Client) setAuthHeader(req *http.Request) {
+	if c.authUser != "" || c.authPwd != "" {
+		req.Header.Set("Authorization", utils.BasicAuth(c.authUser, c.authPwd))
+	}
+}
+
+func (c *Client) do(req *http.Request) (string, error) {
+	c.setAuthHeader(req)
+
+	resp, err := http.DefaultClient.Do(req)
+	if err != nil {
+		return "", err
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != 200 {
+		return "", fmt.Errorf("api status code [%d]", resp.StatusCode)
+	}
+	buf, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return "", err
+	}
+	return string(buf), nil
+}

+ 10 - 0
test/e2e/pkg/utils/utils.go

@@ -0,0 +1,10 @@
+package utils
+
+import (
+	"encoding/base64"
+)
+
+func BasicAuth(username, passwd string) string {
+	auth := username + ":" + passwd
+	return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
+}

+ 35 - 5
test/e2e/plugin/client_plugins.go

@@ -2,10 +2,12 @@ package plugin
 
 import (
 	"fmt"
+	"strconv"
 
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework/consts"
 	"github.com/fatedier/frp/test/e2e/pkg/port"
+	"github.com/fatedier/frp/test/e2e/pkg/request"
 
 	. "github.com/onsi/ginkgo"
 )
@@ -65,12 +67,40 @@ var _ = Describe("[Feature: Client-Plugins]", func() {
 			f.RunProcesses([]string{serverConf}, []string{clientConf})
 
 			for _, test := range tests {
-				framework.ExpectResponse(
-					framework.NewRequest().Port(f.PortByName(test.portName)),
-					[]byte(consts.TestString),
-					test.proxyName,
-				)
+				framework.NewRequestExpect(f).Port(f.PortByName(test.portName)).Ensure()
 			}
 		})
 	})
+
+	It("plugin http_proxy", func() {
+		serverConf := consts.DefaultServerConfig
+		clientConf := consts.DefaultClientConfig
+
+		remotePort := f.AllocPort()
+		clientConf += fmt.Sprintf(`
+		[tcp]
+		type = tcp
+		remote_port = %d
+		plugin = http_proxy
+		plugin_http_user = abc
+		plugin_http_passwd = 123
+		`, remotePort)
+
+		f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+		// http proxy, no auth info
+		framework.NewRequestExpect(f).PortName(framework.HTTPSimpleServerPort).RequestModify(func(r *request.Request) {
+			r.HTTP().Proxy("http://127.0.0.1:" + strconv.Itoa(remotePort))
+		}).Ensure(framework.ExpectResponseCode(407))
+
+		// http proxy, correct auth
+		framework.NewRequestExpect(f).PortName(framework.HTTPSimpleServerPort).RequestModify(func(r *request.Request) {
+			r.HTTP().Proxy("http://abc:123@127.0.0.1:" + strconv.Itoa(remotePort))
+		}).Ensure()
+
+		// connect TCP server by CONNECT method
+		framework.NewRequestExpect(f).PortName(framework.TCPEchoServerPort).RequestModify(func(r *request.Request) {
+			r.TCP().Proxy("http://abc:123@127.0.0.1:" + strconv.Itoa(remotePort))
+		})
+	})
 })

+ 0 - 204
tests/ci/auto_test_frpc.ini

@@ -1,204 +0,0 @@
-[common]
-server_addr = 127.0.0.1
-server_port = 10700
-log_file = console
-log_level = trace
-token = 123456
-admin_port = 10600
-admin_user = abc
-admin_pwd = abc
-
-[tcp_normal]
-type = tcp
-local_ip = 127.0.0.1
-local_port = 10701
-remote_port = 10801
-
-[tcp_ec]
-type = tcp
-local_ip = 127.0.0.1
-local_port = 10701
-remote_port = 10901
-use_encryption = true
-use_compression = true
-
-[tcp_group1]
-type = tcp
-local_ip = 127.0.0.1
-local_port = 10701
-remote_port = 10802
-group = test1
-group_key = 123
-
-[tcp_group2]
-type = tcp
-local_ip = 127.0.0.1
-local_port = 10702
-remote_port = 10802
-group = test1
-group_key = 123
-
-[udp_normal]
-type = udp
-local_ip = 127.0.0.1
-local_port = 10702
-remote_port = 10802
-
-[udp_ec]
-type = udp
-local_ip = 127.0.0.1
-local_port = 10702
-remote_port = 10902
-use_encryption = true
-use_compression = true
-
-[unix_domain]
-type = tcp
-remote_port = 10803
-plugin = unix_domain_socket
-plugin_unix_path = /tmp/frp_echo_server.sock
-
-[stcp]
-type = stcp
-sk = abcdefg
-local_ip = 127.0.0.1
-local_port = 10701
-
-[stcp_ec]
-type = stcp
-sk = abc
-local_ip = 127.0.0.1
-local_port = 10701
-use_encryption = true
-use_compression = true
-
-[sudp]
-type = sudp
-sk = abcdefg
-local_ip = 127.0.0.1
-local_port = 10702
-
-[web01]
-type = http
-local_ip = 127.0.0.1
-local_port = 10704
-custom_domains = 127.0.0.1
-
-[web02]
-type = http
-local_ip = 127.0.0.1
-local_port = 10704
-custom_domains = test2.frp.com
-host_header_rewrite = test2.frp.com
-use_encryption = true
-use_compression = true
-
-[web03]
-type = http
-local_ip = 127.0.0.1
-local_port = 10704
-custom_domains = test3.frp.com
-use_encryption = true
-use_compression = true
-host_header_rewrite = test3.frp.com
-locations = /,/foo
-
-[web04]
-type = http
-local_ip = 127.0.0.1
-local_port = 10704
-custom_domains = test3.frp.com
-use_encryption = true
-use_compression = true
-host_header_rewrite = test3.frp.com
-locations = /bar
-
-[web05]
-type = http
-local_ip = 127.0.0.1
-local_port = 10704
-custom_domains = test5.frp.com
-host_header_rewrite = test5.frp.com
-use_encryption = true
-use_compression = true
-http_user = test
-http_user = test
-
-[web06]
-type = http
-local_ip = 127.0.0.1
-local_port = 10704
-custom_domains = test6.frp.com
-host_header_rewrite = test6.frp.com
-header_X-From-Where = frp
-
-[wildcard_http]
-type = http
-local_ip = 127.0.0.1
-local_port = 10704
-custom_domains = *.frp1.com
-
-[subhost01]
-type = http
-local_ip = 127.0.0.1
-local_port = 10704
-subdomain = test01
-
-[subhost02]
-type = http
-local_ip = 127.0.0.1
-local_port = 10704
-subdomain = test02
-
-[tcp_port_not_allowed]
-type = tcp
-local_ip = 127.0.0.1
-local_port = 10701
-remote_port = 20001
-
-[tcp_port_unavailable]
-type =tcp
-local_ip = 127.0.0.1
-local_port = 10701
-remote_port = 10700
-
-[tcp_port_normal]
-type = tcp
-local_ip = 127.0.0.1
-local_port = 10701
-remote_port = 20002
-
-[tcp_random_port]
-type = tcp
-local_ip = 127.0.0.1
-local_port = 10701
-remote_port = 0
-
-[udp_port_not_allowed]
-type = udp
-local_ip = 127.0.0.1
-local_port = 10702
-remote_port = 20001
-
-[udp_port_normal]
-type = udp
-local_ip = 127.0.0.1
-local_port = 10702
-remote_port = 20002
-
-[udp_random_port]
-type = udp
-local_ip = 127.0.0.1
-local_port = 10702
-remote_port = 0
-
-[http_proxy]
-type = tcp
-plugin = http_proxy
-remote_port = 0
-
-[range:range_tcp]
-type = tcp
-local_ip = 127.0.0.1
-local_port = 30000-30001,30003
-remote_port = 30000-30001,30003

+ 0 - 34
tests/ci/auto_test_frpc_visitor.ini

@@ -1,34 +0,0 @@
-[common]
-server_addr = 0.0.0.0
-server_port = 10700
-log_file = console
-# debug, info, warn, error
-log_level = debug
-token = 123456
-
-[stcp_visitor]
-type = stcp
-role = visitor
-server_name = stcp
-sk = abcdefg
-bind_addr = 127.0.0.1
-bind_port = 10805
-
-[stcp_ec_visitor]
-type = stcp
-role = visitor
-server_name = stcp_ec
-sk = abc
-bind_addr = 127.0.0.1
-bind_port = 10905
-use_encryption = true
-use_compression = true
-
-
-[sudp_visitor]
-type = sudp
-role = visitor
-server_name = sudp
-sk = abcdefg
-bind_addr = 127.0.0.1
-bind_port = 10816

+ 0 - 9
tests/ci/auto_test_frps.ini

@@ -1,9 +0,0 @@
-[common]
-bind_addr = 0.0.0.0
-bind_port = 10700
-vhost_http_port = 10804
-tcpmux_httpconnect_port = 10806
-log_level = trace
-token = 123456
-allow_ports = 10000-20000,20002,30000-50000
-subdomain_host = sub.com

+ 0 - 85
tests/ci/cmd_test.go

@@ -1,85 +0,0 @@
-package ci
-
-import (
-	"testing"
-	"time"
-
-	"github.com/fatedier/frp/tests/consts"
-	"github.com/fatedier/frp/tests/util"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestCmdTCP(t *testing.T) {
-	assert := assert.New(t)
-
-	var err error
-	s := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-t", "123", "-p", "20000"})
-	err = s.Start()
-	if assert.NoError(err) {
-		defer s.Stop()
-	}
-	time.Sleep(500 * time.Millisecond)
-
-	c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"tcp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
-		"-l", "10701", "-r", "20801", "-n", "tcp_test"})
-	err = c.Start()
-	if assert.NoError(err) {
-		defer c.Stop()
-	}
-	time.Sleep(500 * time.Millisecond)
-
-	res, err := util.SendTCPMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
-	assert.NoError(err)
-	assert.Equal(consts.TEST_TCP_ECHO_STR, res)
-}
-
-func TestCmdUDP(t *testing.T) {
-	assert := assert.New(t)
-
-	var err error
-	s := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-t", "123", "-p", "20000"})
-	err = s.Start()
-	if assert.NoError(err) {
-		defer s.Stop()
-	}
-	time.Sleep(500 * time.Millisecond)
-
-	c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"udp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
-		"-l", "10702", "-r", "20802", "-n", "udp_test"})
-	err = c.Start()
-	if assert.NoError(err) {
-		defer c.Stop()
-	}
-	time.Sleep(500 * time.Millisecond)
-
-	res, err := util.SendUDPMsg("127.0.0.1:20802", consts.TEST_UDP_ECHO_STR)
-	assert.NoError(err)
-	assert.Equal(consts.TEST_UDP_ECHO_STR, res)
-}
-
-func TestCmdHTTP(t *testing.T) {
-	assert := assert.New(t)
-
-	var err error
-	s := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-t", "123", "-p", "20000", "--vhost_http_port", "20001"})
-	err = s.Start()
-	if assert.NoError(err) {
-		defer s.Stop()
-	}
-	time.Sleep(500 * time.Millisecond)
-
-	c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"http", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
-		"-n", "udp_test", "-l", "10704", "--custom_domain", "127.0.0.1"})
-	err = c.Start()
-	if assert.NoError(err) {
-		defer c.Stop()
-	}
-	time.Sleep(500 * time.Millisecond)
-
-	code, body, _, err := util.SendHTTPMsg("GET", "http://127.0.0.1:20001", "", nil, "")
-	if assert.NoError(err) {
-		assert.Equal(200, code)
-		assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
-	}
-}

+ 0 - 309
tests/ci/health/health_test.go

@@ -1,309 +0,0 @@
-package health
-
-import (
-	"net/http"
-	"os"
-	"strings"
-	"sync"
-	"testing"
-	"time"
-
-	"github.com/fatedier/frp/tests/config"
-	"github.com/fatedier/frp/tests/consts"
-	"github.com/fatedier/frp/tests/mock"
-	"github.com/fatedier/frp/tests/util"
-
-	"github.com/stretchr/testify/assert"
-)
-
-const FRPS_CONF = `
-[common]
-bind_addr = 0.0.0.0
-bind_port = 14000
-vhost_http_port = 14000
-log_file = console
-log_level = debug
-token = 123456
-`
-
-const FRPC_CONF = `
-[common]
-server_addr = 127.0.0.1
-server_port = 14000
-log_file = console
-log_level = debug
-token = 123456
-
-[tcp1]
-type = tcp
-local_port = 15001
-remote_port = 15000
-group = test
-group_key = 123
-health_check_type = tcp
-health_check_interval_s = 1
-
-[tcp2]
-type = tcp
-local_port = 15002
-remote_port = 15000
-group = test
-group_key = 123
-health_check_type = tcp
-health_check_interval_s = 1
-
-[http1]
-type = http
-local_port = 15003
-custom_domains = test1.com
-health_check_type = http
-health_check_interval_s = 1
-health_check_url = /health
-
-[http2]
-type = http
-local_port = 15004
-custom_domains = test2.com
-health_check_type = http
-health_check_interval_s = 1
-health_check_url = /health
-
-[http3]
-type = http
-local_port = 15005
-custom_domains = test.balancing.com
-group = test-balancing
-group_key = 123
-
-[http4]
-type = http
-local_port = 15006
-custom_domains = test.balancing.com
-group = test-balancing
-group_key = 123
-`
-
-func TestHealthCheck(t *testing.T) {
-	assert := assert.New(t)
-
-	// ****** start background services ******
-	echoSvc1 := mock.NewEchoServer(15001, 1, "echo1")
-	err := echoSvc1.Start()
-	if assert.NoError(err) {
-		defer echoSvc1.Stop()
-	}
-
-	echoSvc2 := mock.NewEchoServer(15002, 1, "echo2")
-	err = echoSvc2.Start()
-	if assert.NoError(err) {
-		defer echoSvc2.Stop()
-	}
-
-	var healthMu sync.RWMutex
-	svc1Health := true
-	svc2Health := true
-	httpSvc1 := mock.NewHTTPServer(15003, func(w http.ResponseWriter, r *http.Request) {
-		if strings.Contains(r.URL.Path, "health") {
-			healthMu.RLock()
-			defer healthMu.RUnlock()
-			if svc1Health {
-				w.WriteHeader(200)
-			} else {
-				w.WriteHeader(500)
-			}
-		} else {
-			w.Write([]byte("http1"))
-		}
-	})
-	err = httpSvc1.Start()
-	if assert.NoError(err) {
-		defer httpSvc1.Stop()
-	}
-
-	httpSvc2 := mock.NewHTTPServer(15004, func(w http.ResponseWriter, r *http.Request) {
-		if strings.Contains(r.URL.Path, "health") {
-			healthMu.RLock()
-			defer healthMu.RUnlock()
-			if svc2Health {
-				w.WriteHeader(200)
-			} else {
-				w.WriteHeader(500)
-			}
-		} else {
-			w.Write([]byte("http2"))
-		}
-	})
-	err = httpSvc2.Start()
-	if assert.NoError(err) {
-		defer httpSvc2.Stop()
-	}
-
-	httpSvc3 := mock.NewHTTPServer(15005, func(w http.ResponseWriter, r *http.Request) {
-		time.Sleep(time.Second)
-		w.Write([]byte("http3"))
-	})
-	err = httpSvc3.Start()
-	if assert.NoError(err) {
-		defer httpSvc3.Stop()
-	}
-
-	httpSvc4 := mock.NewHTTPServer(15006, func(w http.ResponseWriter, r *http.Request) {
-		time.Sleep(time.Second)
-		w.Write([]byte("http4"))
-	})
-	err = httpSvc4.Start()
-	if assert.NoError(err) {
-		defer httpSvc4.Stop()
-	}
-
-	time.Sleep(200 * time.Millisecond)
-
-	// ****** start frps and frpc ******
-	frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_CONF)
-	if assert.NoError(err) {
-		defer os.Remove(frpsCfgPath)
-	}
-
-	frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_CONF)
-	if assert.NoError(err) {
-		defer os.Remove(frpcCfgPath)
-	}
-
-	frpsProcess := util.NewProcess(consts.FRPS_SUB_BIN_PATH, []string{"-c", frpsCfgPath})
-	err = frpsProcess.Start()
-	if assert.NoError(err) {
-		defer frpsProcess.Stop()
-	}
-
-	time.Sleep(500 * time.Millisecond)
-
-	frpcProcess := util.NewProcess(consts.FRPC_SUB_BIN_PATH, []string{"-c", frpcCfgPath})
-	err = frpcProcess.Start()
-	if assert.NoError(err) {
-		defer frpcProcess.Stop()
-	}
-	time.Sleep(1000 * time.Millisecond)
-
-	// ****** healcheck type tcp ******
-	// echo1 and echo2 is ok
-	result := make([]string, 0)
-	res, err := util.SendTCPMsg("127.0.0.1:15000", "echo")
-	assert.NoError(err)
-	result = append(result, res)
-
-	res, err = util.SendTCPMsg("127.0.0.1:15000", "echo")
-	assert.NoError(err)
-	result = append(result, res)
-
-	assert.Contains(result, "echo1")
-	assert.Contains(result, "echo2")
-
-	// close echo2 server, echo1 is work
-	echoSvc2.Stop()
-	time.Sleep(1200 * time.Millisecond)
-
-	result = make([]string, 0)
-	res, err = util.SendTCPMsg("127.0.0.1:15000", "echo")
-	assert.NoError(err)
-	result = append(result, res)
-
-	res, err = util.SendTCPMsg("127.0.0.1:15000", "echo")
-	assert.NoError(err)
-	result = append(result, res)
-
-	assert.NotContains(result, "echo2")
-
-	// resume echo2 server, all services are ok
-	echoSvc2 = mock.NewEchoServer(15002, 1, "echo2")
-	err = echoSvc2.Start()
-	if assert.NoError(err) {
-		defer echoSvc2.Stop()
-	}
-
-	time.Sleep(1200 * time.Millisecond)
-
-	result = make([]string, 0)
-	res, err = util.SendTCPMsg("127.0.0.1:15000", "echo")
-	assert.NoError(err)
-	result = append(result, res)
-
-	res, err = util.SendTCPMsg("127.0.0.1:15000", "echo")
-	assert.NoError(err)
-	result = append(result, res)
-
-	assert.Contains(result, "echo1")
-	assert.Contains(result, "echo2")
-
-	// ****** healcheck type http ******
-	// http1 and http2 is ok
-	code, body, _, err := util.SendHTTPMsg("GET", "http://127.0.0.1:14000/xxx", "test1.com", nil, "")
-	assert.NoError(err)
-	assert.Equal(200, code)
-	assert.Equal("http1", body)
-
-	code, body, _, err = util.SendHTTPMsg("GET", "http://127.0.0.1:14000/xxx", "test2.com", nil, "")
-	assert.NoError(err)
-	assert.Equal(200, code)
-	assert.Equal("http2", body)
-
-	// http2 health check error
-	healthMu.Lock()
-	svc2Health = false
-	healthMu.Unlock()
-	time.Sleep(1200 * time.Millisecond)
-
-	code, body, _, err = util.SendHTTPMsg("GET", "http://127.0.0.1:14000/xxx", "test1.com", nil, "")
-	assert.NoError(err)
-	assert.Equal(200, code)
-	assert.Equal("http1", body)
-
-	code, _, _, err = util.SendHTTPMsg("GET", "http://127.0.0.1:14000/xxx", "test2.com", nil, "")
-	assert.NoError(err)
-	assert.Equal(404, code)
-
-	// resume http2 service, http1 and http2 are ok
-	healthMu.Lock()
-	svc2Health = true
-	healthMu.Unlock()
-	time.Sleep(1200 * time.Millisecond)
-
-	code, body, _, err = util.SendHTTPMsg("GET", "http://127.0.0.1:14000/xxx", "test1.com", nil, "")
-	assert.NoError(err)
-	assert.Equal(200, code)
-	assert.Equal("http1", body)
-
-	code, body, _, err = util.SendHTTPMsg("GET", "http://127.0.0.1:14000/xxx", "test2.com", nil, "")
-	assert.NoError(err)
-	assert.Equal(200, code)
-	assert.Equal("http2", body)
-
-	// ****** load balancing type http ******
-	result = make([]string, 0)
-	var wait sync.WaitGroup
-	var mu sync.Mutex
-	wait.Add(2)
-
-	go func() {
-		defer wait.Done()
-		code, body, _, err := util.SendHTTPMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
-		assert.NoError(err)
-		assert.Equal(200, code)
-		mu.Lock()
-		result = append(result, body)
-		mu.Unlock()
-	}()
-
-	go func() {
-		defer wait.Done()
-		code, body, _, err = util.SendHTTPMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
-		assert.NoError(err)
-		assert.Equal(200, code)
-		mu.Lock()
-		result = append(result, body)
-		mu.Unlock()
-	}()
-	wait.Wait()
-
-	assert.Contains(result, "http3")
-	assert.Contains(result, "http4")
-}

+ 0 - 250
tests/ci/normal_test.go

@@ -1,250 +0,0 @@
-package ci
-
-import (
-	"fmt"
-	"net/http"
-	"net/url"
-	"os"
-	"testing"
-	"time"
-
-	"github.com/gorilla/websocket"
-	"github.com/stretchr/testify/assert"
-
-	"github.com/fatedier/frp/client/proxy"
-	"github.com/fatedier/frp/tests/consts"
-	"github.com/fatedier/frp/tests/mock"
-	"github.com/fatedier/frp/tests/util"
-
-	gnet "github.com/fatedier/golib/net"
-)
-
-func TestMain(m *testing.M) {
-	var err error
-	tcpEcho1 := mock.NewEchoServer(consts.TEST_TCP_PORT, 1, "")
-	tcpEcho2 := mock.NewEchoServer(consts.TEST_TCP2_PORT, 2, "")
-
-	if err = tcpEcho1.Start(); err != nil {
-		panic(err)
-	}
-	if err = tcpEcho2.Start(); err != nil {
-		panic(err)
-	}
-
-	go mock.StartUDPEchoServer(consts.TEST_UDP_PORT)
-	go mock.StartUnixDomainServer(consts.TEST_UNIX_DOMAIN_ADDR)
-	go mock.StartHTTPServer(consts.TEST_HTTP_PORT)
-
-	p1 := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", "./auto_test_frps.ini"})
-	if err = p1.Start(); err != nil {
-		panic(err)
-	}
-
-	time.Sleep(500 * time.Millisecond)
-	p2 := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", "./auto_test_frpc.ini"})
-	if err = p2.Start(); err != nil {
-		panic(err)
-	}
-
-	p3 := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", "./auto_test_frpc_visitor.ini"})
-	if err = p3.Start(); err != nil {
-		panic(err)
-	}
-	time.Sleep(500 * time.Millisecond)
-
-	exitCode := m.Run()
-	p1.Stop()
-	p2.Stop()
-	p3.Stop()
-	os.Exit(exitCode)
-}
-
-func TestHTTP(t *testing.T) {
-	assert := assert.New(t)
-	// web01
-	code, body, _, err := util.SendHTTPMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "", nil, "")
-	if assert.NoError(err) {
-		assert.Equal(200, code)
-		assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
-	}
-
-	// web02
-	code, body, _, err = util.SendHTTPMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test2.frp.com", nil, "")
-	if assert.NoError(err) {
-		assert.Equal(200, code)
-		assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
-	}
-
-	// error host header
-	code, body, _, err = util.SendHTTPMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "errorhost.frp.com", nil, "")
-	if assert.NoError(err) {
-		assert.Equal(404, code)
-	}
-
-	// web03
-	code, body, _, err = util.SendHTTPMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test3.frp.com", nil, "")
-	if assert.NoError(err) {
-		assert.Equal(200, code)
-		assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
-	}
-
-	code, body, _, err = util.SendHTTPMsg("GET", fmt.Sprintf("http://127.0.0.1:%d/foo", consts.TEST_HTTP_FRP_PORT), "test3.frp.com", nil, "")
-	if assert.NoError(err) {
-		assert.Equal(200, code)
-		assert.Equal(consts.TEST_HTTP_FOO_STR, body)
-	}
-
-	// web04
-	code, body, _, err = util.SendHTTPMsg("GET", fmt.Sprintf("http://127.0.0.1:%d/bar", consts.TEST_HTTP_FRP_PORT), "test3.frp.com", nil, "")
-	if assert.NoError(err) {
-		assert.Equal(200, code)
-		assert.Equal(consts.TEST_HTTP_BAR_STR, body)
-	}
-
-	// web05
-	code, body, _, err = util.SendHTTPMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test5.frp.com", nil, "")
-	if assert.NoError(err) {
-		assert.Equal(401, code)
-	}
-
-	headers := make(map[string]string)
-	headers["Authorization"] = util.BasicAuth("test", "test")
-	code, body, _, err = util.SendHTTPMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test5.frp.com", headers, "")
-	if assert.NoError(err) {
-		assert.Equal(401, code)
-	}
-
-	// web06
-	var header http.Header
-	code, body, header, err = util.SendHTTPMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test6.frp.com", nil, "")
-	if assert.NoError(err) {
-		assert.Equal(200, code)
-		assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
-		assert.Equal("true", header.Get("X-Header-Set"))
-	}
-
-	// wildcard_http
-	// test.frp1.com match *.frp1.com
-	code, body, _, err = util.SendHTTPMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test.frp1.com", nil, "")
-	if assert.NoError(err) {
-		assert.Equal(200, code)
-		assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
-	}
-
-	// new.test.frp1.com also match *.frp1.com
-	code, body, _, err = util.SendHTTPMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "new.test.frp1.com", nil, "")
-	if assert.NoError(err) {
-		assert.Equal(200, code)
-		assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
-	}
-
-	// subhost01
-	code, body, _, err = util.SendHTTPMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test01.sub.com", nil, "")
-	if assert.NoError(err) {
-		assert.Equal(200, code)
-		assert.Equal("test01.sub.com", body)
-	}
-
-	// subhost02
-	code, body, _, err = util.SendHTTPMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test02.sub.com", nil, "")
-	if assert.NoError(err) {
-		assert.Equal(200, code)
-		assert.Equal("test02.sub.com", body)
-	}
-}
-
-func TestWebSocket(t *testing.T) {
-	assert := assert.New(t)
-
-	u := url.URL{Scheme: "ws", Host: fmt.Sprintf("%s:%d", "127.0.0.1", consts.TEST_HTTP_FRP_PORT), Path: "/ws"}
-	c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
-	assert.NoError(err)
-	defer c.Close()
-
-	err = c.WriteMessage(websocket.TextMessage, []byte(consts.TEST_HTTP_NORMAL_STR))
-	assert.NoError(err)
-
-	_, msg, err := c.ReadMessage()
-	assert.NoError(err)
-	assert.Equal(consts.TEST_HTTP_NORMAL_STR, string(msg))
-}
-
-func TestRandomPort(t *testing.T) {
-	assert := assert.New(t)
-	// tcp
-	status, err := util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyTCPRandomPort)
-	if assert.NoError(err) {
-		addr := status.RemoteAddr
-		res, err := util.SendTCPMsg(addr, consts.TEST_TCP_ECHO_STR)
-		assert.NoError(err)
-		assert.Equal(consts.TEST_TCP_ECHO_STR, res)
-	}
-
-	// udp
-	status, err = util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyUDPRandomPort)
-	if assert.NoError(err) {
-		addr := status.RemoteAddr
-		res, err := util.SendUDPMsg(addr, consts.TEST_UDP_ECHO_STR)
-		assert.NoError(err)
-		assert.Equal(consts.TEST_UDP_ECHO_STR, res)
-	}
-}
-
-func TestPluginHTTPProxy(t *testing.T) {
-	assert := assert.New(t)
-	status, err := util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, consts.ProxyHTTPProxy)
-	if assert.NoError(err) {
-		assert.Equal(proxy.ProxyPhaseRunning, status.Status)
-
-		// http proxy
-		addr := status.RemoteAddr
-		code, body, _, err := util.SendHTTPMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT),
-			"", nil, "http://"+addr)
-		if assert.NoError(err) {
-			assert.Equal(200, code)
-			assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
-		}
-
-		// connect method
-		conn, err := gnet.DialTcpByProxy("http://"+addr, fmt.Sprintf("127.0.0.1:%d", consts.TEST_TCP_FRP_PORT))
-		if assert.NoError(err) {
-			res, err := util.SendTCPMsgByConn(conn, consts.TEST_TCP_ECHO_STR)
-			assert.NoError(err)
-			assert.Equal(consts.TEST_TCP_ECHO_STR, res)
-		}
-	}
-}
-
-func TestRangePortsMapping(t *testing.T) {
-	assert := assert.New(t)
-
-	for i := 0; i < 3; i++ {
-		name := fmt.Sprintf("%s_%d", consts.ProxyRangeTCPPrefix, i)
-		status, err := util.GetProxyStatus(consts.ADMIN_ADDR, consts.ADMIN_USER, consts.ADMIN_PWD, name)
-		if assert.NoError(err) {
-			assert.Equal(proxy.ProxyPhaseRunning, status.Status)
-		}
-	}
-}
-
-func TestGroup(t *testing.T) {
-	assert := assert.New(t)
-
-	var (
-		p1 int
-		p2 int
-	)
-	addr := fmt.Sprintf("127.0.0.1:%d", consts.TEST_TCP2_FRP_PORT)
-
-	for i := 0; i < 6; i++ {
-		res, err := util.SendTCPMsg(addr, consts.TEST_TCP_ECHO_STR)
-		assert.NoError(err)
-		switch res {
-		case consts.TEST_TCP_ECHO_STR:
-			p1++
-		case consts.TEST_TCP_ECHO_STR + consts.TEST_TCP_ECHO_STR:
-			p2++
-		}
-	}
-	assert.True(p1 > 0 && p2 > 0, "group proxies load balancing")
-}

+ 0 - 115
tests/ci/reconnect_test.go

@@ -1,115 +0,0 @@
-package ci
-
-import (
-	"os"
-	"testing"
-	"time"
-
-	"github.com/fatedier/frp/tests/config"
-	"github.com/fatedier/frp/tests/consts"
-	"github.com/fatedier/frp/tests/util"
-
-	"github.com/stretchr/testify/assert"
-)
-
-const FRPS_RECONNECT_CONF = `
-[common]
-bind_addr = 0.0.0.0
-bind_port = 20000
-log_file = console
-log_level = debug
-token = 123456
-`
-
-const FRPC_RECONNECT_CONF = `
-[common]
-server_addr = 127.0.0.1
-server_port = 20000
-log_file = console
-log_level = debug
-token = 123456
-admin_port = 21000
-admin_user = abc
-admin_pwd = abc
-
-[tcp]
-type = tcp
-local_port = 10701
-remote_port = 20801
-`
-
-func TestReconnect(t *testing.T) {
-	assert := assert.New(t)
-	frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_RECONNECT_CONF)
-	if assert.NoError(err) {
-		defer os.Remove(frpsCfgPath)
-	}
-
-	frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_RECONNECT_CONF)
-	if assert.NoError(err) {
-		defer os.Remove(frpcCfgPath)
-	}
-
-	frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
-	err = frpsProcess.Start()
-	if assert.NoError(err) {
-		defer frpsProcess.Stop()
-	}
-
-	time.Sleep(500 * time.Millisecond)
-
-	frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
-	err = frpcProcess.Start()
-	if assert.NoError(err) {
-		defer frpcProcess.Stop()
-	}
-	time.Sleep(500 * time.Millisecond)
-
-	// test tcp
-	res, err := util.SendTCPMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
-	assert.NoError(err)
-	assert.Equal(consts.TEST_TCP_ECHO_STR, res)
-
-	// stop frpc
-	frpcProcess.Stop()
-	time.Sleep(200 * time.Millisecond)
-
-	// test tcp, expect failed
-	_, err = util.SendTCPMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
-	assert.Error(err)
-
-	// restart frpc
-	newFrpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
-	err = newFrpcProcess.Start()
-	if assert.NoError(err) {
-		defer newFrpcProcess.Stop()
-	}
-	time.Sleep(500 * time.Millisecond)
-
-	// test tcp
-	res, err = util.SendTCPMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
-	assert.NoError(err)
-	assert.Equal(consts.TEST_TCP_ECHO_STR, res)
-
-	// stop frps
-	frpsProcess.Stop()
-	time.Sleep(200 * time.Millisecond)
-
-	// test tcp, expect failed
-	_, err = util.SendTCPMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
-	assert.Error(err)
-
-	// restart frps
-	newFrpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
-	err = newFrpsProcess.Start()
-	if assert.NoError(err) {
-		defer newFrpsProcess.Stop()
-	}
-
-	time.Sleep(2 * time.Second)
-
-	// test tcp
-	res, err = util.SendTCPMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
-	assert.NoError(err)
-	assert.Equal(consts.TEST_TCP_ECHO_STR, res)
-}

+ 0 - 150
tests/ci/reload_test.go

@@ -1,150 +0,0 @@
-package ci
-
-import (
-	"os"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/assert"
-
-	"github.com/fatedier/frp/tests/config"
-	"github.com/fatedier/frp/tests/consts"
-	"github.com/fatedier/frp/tests/util"
-)
-
-const FRPS_RELOAD_CONF = `
-[common]
-bind_addr = 0.0.0.0
-bind_port = 20000
-log_file = console
-# debug, info, warn, error
-log_level = debug
-token = 123456
-`
-
-const FRPC_RELOAD_CONF_1 = `
-[common]
-server_addr = 127.0.0.1
-server_port = 20000
-log_file = console
-# debug, info, warn, error
-log_level = debug
-token = 123456
-admin_port = 21000
-admin_user = abc
-admin_pwd = abc
-
-[tcp]
-type = tcp
-local_port = 10701
-remote_port = 20801
-
-# change remote port
-[tcp2]
-type = tcp
-local_port = 10701
-remote_port = 20802
-
-# delete
-[tcp3]
-type = tcp
-local_port = 10701
-remote_port = 20803
-`
-
-const FRPC_RELOAD_CONF_2 = `
-[common]
-server_addr = 127.0.0.1
-server_port = 20000
-log_file = console
-# debug, info, warn, error
-log_level = debug
-token = 123456
-admin_port = 21000
-admin_user = abc
-admin_pwd = abc
-
-[tcp]
-type = tcp
-local_port = 10701
-remote_port = 20801
-
-[tcp2]
-type = tcp
-local_port = 10701
-remote_port = 20902
-`
-
-func TestReload(t *testing.T) {
-	assert := assert.New(t)
-	frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_RELOAD_CONF)
-	if assert.NoError(err) {
-		defer os.Remove(frpsCfgPath)
-	}
-
-	frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_RELOAD_CONF_1)
-	if assert.NoError(err) {
-		rmFile1 := frpcCfgPath
-		defer os.Remove(rmFile1)
-	}
-
-	frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
-	err = frpsProcess.Start()
-	if assert.NoError(err) {
-		defer frpsProcess.Stop()
-	}
-
-	time.Sleep(500 * time.Millisecond)
-
-	frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
-	err = frpcProcess.Start()
-	if assert.NoError(err) {
-		defer frpcProcess.Stop()
-	}
-
-	time.Sleep(500 * time.Millisecond)
-
-	// test tcp1
-	res, err := util.SendTCPMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
-	assert.NoError(err)
-	assert.Equal(consts.TEST_TCP_ECHO_STR, res)
-
-	// test tcp2
-	res, err = util.SendTCPMsg("127.0.0.1:20802", consts.TEST_TCP_ECHO_STR)
-	assert.NoError(err)
-	assert.Equal(consts.TEST_TCP_ECHO_STR, res)
-
-	// test tcp3
-	res, err = util.SendTCPMsg("127.0.0.1:20803", consts.TEST_TCP_ECHO_STR)
-	assert.NoError(err)
-	assert.Equal(consts.TEST_TCP_ECHO_STR, res)
-
-	// reload frpc config
-	frpcCfgPath, err = config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_RELOAD_CONF_2)
-	if assert.NoError(err) {
-		rmFile2 := frpcCfgPath
-		defer os.Remove(rmFile2)
-	}
-	err = util.ReloadConf("127.0.0.1:21000", "abc", "abc")
-	assert.NoError(err)
-
-	time.Sleep(time.Second)
-
-	// test tcp1
-	res, err = util.SendTCPMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
-	assert.NoError(err)
-	assert.Equal(consts.TEST_TCP_ECHO_STR, res)
-
-	// test origin tcp2, expect failed
-	res, err = util.SendTCPMsg("127.0.0.1:20802", consts.TEST_TCP_ECHO_STR)
-	assert.Error(err)
-
-	// test new origin tcp2 with different port
-	res, err = util.SendTCPMsg("127.0.0.1:20902", consts.TEST_TCP_ECHO_STR)
-	assert.NoError(err)
-	assert.Equal(consts.TEST_TCP_ECHO_STR, res)
-
-	// test tcp3, expect failed
-	res, err = util.SendTCPMsg("127.0.0.1:20803", consts.TEST_TCP_ECHO_STR)
-	assert.Error(err)
-}

+ 0 - 72
tests/ci/template_test.go

@@ -1,72 +0,0 @@
-package ci
-
-import (
-	"os"
-	"testing"
-	"time"
-
-	"github.com/fatedier/frp/tests/config"
-	"github.com/fatedier/frp/tests/consts"
-	"github.com/fatedier/frp/tests/util"
-
-	"github.com/stretchr/testify/assert"
-)
-
-const FRPS_TEMPLATE_CONF = `
-[common]
-bind_addr = 0.0.0.0
-bind_port = {{ .Envs.SERVER_PORT }}
-log_file = console
-# debug, info, warn, error
-log_level = debug
-token = 123456
-`
-
-const FRPC_TEMPLATE_CONF = `
-[common]
-server_addr = 127.0.0.1
-server_port = 20000
-log_file = console
-# debug, info, warn, error
-log_level = debug
-token = {{ .Envs.FRP_TOKEN }}
-
-[tcp]
-type = tcp
-local_port = 10701
-remote_port = {{ .Envs.TCP_REMOTE_PORT }}
-`
-
-func TestConfTemplate(t *testing.T) {
-	assert := assert.New(t)
-	frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TEMPLATE_CONF)
-	if assert.NoError(err) {
-		defer os.Remove(frpsCfgPath)
-	}
-
-	frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TEMPLATE_CONF)
-	if assert.NoError(err) {
-		defer os.Remove(frpcCfgPath)
-	}
-
-	frpsProcess := util.NewProcess("env", []string{"SERVER_PORT=20000", consts.FRPS_BIN_PATH, "-c", frpsCfgPath})
-	err = frpsProcess.Start()
-	if assert.NoError(err) {
-		defer frpsProcess.Stop()
-	}
-
-	time.Sleep(500 * time.Millisecond)
-
-	frpcProcess := util.NewProcess("env", []string{"FRP_TOKEN=123456", "TCP_REMOTE_PORT=20801", consts.FRPC_BIN_PATH, "-c", frpcCfgPath})
-	err = frpcProcess.Start()
-	if assert.NoError(err) {
-		defer frpcProcess.Stop()
-	}
-
-	time.Sleep(500 * time.Millisecond)
-
-	// test tcp1
-	res, err := util.SendTCPMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
-	assert.NoError(err)
-	assert.Equal(consts.TEST_TCP_ECHO_STR, res)
-}

+ 0 - 13
tests/config/config.go

@@ -1,13 +0,0 @@
-package config
-
-import (
-	"io/ioutil"
-	"os"
-	"path/filepath"
-)
-
-func GenerateConfigFile(path string, content string) (realPath string, err error) {
-	realPath = filepath.Join(os.TempDir(), path)
-	err = ioutil.WriteFile(realPath, []byte(content), 0666)
-	return realPath, err
-}

+ 0 - 76
tests/consts/consts.go

@@ -1,76 +0,0 @@
-package consts
-
-import "path/filepath"
-
-var (
-	FRPS_BIN_PATH = "../../bin/frps"
-	FRPC_BIN_PATH = "../../bin/frpc"
-
-	FRPS_SUB_BIN_PATH = "../../../bin/frps"
-	FRPC_SUB_BIN_PATH = "../../../bin/frpc"
-
-	FRPS_NORMAL_CONFIG = "./auto_test_frps.ini"
-	FRPC_NORMAL_CONFIG = "./auto_test_frpc.ini"
-
-	SERVER_ADDR = "127.0.0.1"
-	ADMIN_ADDR  = "127.0.0.1:10600"
-	ADMIN_USER  = "abc"
-	ADMIN_PWD   = "abc"
-
-	TEST_STR                    = "frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet."
-	TEST_TCP_PORT        int    = 10701
-	TEST_TCP2_PORT       int    = 10702
-	TEST_TCP_FRP_PORT    int    = 10801
-	TEST_TCP2_FRP_PORT   int    = 10802
-	TEST_TCP_EC_FRP_PORT int    = 10901
-	TEST_TCP_ECHO_STR    string = "tcp type:" + TEST_STR
-
-	TEST_UDP_PORT        int    = 10702
-	TEST_UDP_FRP_PORT    int    = 10802
-	TEST_UDP_EC_FRP_PORT int    = 10902
-	TEST_UDP_ECHO_STR    string = "udp type:" + TEST_STR
-
-	TEST_UNIX_DOMAIN_ADDR     string = "/tmp/frp_echo_server.sock"
-	TEST_UNIX_DOMAIN_FRP_PORT int    = 10803
-	TEST_UNIX_DOMAIN_STR      string = "unix domain type:" + TEST_STR
-
-	TEST_HTTP_PORT       int    = 10704
-	TEST_HTTP_FRP_PORT   int    = 10804
-	TEST_HTTP_NORMAL_STR string = "http normal string: " + TEST_STR
-	TEST_HTTP_FOO_STR    string = "http foo string: " + TEST_STR
-	TEST_HTTP_BAR_STR    string = "http bar string: " + TEST_STR
-
-	TEST_TCP_MUX_FRP_PORT int = 10806
-
-	TEST_STCP_FRP_PORT    int    = 10805
-	TEST_STCP_EC_FRP_PORT int    = 10905
-	TEST_STCP_ECHO_STR    string = "stcp type:" + TEST_STR
-
-	TEST_SUDP_FRP_PORT int    = 10816
-	TEST_SUDP_ECHO_STR string = "sudp type:" + TEST_STR
-
-	ProxyTCPPortNotAllowed  string = "tcp_port_not_allowed"
-	ProxyTCPPortUnavailable string = "tcp_port_unavailable"
-	ProxyTCPPortNormal      string = "tcp_port_normal"
-	ProxyTCPRandomPort      string = "tcp_random_port"
-	ProxyUDPPortNotAllowed  string = "udp_port_not_allowed"
-	ProxyUDPPortNormal      string = "udp_port_normal"
-	ProxyUDPRandomPort      string = "udp_random_port"
-	ProxyHTTPProxy          string = "http_proxy"
-
-	ProxyRangeTCPPrefix string = "range_tcp"
-)
-
-func init() {
-	if path, err := filepath.Abs(FRPS_BIN_PATH); err != nil {
-		panic(err)
-	} else {
-		FRPS_BIN_PATH = path
-	}
-
-	if path, err := filepath.Abs(FRPC_BIN_PATH); err != nil {
-		panic(err)
-	} else {
-		FRPC_BIN_PATH = path
-	}
-}

+ 0 - 120
tests/mock/echo_server.go

@@ -1,120 +0,0 @@
-package mock
-
-import (
-	"fmt"
-	"io"
-	"net"
-	"os"
-	"syscall"
-
-	frpNet "github.com/fatedier/frp/pkg/util/net"
-)
-
-type EchoServer struct {
-	l net.Listener
-
-	port        int
-	repeatedNum int
-	specifyStr  string
-}
-
-func NewEchoServer(port int, repeatedNum int, specifyStr string) *EchoServer {
-	if repeatedNum <= 0 {
-		repeatedNum = 1
-	}
-	return &EchoServer{
-		port:        port,
-		repeatedNum: repeatedNum,
-		specifyStr:  specifyStr,
-	}
-}
-
-func (es *EchoServer) Start() error {
-	l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", es.port))
-	if err != nil {
-		fmt.Printf("echo server listen error: %v\n", err)
-		return err
-	}
-	es.l = l
-
-	go func() {
-		for {
-			c, err := l.Accept()
-			if err != nil {
-				return
-			}
-
-			go echoWorker(c, es.repeatedNum, es.specifyStr)
-		}
-	}()
-	return nil
-}
-
-func (es *EchoServer) Stop() {
-	es.l.Close()
-}
-
-func StartUDPEchoServer(port int) {
-	l, err := frpNet.ListenUDP("127.0.0.1", port)
-	if err != nil {
-		fmt.Printf("udp echo server listen error: %v\n", err)
-		return
-	}
-
-	for {
-		c, err := l.Accept()
-		if err != nil {
-			fmt.Printf("udp echo server accept error: %v\n", err)
-			return
-		}
-
-		go echoWorker(c, 1, "")
-	}
-}
-
-func StartUnixDomainServer(unixPath string) {
-	os.Remove(unixPath)
-	syscall.Umask(0)
-	l, err := net.Listen("unix", unixPath)
-	if err != nil {
-		fmt.Printf("unix domain server listen error: %v\n", err)
-		return
-	}
-
-	for {
-		c, err := l.Accept()
-		if err != nil {
-			fmt.Printf("unix domain server accept error: %v\n", err)
-			return
-		}
-
-		go echoWorker(c, 1, "")
-	}
-}
-
-func echoWorker(c net.Conn, repeatedNum int, specifyStr string) {
-	buf := make([]byte, 2048)
-
-	for {
-		n, err := c.Read(buf)
-		if err != nil {
-			if err == io.EOF {
-				c.Close()
-				break
-			} else {
-				fmt.Printf("echo server read error: %v\n", err)
-				return
-			}
-		}
-
-		if specifyStr != "" {
-			c.Write([]byte(specifyStr))
-		} else {
-			var w []byte
-			for i := 0; i < repeatedNum; i++ {
-				w = append(w, buf[:n]...)
-			}
-			c.Write(w)
-		}
-	}
-}

+ 0 - 110
tests/mock/http_server.go

@@ -1,110 +0,0 @@
-package mock
-
-import (
-	"fmt"
-	"log"
-	"net"
-	"net/http"
-	"regexp"
-	"strings"
-
-	"github.com/fatedier/frp/tests/consts"
-
-	"github.com/gorilla/websocket"
-)
-
-type HTTPServer struct {
-	l net.Listener
-
-	port    int
-	handler http.HandlerFunc
-}
-
-func NewHTTPServer(port int, handler http.HandlerFunc) *HTTPServer {
-	return &HTTPServer{
-		port:    port,
-		handler: handler,
-	}
-}
-
-func (hs *HTTPServer) Start() error {
-	l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", hs.port))
-	if err != nil {
-		fmt.Printf("http server listen error: %v\n", err)
-		return err
-	}
-	hs.l = l
-
-	go http.Serve(l, http.HandlerFunc(hs.handler))
-	return nil
-}
-
-func (hs *HTTPServer) Stop() {
-	hs.l.Close()
-}
-
-var upgrader = websocket.Upgrader{}
-
-func StartHTTPServer(port int) {
-	http.HandleFunc("/", handleHTTP)
-	http.HandleFunc("/ws", handleWebSocket)
-	http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), nil)
-}
-
-func handleWebSocket(w http.ResponseWriter, r *http.Request) {
-	c, err := upgrader.Upgrade(w, r, nil)
-	if err != nil {
-		log.Print("upgrade:", err)
-		return
-	}
-	defer c.Close()
-	for {
-		mt, message, err := c.ReadMessage()
-		if err != nil {
-			break
-		}
-		err = c.WriteMessage(mt, message)
-		if err != nil {
-			log.Println("write:", err)
-			break
-		}
-	}
-}
-
-func handleHTTP(w http.ResponseWriter, r *http.Request) {
-	if r.Header.Get("X-From-Where") == "frp" {
-		w.Header().Set("X-Header-Set", "true")
-	}
-
-	match, err := regexp.Match(`.*\.sub\.com`, []byte(r.Host))
-	if err != nil {
-		w.WriteHeader(500)
-		return
-	}
-
-	if match {
-		w.WriteHeader(200)
-		w.Write([]byte(r.Host))
-		return
-	}
-
-	if strings.HasPrefix(r.Host, "127.0.0.1") || strings.HasPrefix(r.Host, "test2.frp.com") ||
-		strings.HasPrefix(r.Host, "test5.frp.com") || strings.HasPrefix(r.Host, "test6.frp.com") ||
-		strings.HasPrefix(r.Host, "test.frp1.com") || strings.HasPrefix(r.Host, "new.test.frp1.com") {
-
-		w.WriteHeader(200)
-		w.Write([]byte(consts.TEST_HTTP_NORMAL_STR))
-	} else if strings.Contains(r.Host, "test3.frp.com") {
-		w.WriteHeader(200)
-		if strings.Contains(r.URL.Path, "foo") {
-			w.Write([]byte(consts.TEST_HTTP_FOO_STR))
-		} else if strings.Contains(r.URL.Path, "bar") {
-			w.Write([]byte(consts.TEST_HTTP_BAR_STR))
-		} else {
-			w.Write([]byte(consts.TEST_HTTP_NORMAL_STR))
-		}
-	} else {
-		w.WriteHeader(404)
-	}
-	return
-}

+ 0 - 47
tests/util/process.go

@@ -1,47 +0,0 @@
-package util
-
-import (
-	"bytes"
-	"context"
-	"os/exec"
-)
-
-type Process struct {
-	cmd         *exec.Cmd
-	cancel      context.CancelFunc
-	errorOutput *bytes.Buffer
-
-	beforeStopHandler func()
-}
-
-func NewProcess(path string, params []string) *Process {
-	ctx, cancel := context.WithCancel(context.Background())
-	cmd := exec.CommandContext(ctx, path, params...)
-	p := &Process{
-		cmd:    cmd,
-		cancel: cancel,
-	}
-	p.errorOutput = bytes.NewBufferString("")
-	cmd.Stderr = p.errorOutput
-	return p
-}
-
-func (p *Process) Start() error {
-	return p.cmd.Start()
-}
-
-func (p *Process) Stop() error {
-	if p.beforeStopHandler != nil {
-		p.beforeStopHandler()
-	}
-	p.cancel()
-	return p.cmd.Wait()
-}
-
-func (p *Process) ErrorOutput() string {
-	return p.errorOutput.String()
-}
-
-func (p *Process) SetBeforeStopHandler(fn func()) {
-	p.beforeStopHandler = fn
-}

+ 0 - 210
tests/util/util.go

@@ -1,210 +0,0 @@
-package util
-
-import (
-	"encoding/base64"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"net"
-	"net/http"
-	"net/url"
-	"strings"
-	"time"
-
-	"github.com/fatedier/frp/client"
-)
-
-func GetProxyStatus(statusAddr string, user string, passwd string, name string) (status *client.ProxyStatusResp, err error) {
-	req, err := http.NewRequest("GET", "http://"+statusAddr+"/api/status", nil)
-	if err != nil {
-		return status, err
-	}
-
-	authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(user+":"+passwd))
-	req.Header.Add("Authorization", authStr)
-	resp, err := http.DefaultClient.Do(req)
-	if err != nil {
-		return status, err
-	}
-	defer resp.Body.Close()
-	if resp.StatusCode != 200 {
-		return status, fmt.Errorf("admin api status code [%d]", resp.StatusCode)
-	}
-	body, err := ioutil.ReadAll(resp.Body)
-	if err != nil {
-		return status, err
-	}
-	allStatus := &client.StatusResp{}
-	err = json.Unmarshal(body, &allStatus)
-	if err != nil {
-		return status, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
-	}
-	for _, s := range allStatus.TCP {
-		if s.Name == name {
-			return &s, nil
-		}
-	}
-	for _, s := range allStatus.UDP {
-		if s.Name == name {
-			return &s, nil
-		}
-	}
-	for _, s := range allStatus.HTTP {
-		if s.Name == name {
-			return &s, nil
-		}
-	}
-	for _, s := range allStatus.HTTPS {
-		if s.Name == name {
-			return &s, nil
-		}
-	}
-	for _, s := range allStatus.STCP {
-		if s.Name == name {
-			return &s, nil
-		}
-	}
-	for _, s := range allStatus.XTCP {
-		if s.Name == name {
-			return &s, nil
-		}
-	}
-	for _, s := range allStatus.SUDP {
-		if s.Name == name {
-			return &s, nil
-		}
-	}
-
-	return status, errors.New("no proxy status found")
-}
-
-func ReloadConf(reloadAddr string, user string, passwd string) error {
-	req, err := http.NewRequest("GET", "http://"+reloadAddr+"/api/reload", nil)
-	if err != nil {
-		return err
-	}
-
-	authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(user+":"+passwd))
-	req.Header.Add("Authorization", authStr)
-	resp, err := http.DefaultClient.Do(req)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != 200 {
-		return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
-	}
-	io.Copy(ioutil.Discard, resp.Body)
-	return nil
-}
-
-func SendTCPMsg(addr string, msg string) (res string, err error) {
-	c, err := net.Dial("tcp", addr)
-	if err != nil {
-		err = fmt.Errorf("connect to tcp server error: %v", err)
-		return
-	}
-	defer c.Close()
-	return SendTCPMsgByConn(c, msg)
-}
-
-func SendTCPMsgByConn(c net.Conn, msg string) (res string, err error) {
-	timer := time.Now().Add(5 * time.Second)
-	c.SetDeadline(timer)
-	c.Write([]byte(msg))
-
-	buf := make([]byte, 2048)
-	n, errRet := c.Read(buf)
-	if errRet != nil {
-		err = fmt.Errorf("read from tcp server error: %v", errRet)
-		return
-	}
-	return string(buf[:n]), nil
-}
-
-func SendUDPMsg(addr string, msg string) (res string, err error) {
-	udpAddr, errRet := net.ResolveUDPAddr("udp", addr)
-	if errRet != nil {
-		err = fmt.Errorf("resolve udp addr error: %v", err)
-		return
-	}
-	conn, errRet := net.DialUDP("udp", nil, udpAddr)
-	if errRet != nil {
-		err = fmt.Errorf("dial udp server error: %v", err)
-		return
-	}
-	defer conn.Close()
-	_, err = conn.Write([]byte(msg))
-	if err != nil {
-		err = fmt.Errorf("write to udp server error: %v", err)
-		return
-	}
-
-	conn.SetReadDeadline(time.Now().Add(10 * time.Second))
-	buf := make([]byte, 2048)
-	n, errRet := conn.Read(buf)
-	if errRet != nil {
-		err = fmt.Errorf("read from udp server error: %v", err)
-		return
-	}
-	return string(buf[:n]), nil
-}
-
-func SendHTTPMsg(method, urlStr string, host string, headers map[string]string, proxy string) (code int, body string, header http.Header, err error) {
-	req, errRet := http.NewRequest(method, urlStr, nil)
-	if errRet != nil {
-		err = errRet
-		return
-	}
-
-	if host != "" {
-		req.Host = host
-	}
-	for k, v := range headers {
-		req.Header.Set(k, v)
-	}
-
-	tr := &http.Transport{
-		DialContext: (&net.Dialer{
-			Timeout:   30 * time.Second,
-			KeepAlive: 30 * time.Second,
-			DualStack: true,
-		}).DialContext,
-		MaxIdleConns:          100,
-		IdleConnTimeout:       90 * time.Second,
-		TLSHandshakeTimeout:   10 * time.Second,
-		ExpectContinueTimeout: 1 * time.Second,
-	}
-
-	if len(proxy) != 0 {
-		tr.Proxy = func(req *http.Request) (*url.URL, error) {
-			return url.Parse(proxy)
-		}
-	}
-	client := http.Client{
-		Transport: tr,
-	}
-
-	resp, errRet := client.Do(req)
-	if errRet != nil {
-		err = errRet
-		return
-	}
-	code = resp.StatusCode
-	header = resp.Header
-	buf, errRet := ioutil.ReadAll(resp.Body)
-	if errRet != nil {
-		err = errRet
-		return
-	}
-	body = string(buf)
-	return
-}
-
-func BasicAuth(username, passwd string) string {
-	auth := username + ":" + passwd
-	return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
-}