SongZihuan 3 mesiacov pred
rodič
commit
1a4e13ad5d
100 zmenil súbory, kde vykonal 6225 pridanie a 0 odobranie
  1. 1 0
      .gitattributes
  2. 41 0
      .github/ISSUE_TEMPLATE/1-bug.md
  3. 15 0
      .github/ISSUE_TEMPLATE/2-feature.md
  4. 7 0
      .github/ISSUE_TEMPLATE/3-thank.md
  5. 1 0
      .github/ISSUE_TEMPLATE/config.yaml
  6. 14 0
      .github/pull-request-template.md
  7. 14 0
      .gitignore
  8. 8 0
      LICENSE
  9. 21 0
      LICENSE.gin
  10. 4 0
      README.md
  11. 13 0
      REEPORT
  12. 1 0
      VERSION
  13. 1 0
      VIA
  14. 31 0
      go.mod
  15. 126 0
      go.sum
  16. 17 0
      resource.go
  17. 170 0
      src/certssl/account/data.go
  18. 20 0
      src/certssl/account/register.go
  19. 92 0
      src/certssl/applycert/main.go
  20. 76 0
      src/certssl/applycert/read.go
  21. 97 0
      src/certssl/applycert/write.go
  22. 9 0
      src/certssl/filename/filename.go
  23. 131 0
      src/certssl/main.go
  24. 10 0
      src/cmd/version1/main.go
  25. 303 0
      src/config/config.go
  26. 83 0
      src/config/configerr/error.go
  27. 100 0
      src/config/configwatcher/watcher.go
  28. 89 0
      src/config/globalconfig.go
  29. 33 0
      src/config/httpconfig.go
  30. 69 0
      src/config/httpsconfig.go
  31. 69 0
      src/config/main.go
  32. 98 0
      src/config/rules/action/api/api.go
  33. 40 0
      src/config/rules/action/api/headerconfig.go
  34. 35 0
      src/config/rules/action/api/headerdelconfig.go
  35. 30 0
      src/config/rules/action/api/protectheader.go
  36. 32 0
      src/config/rules/action/api/queryconfig.go
  37. 27 0
      src/config/rules/action/api/querydelconfig.go
  38. 1 0
      src/config/rules/action/cors/README
  39. 33 0
      src/config/rules/action/cors/corsconfig.go
  40. 80 0
      src/config/rules/action/dir/dirconfig.go
  41. 23 0
      src/config/rules/action/dir/ignorefileconfig.go
  42. 23 0
      src/config/rules/action/dir/indexfileconfig.go
  43. 33 0
      src/config/rules/action/file/fileconfig.go
  44. 41 0
      src/config/rules/action/redirect/redirectconfig.go
  45. 1 0
      src/config/rules/action/remotetrust/README
  46. 31 0
      src/config/rules/action/remotetrust/remotetrustconfig.go
  47. 1 0
      src/config/rules/action/rewrite/README
  48. 20 0
      src/config/rules/action/rewrite/rewriteconfig.go
  49. 1 0
      src/config/rules/match/README
  50. 33 0
      src/config/rules/match/matchconfig.go
  51. 83 0
      src/config/rules/ruleconfig.go
  52. 26 0
      src/config/rules/rulelistconfig.go
  53. 110 0
      src/config/rulescompile/actioncompile/apicompile/api.go
  54. 15 0
      src/config/rulescompile/actioncompile/apicompile/headerconfig.go
  55. 13 0
      src/config/rulescompile/actioncompile/apicompile/headerdelconfig.go
  56. 15 0
      src/config/rulescompile/actioncompile/apicompile/queryconfig.go
  57. 13 0
      src/config/rulescompile/actioncompile/apicompile/querydelconfig.go
  58. 77 0
      src/config/rulescompile/actioncompile/corscompile/corsconfig.go
  59. 65 0
      src/config/rulescompile/actioncompile/dircompile/dirconfig.go
  60. 32 0
      src/config/rulescompile/actioncompile/dircompile/ignorefileconfig.go
  61. 40 0
      src/config/rulescompile/actioncompile/dircompile/indexfileconfig.go
  62. 23 0
      src/config/rulescompile/actioncompile/filecompile/fileconfig.go
  63. 33 0
      src/config/rulescompile/actioncompile/redirectcompile/redirectcompile.go
  64. 24 0
      src/config/rulescompile/actioncompile/remotetrustcompile/remotetrustcompileconfig.go
  65. 33 0
      src/config/rulescompile/actioncompile/rewritecompile/rewriteconfig.go
  66. 61 0
      src/config/rulescompile/matchcompile/matchconfig.go
  67. 114 0
      src/config/rulescompile/ruleconfig.go
  68. 22 0
      src/config/rulescompile/rulelistconfig.go
  69. 19 0
      src/config/signalconfig.go
  70. 58 0
      src/config/yamlconfig.go
  71. 370 0
      src/flagparser/data.go
  72. 29 0
      src/flagparser/error.go
  73. 82 0
      src/flagparser/flag.go
  74. 111 0
      src/flagparser/main.go
  75. 373 0
      src/logger/logger.go
  76. 264 0
      src/logger/main.go
  77. 99 0
      src/mainfunc/v1.go
  78. 89 0
      src/server/context/context.go
  79. 44 0
      src/server/core/abort.go
  80. 217 0
      src/server/core/api.go
  81. 36 0
      src/server/core/cors.go
  82. 7 0
      src/server/core/default.go
  83. 178 0
      src/server/core/dir.go
  84. 41 0
      src/server/core/file.go
  85. 49 0
      src/server/core/hptag.go
  86. 29 0
      src/server/core/match.go
  87. 51 0
      src/server/core/normalserver.go
  88. 51 0
      src/server/core/proxytrust.go
  89. 26 0
      src/server/core/redirect.go
  90. 47 0
      src/server/core/server.go
  91. 55 0
      src/server/httpserver/server.go
  92. 166 0
      src/server/httpsserver/server.go
  93. 57 0
      src/server/main.go
  94. 199 0
      src/server/middleware/loggerserver/loggerserver.go
  95. 99 0
      src/server/request/proxyrequest/request.go
  96. 62 0
      src/server/request/readonlyrequest/request.go
  97. 154 0
      src/server/responsewriter/writer.go
  98. 17 0
      src/utils/cidr.go
  99. 53 0
      src/utils/cmd.go
  100. 45 0
      src/utils/exit.go

+ 1 - 0
.gitattributes

@@ -0,0 +1 @@
+* text=auto eol=lf

+ 41 - 0
.github/ISSUE_TEMPLATE/1-bug.md

@@ -0,0 +1,41 @@
+---
+name: 遇到错误或反常情况
+about: 如果你遇到Bug(错误、不合理的情况)请通过此Issue反馈。
+---
+## Bug提交
+### 问题
+
+请简单概况问题。
+
+### 发生阶段
+
+* 运行(`go run`)
+* 构建(`go build`)
+* 构建后运行
+* 其他(请详细描述)
+
+(请在上述类型中选择或自行补充)
+
+### 你再做什么
+
+请详细说明你在做什么。
+
+### 项目运行的情况
+
+项目运行的情况是什么?
+
+例如:调用了某个API,数据库的写入或读取。
+
+### 发生了什么事情
+
+发生了什么错误?
+
+并且请把日志(若有)、命令行输出一并告诉我们。
+
+### 正确到结果应该是
+
+你希望正常的结果应该是什么?
+
+### 其他补充
+
+你的其他补充。

+ 15 - 0
.github/ISSUE_TEMPLATE/2-feature.md

@@ -0,0 +1,15 @@
+---
+name: 新功能请求
+about: 如果你有新功能请求,欢迎你告知我们。
+---
+## 新功能请求
+
+请简单概述你的功能请求。
+
+## 现状
+
+关于此功能的现状如何?
+
+* 我有想法,但我没办法自己实现
+* 我有想法,并且我计划自己实现并提交PR
+* 我已经提交PR

+ 7 - 0
.github/ISSUE_TEMPLATE/3-thank.md

@@ -0,0 +1,7 @@
+---
+name: 分享喜悦
+about: 如果我们的项目对你有帮助,或者你想分享一些喜悦(和项目有关的)欢迎使用此模板。
+---
+## 分享喜悦
+
+请简单叙述发生的事情,并为我们留言吧。

+ 1 - 0
.github/ISSUE_TEMPLATE/config.yaml

@@ -0,0 +1 @@
+blank_issues_enabled: true

+ 14 - 0
.github/pull-request-template.md

@@ -0,0 +1,14 @@
+<!-- 请务必在创建PR前,在右侧 Labels 选项中加上label的其中一个: [feature]、[fix]、[documentation] 。以便于Actions自动生成Releases时自动对PR进行归类。-->
+
+**在提出此拉取请求时,我确认了以下几点(请复选框):**
+
+- [ ] 我已阅读并理解[LICENSE](LICENSE),并自愿成为本项目的贡献者。
+- [ ] 我已检查没有与此请求重复的拉取请求。
+- [ ] 我已经考虑过,并确认这份呈件对其他人很有价值。
+- [ ] 我接受此提交可能不会被使用,并根据维护人员的意愿关闭拉取请求。
+
+**填写PR内容:**
+
+-
+-
+-

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+.idea
+etc
+tmp
+cert
+
+*.exe
+*.out
+
+.DS_Store
+
+testdata
+remote-testdata
+
+pkg

+ 8 - 0
LICENSE

@@ -0,0 +1,8 @@
+The MIT License (MIT)
+Copyright © 2024 宋子桓(Song Zihuan)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 21 - 0
LICENSE.gin

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Manuel Martínez-Almeida
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 4 - 0
README.md

@@ -0,0 +1,4 @@
+# 桓代理(HuanProxy)
+
+## 简介
+HuanProxy:简单的反向代理和静态文件托管服务器,支持SSL证书申请和续订

+ 13 - 0
REEPORT

@@ -0,0 +1,13 @@
+How to report of Huan-Proxy
+
+Author: 宋子桓(Song Zihuan)
+Author Github: https://github.com/SongZihuan
+Author Website: https://song-zh.com
+Author Email: songzihuan@song-zh.com
+
+Github: https://github.com/SongZihuan/huan-proxy
+Github Issues: https://github.com/SongZihuan/huan-proxy/issues
+
+Report: You can report issues and contact the author through Github Issues or Author Email.
+Quality Assurance: If you only have the license in the LICENSE file in the root directory of this project, you will not get any quality assurance. But the author is happy to solve the problem for you, unless this project has been archived as read-only.
+Other Fork versions: Please contact the author of the Fork version for assistance.

+ 1 - 0
VERSION

@@ -0,0 +1 @@
+v1.0.0

+ 1 - 0
VIA

@@ -0,0 +1 @@
+Huan-Proxy

+ 31 - 0
go.mod

@@ -0,0 +1,31 @@
+module github.com/SongZihuan/huan-proxy
+
+go 1.22.0
+
+require (
+	github.com/fsnotify/fsnotify v1.8.0
+	github.com/gabriel-vasile/mimetype v1.4.7
+	github.com/go-acme/lego/v4 v4.21.0
+	github.com/mattn/go-isatty v0.0.20
+	gopkg.in/yaml.v3 v3.0.1
+)
+
+require (
+	github.com/aliyun/alibaba-cloud-sdk-go v1.63.72 // indirect
+	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+	github.com/go-jose/go-jose/v4 v4.0.4 // indirect
+	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/miekg/dns v1.1.62 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
+	golang.org/x/crypto v0.31.0 // indirect
+	golang.org/x/mod v0.22.0 // indirect
+	golang.org/x/net v0.33.0 // indirect
+	golang.org/x/sync v0.10.0 // indirect
+	golang.org/x/sys v0.28.0 // indirect
+	golang.org/x/text v0.21.0 // indirect
+	golang.org/x/tools v0.28.0 // indirect
+	gopkg.in/ini.v1 v1.67.0 // indirect
+)

+ 126 - 0
go.sum

@@ -0,0 +1,126 @@
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
+github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
+github.com/aliyun/alibaba-cloud-sdk-go v1.63.72 h1:HvFZUzEbNvfe8F2Mg0wBGv90bPhWDxgVtDHR5zoBOU0=
+github.com/aliyun/alibaba-cloud-sdk-go v1.63.72/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
+github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
+github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
+github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
+github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
+github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
+github.com/go-acme/lego/v4 v4.21.0 h1:arEW+8o5p7VI8Bk1kr/PDlgD1DrxtTH1gJ4b7mehL8o=
+github.com/go-acme/lego/v4 v4.21.0/go.mod h1:HrSWzm3Ckj45Ie3i+p1zKVobbQoMOaGu9m4up0dUeDI=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
+github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
+github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
+github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
+github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
+github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
+github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
+github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
+golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
+golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
+golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
+golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
+gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
+gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
+gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v2 v2.2.8/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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

+ 17 - 0
resource.go

@@ -0,0 +1,17 @@
+package resource
+
+import (
+	_ "embed"
+)
+
+//go:embed VERSION
+var Version string
+
+//go:embed LICENSE
+var License string
+
+//go:embed REEPORT
+var Report string
+
+//go:embed VIA
+var Via string

+ 170 - 0
src/certssl/account/data.go

@@ -0,0 +1,170 @@
+package account
+
+import (
+	"crypto"
+	"encoding/json"
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"github.com/go-acme/lego/v4/certcrypto"
+	"github.com/go-acme/lego/v4/lego"
+	"github.com/go-acme/lego/v4/registration"
+	"os"
+	"path"
+	"time"
+)
+
+const DefaultAccountExp = 24 * time.Hour
+const DefaultUserKeyType = certcrypto.RSA4096
+
+var ErrExpiredAccount = fmt.Errorf("account expired")
+var ErrNotValidAccount = fmt.Errorf("account not valid")
+var user *Account
+
+type Data struct {
+	Resource       *registration.Resource `json:"resource,omitempty"`
+	Email          string                 `json:"email,omitempty"`
+	RegisterTime   int64                  `json:"register-time,omitempty"`
+	ExpirationTime int64                  `json:"expiration-time,omitempty"`
+}
+
+// Account 不得包含指针
+type Account struct {
+	data        Data
+	key         crypto.PrivateKey
+	dir         string
+	accountpath string
+	keypath     string
+}
+
+func NewAccount(basedir string, email string) (*Account, error) {
+	dir := path.Join(basedir, "account", email)
+	err := os.MkdirAll(dir, 0775)
+	if err != nil {
+		return nil, fmt.Errorf("create account dir failed: %s", err.Error())
+	}
+
+	privateKey, err := certcrypto.GeneratePrivateKey(DefaultUserKeyType)
+	if err != nil {
+		return nil, fmt.Errorf("generate new user private key failed: %s", err.Error())
+	}
+
+	now := time.Now()
+	user = &Account{
+		data: Data{
+			Email:          email,
+			Resource:       nil,
+			RegisterTime:   now.Unix(),
+			ExpirationTime: now.Add(DefaultAccountExp).Unix(),
+		},
+		key:         privateKey,
+		dir:         dir,
+		accountpath: path.Join(dir, "account.json"),
+		keypath:     path.Join(dir, "account.key"),
+	}
+	return user, nil
+}
+
+func LoadAccount(basedir string, email string) (*Account, error) {
+	if user != nil {
+		return user, nil
+	}
+
+	dir := path.Join(basedir, "account", email)
+	accountpath := path.Join(dir, "account.json")
+	keypath := path.Join(dir, "account.key")
+
+	dataAccount, err := os.ReadFile(accountpath)
+	if err != nil {
+		return nil, fmt.Errorf("read account file failed: %s", err.Error())
+	}
+
+	var data Data
+	err = json.Unmarshal(dataAccount, &data)
+	if err != nil {
+		return nil, fmt.Errorf("load account error")
+	}
+
+	dataKey, err := os.ReadFile(keypath)
+	if err != nil {
+		return nil, fmt.Errorf("read account key file failed: %s", err.Error())
+	}
+
+	privateKey, err := utils.ReadPrivateKey(dataKey)
+	if err != nil {
+		return nil, fmt.Errorf("read account key failed: %s", err.Error())
+	}
+
+	if time.Now().After(time.Unix(data.ExpirationTime, 0)) {
+		return nil, ErrExpiredAccount
+	}
+
+	if data.Resource == nil || data.Resource.Body.Status != "valid" {
+		return nil, ErrNotValidAccount
+	}
+
+	user = &Account{
+		data:        data,
+		key:         privateKey,
+		dir:         dir,
+		accountpath: accountpath,
+		keypath:     keypath,
+	}
+	return user, nil
+}
+
+func (u *Account) GetEmail() string {
+	return u.data.Email
+}
+
+func (u *Account) GetRegistration() *registration.Resource {
+	return u.data.Resource
+}
+
+func (u *Account) GetPrivateKey() crypto.PrivateKey {
+	return u.key
+}
+
+func (u *Account) SaveAccount() error {
+	err := os.MkdirAll(u.dir, 0775)
+	if err != nil {
+		return fmt.Errorf("create account dir failed: %s", err.Error())
+	}
+
+	data, err := json.Marshal(u.data)
+	if err != nil {
+		return err
+	}
+
+	err = os.WriteFile(u.accountpath, data, 0644)
+	if err != nil {
+		return fmt.Errorf("failed to write account %s: %s", u.accountpath, err.Error())
+	}
+
+	privateKeyData, err := utils.EncodePrivateKeyToPEM(u.key)
+	if err != nil {
+		return fmt.Errorf("failed to read account private %s: %s", u.accountpath, err.Error())
+	}
+
+	err = os.WriteFile(u.keypath, privateKeyData, 0644)
+	if err != nil {
+		return fmt.Errorf("failed to write account %s: %s", u.keypath, err.Error())
+	}
+
+	return nil
+}
+
+func (u *Account) Register(client *lego.Client) (*registration.Resource, error) {
+	if u.data.Resource != nil {
+		return u.data.Resource, nil
+	}
+
+	res, err := register(client)
+	if err != nil {
+		return nil, fmt.Errorf("new account failed: %s", err.Error())
+	} else if res == nil {
+		return nil, fmt.Errorf("new account failed: register return nil, unknown error")
+	}
+
+	u.data.Resource = res
+	return u.data.Resource, nil
+}

+ 20 - 0
src/certssl/account/register.go

@@ -0,0 +1,20 @@
+package account
+
+import (
+	"fmt"
+	"github.com/go-acme/lego/v4/lego"
+	"github.com/go-acme/lego/v4/registration"
+)
+
+func register(client *lego.Client) (*registration.Resource, error) {
+	regOption := registration.RegisterOptions{
+		TermsOfServiceAgreed: true,
+	}
+
+	reg, err := client.Registration.Register(regOption)
+	if err != nil {
+		return nil, fmt.Errorf("register failed: %s", err.Error())
+	}
+
+	return reg, nil
+}

+ 92 - 0
src/certssl/applycert/main.go

@@ -0,0 +1,92 @@
+package applycert
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/certssl/account"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"github.com/go-acme/lego/v4/certcrypto"
+	"github.com/go-acme/lego/v4/certificate"
+	"github.com/go-acme/lego/v4/lego"
+	"github.com/go-acme/lego/v4/providers/dns/alidns"
+	"time"
+)
+
+const DefaultCertTimeout = 30 * 24 * time.Hour
+const DefaultCertType = certcrypto.RSA4096
+
+func ApplyCert(basedir string, email string, aliyunAccessKey string, aliyunAccessSecret string, domain string) (*certificate.Resource, error) {
+	if domain == "" || !utils.IsValidDomain(domain) {
+		return nil, fmt.Errorf("domain is invalid")
+	}
+
+	user, err := account.LoadAccount(basedir, email)
+	if err != nil {
+		fmt.Printf("load local account failed, register a new on for %s: %s\n", email, err.Error())
+		user, err = account.NewAccount(basedir, email)
+		if err != nil {
+			return nil, fmt.Errorf("generate new user failed: %s", err.Error())
+		}
+	}
+
+	config := lego.NewConfig(user)
+	config.Certificate.KeyType = DefaultCertType
+	config.Certificate.Timeout = DefaultCertTimeout
+	config.CADirURL = "https://acme-v02.api.letsencrypt.org/directory"
+	client, err := lego.NewClient(config)
+	if err != nil {
+		return nil, fmt.Errorf("new client failed: %s", err.Error())
+	}
+
+	aliyunDnsConfig := alidns.NewDefaultConfig()
+	aliyunDnsConfig.APIKey = aliyunAccessKey
+	aliyunDnsConfig.SecretKey = aliyunAccessSecret
+
+	provider, err := alidns.NewDNSProviderConfig(aliyunDnsConfig)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize AliDNS provider: %s", err.Error())
+	}
+
+	err = client.Challenge.SetDNS01Provider(provider)
+	if err != nil {
+		return nil, fmt.Errorf("set challenge dns1 provider failed: %s", err.Error())
+	}
+
+	reg, err := user.Register(client)
+	if err != nil {
+		return nil, fmt.Errorf("get account failed: %s", err.Error())
+	} else if reg == nil {
+		return nil, fmt.Errorf("get account failed: return nil account.resurce, unknown reason")
+	}
+
+	request := certificate.ObtainRequest{
+		Domains: []string{domain},
+		Bundle:  true,
+	}
+
+	resource, err := client.Certificate.Obtain(request)
+	if err != nil {
+		return nil, fmt.Errorf("obtain certificate failed: %s", err.Error())
+	}
+
+	err = user.SaveAccount()
+	if err != nil {
+		return nil, fmt.Errorf("save account error after obtain: %s", err.Error())
+	}
+
+	cert, err := utils.ReadCertificate(resource.Certificate)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read certificate: %s", err.Error())
+	}
+
+	err = writerWithDate(basedir, cert, resource)
+	if err != nil {
+		return nil, fmt.Errorf("writer certificate backup failed: %s", err.Error())
+	}
+
+	err = writer(basedir, cert, resource)
+	if err != nil {
+		return nil, fmt.Errorf("writer certificate failed: %s", err.Error())
+	}
+
+	return resource, nil
+}

+ 76 - 0
src/certssl/applycert/read.go

@@ -0,0 +1,76 @@
+package applycert
+
+import (
+	"crypto"
+	"crypto/x509"
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/certssl/filename"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"os"
+	"path"
+)
+
+func ReadLocalCertificateAndPrivateKey(basedir string, domain string) (crypto.PrivateKey, *x509.Certificate, *x509.Certificate, error) {
+	dir := path.Join(basedir, domain)
+	cert, err := readCertificate(dir)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("read certificate failed: %s", err.Error())
+	}
+
+	cacert, err := readCACertificate(dir)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("read certificate failed: %s", err.Error())
+	}
+
+	privateKey, err := readPrivateKey(dir)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("read private key failed: %s", err.Error())
+	}
+
+	return privateKey, cert, cacert, nil
+}
+
+func readCertificate(dir string) (*x509.Certificate, error) {
+	filepath := path.Join(dir, filename.FileCertificate)
+	data, err := os.ReadFile(filepath)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read certificate file: %v", err)
+	}
+
+	cert, err := utils.ReadCertificate(data)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parser certificate file: %v", err)
+	}
+
+	return cert, nil
+}
+
+func readCACertificate(dir string) (*x509.Certificate, error) {
+	filepath := path.Join(dir, filename.FileIssuerCertificate)
+	data, err := os.ReadFile(filepath)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read certificate file: %v", err)
+	}
+
+	cert, err := utils.ReadCertificate(data)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parser certificate file: %v", err)
+	}
+
+	return cert, nil
+}
+
+func readPrivateKey(dir string) (crypto.PrivateKey, error) {
+	filepath := path.Join(dir, filename.FilePrivateKey)
+	data, err := os.ReadFile(filepath)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read key file: %v", err)
+	}
+
+	privateKey, err := utils.ReadPrivateKey(data)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parser key file: %v", err)
+	}
+
+	return privateKey, nil
+}

+ 97 - 0
src/certssl/applycert/write.go

@@ -0,0 +1,97 @@
+package applycert
+
+import (
+	"crypto/x509"
+	"encoding/json"
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/certssl/filename"
+	"github.com/go-acme/lego/v4/certificate"
+	"os"
+	"path"
+)
+
+func writerWithDate(basedir string, cert *x509.Certificate, resource *certificate.Resource) error {
+	domain := cert.Subject.CommonName
+	if domain == "" && len(cert.DNSNames) == 0 {
+		return fmt.Errorf("no domains in certificate")
+	}
+	domain = cert.DNSNames[0]
+
+	year := fmt.Sprintf("%d", cert.NotBefore.Year())
+	month := fmt.Sprintf("%d", cert.NotBefore.Month())
+	day := fmt.Sprintf("%d", cert.NotBefore.Day())
+
+	backupdir := path.Join(basedir, "cert-backup", domain, year, month, day, cert.NotBefore.Format("2006-01-02-15:04:05"))
+	err := os.MkdirAll(backupdir, 0775)
+	if err != nil {
+		return err
+	}
+
+	err = os.WriteFile(path.Join(backupdir, filename.FilePrivateKey), resource.PrivateKey, os.ModePerm)
+	if err != nil {
+		return err
+	}
+
+	err = os.WriteFile(path.Join(backupdir, filename.FileCertificate), resource.Certificate, os.ModePerm)
+	if err != nil {
+		return err
+	}
+
+	err = os.WriteFile(path.Join(backupdir, filename.FileIssuerCertificate), resource.IssuerCertificate, os.ModePerm)
+	if err != nil {
+		return err
+	}
+
+	err = os.WriteFile(path.Join(backupdir, filename.FileCSR), resource.CSR, os.ModePerm)
+	if err != nil {
+		return err
+	}
+
+	data, err := json.Marshal(resource)
+	if err != nil {
+		return err
+	}
+
+	err = os.WriteFile(path.Join(backupdir, filename.FileResource), data, os.ModePerm)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func writer(basedir string, cert *x509.Certificate, resource *certificate.Resource) error {
+	domain := cert.Subject.CommonName
+	if domain == "" && len(cert.DNSNames) == 0 {
+		return fmt.Errorf("no domains in certificate")
+	}
+	domain = cert.DNSNames[0]
+
+	dir := path.Join(basedir, domain)
+	err := os.MkdirAll(dir, 0775)
+	if err != nil {
+		return fmt.Errorf("failed to create directory %s: %s", dir, err.Error())
+	}
+
+	err = os.WriteFile(path.Join(dir, filename.FilePrivateKey), resource.PrivateKey, os.ModePerm)
+	if err != nil {
+		return err
+	}
+
+	err = os.WriteFile(path.Join(dir, filename.FileCertificate), resource.Certificate, os.ModePerm)
+	if err != nil {
+		return err
+	}
+
+	err = os.WriteFile(path.Join(dir, filename.FileIssuerCertificate), resource.IssuerCertificate, os.ModePerm)
+	if err != nil {
+		return err
+	}
+
+	err = os.WriteFile(path.Join(dir, filename.FileCSR), resource.CSR, os.ModePerm)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

+ 9 - 0
src/certssl/filename/filename.go

@@ -0,0 +1,9 @@
+package filename
+
+const (
+	FilePrivateKey        = "private.key"
+	FileCertificate       = "cert.pem"
+	FileIssuerCertificate = "ca-cert.pem"
+	FileCSR               = "csr.pem"
+	FileResource          = "resource.json"
+)

+ 131 - 0
src/certssl/main.go

@@ -0,0 +1,131 @@
+package certssl
+
+import (
+	"crypto"
+	"crypto/x509"
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/certssl/applycert"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"time"
+)
+
+const CertDefaultNewApplyTime = 5 * 24 * time.Hour
+
+func GetCertificateAndPrivateKey(basedir string, email string, aliyunAccessKey string, aliyunAccessSecret string, domain string) (crypto.PrivateKey, *x509.Certificate, *x509.Certificate, error) {
+	if email == "" {
+		email = "no-reply@example.com"
+	}
+
+	if !utils.IsValidEmail(email) {
+		return nil, nil, nil, fmt.Errorf("not a valid email")
+	}
+
+	if !utils.IsValidDomain(domain) {
+		return nil, nil, nil, fmt.Errorf("not a valid domain")
+	}
+
+	privateKey, cert, cacert, err := applycert.ReadLocalCertificateAndPrivateKey(basedir, domain)
+	if err == nil && utils.CheckCertWithDomain(cert, domain) && utils.CheckCertWithTime(cert, 5*24*time.Hour) {
+		return privateKey, cert, cacert, nil
+	}
+
+	resource, err := applycert.ApplyCert(basedir, email, aliyunAccessKey, aliyunAccessSecret, domain)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("apply cert failed: %s", err.Error())
+	} else if resource == nil {
+		return nil, nil, nil, fmt.Errorf("read cert failed: private key or certificate (resource) is nil, unknown reason")
+	}
+
+	privateKey, err = utils.ReadPrivateKey(resource.PrivateKey)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("read private key failed: %s", err.Error())
+	}
+
+	cert, err = utils.ReadCertificate(resource.Certificate)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("read cert failed: %s", err.Error())
+	}
+
+	cacert, err = utils.ReadCertificate(resource.IssuerCertificate)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("read cert failed: %s", err.Error())
+	}
+
+	return privateKey, cert, cacert, nil
+}
+
+type NewCert struct {
+	PrivateKey        crypto.PrivateKey
+	Certificate       *x509.Certificate
+	IssuerCertificate *x509.Certificate
+	Error             error
+}
+
+func WatchCertificate(dir string, email string, aliyunAccessKey string, aliyunAccessSecret string, domain string, oldCert *x509.Certificate, stopchan chan bool, newchan chan NewCert) error {
+	for {
+		select {
+		case <-stopchan:
+			newchan <- NewCert{
+				PrivateKey:  nil,
+				Certificate: nil,
+				Error:       nil,
+			}
+			close(stopchan)
+			return nil
+		default:
+			privateKey, cert, cacert, err := watchCertificate(dir, email, aliyunAccessKey, aliyunAccessSecret, domain, oldCert)
+			if err != nil {
+				newchan <- NewCert{
+					Error: fmt.Errorf("watch cert failed: %s", err.Error()),
+				}
+			} else if privateKey != nil && cert != nil && cacert != nil {
+				oldCert = cert
+				newchan <- NewCert{
+					PrivateKey:        privateKey,
+					Certificate:       cert,
+					IssuerCertificate: cacert,
+				}
+			}
+		}
+	}
+}
+
+func watchCertificate(dir string, email string, aliyunAccessKey string, aliyunAccessSecret string, domain string, oldCert *x509.Certificate) (crypto.PrivateKey, *x509.Certificate, *x509.Certificate, error) {
+	if email == "" {
+		email = "no-reply@example.com"
+	}
+
+	if !utils.IsValidEmail(email) {
+		return nil, nil, nil, fmt.Errorf("not a valid email")
+	}
+
+	if !utils.IsValidDomain(domain) {
+		return nil, nil, nil, fmt.Errorf("not a valid domain")
+	}
+
+	if utils.CheckCertWithDomain(oldCert, domain) && utils.CheckCertWithTime(oldCert, CertDefaultNewApplyTime) {
+		return nil, nil, nil, nil
+	}
+
+	resource, err := applycert.ApplyCert(dir, email, aliyunAccessKey, aliyunAccessSecret, domain)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("apply cert fail: %s", err.Error())
+	}
+
+	privateKey, err := utils.ReadPrivateKey(resource.PrivateKey)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("read private key failed: %s", err.Error())
+	}
+
+	cert, err := utils.ReadCertificate(resource.Certificate)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("read cert failed: %s", err.Error())
+	}
+
+	cacert, err := utils.ReadCertificate(resource.IssuerCertificate)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("read cert failed: %s", err.Error())
+	}
+
+	return privateKey, cert, cacert, nil
+}

+ 10 - 0
src/cmd/version1/main.go

@@ -0,0 +1,10 @@
+package main
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/mainfunc"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+func main() {
+	utils.Exit(mainfunc.MainV1())
+}

+ 303 - 0
src/config/config.go

@@ -0,0 +1,303 @@
+package config
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile"
+	"github.com/SongZihuan/huan-proxy/src/flagparser"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"github.com/fsnotify/fsnotify"
+	"os"
+	"path/filepath"
+	"sync"
+)
+
+type ConfigStruct struct {
+	ConfigLock sync.Mutex
+
+	configReady    bool
+	yamlHasParser  bool
+	sigchan        chan os.Signal
+	configPath     string
+	configDir      string
+	configFileName string
+	watcher        *fsnotify.Watcher
+
+	Yaml  *YamlConfig
+	Rules *rulescompile.RuleListCompileConfig
+}
+
+func newConfig(configPath string) (*ConfigStruct, error) {
+	if configPath == "" {
+		if !flagparser.IsReady() {
+			panic("flag is not ready")
+		}
+
+		configPath = flagparser.ConfigFile()
+	}
+
+	configPath, err := utils.CleanFilePathAbs(configPath)
+	if err != nil {
+		return nil, err
+	}
+
+	configDir := filepath.Dir(configPath)
+	configFileName := filepath.Base(configPath)
+
+	return &ConfigStruct{
+		// Lock不用初始化
+		configReady:    false,
+		yamlHasParser:  false,
+		sigchan:        make(chan os.Signal),
+		configPath:     configPath,
+		configDir:      configDir,
+		configFileName: configFileName,
+		Yaml:           nil,
+		Rules:          nil,
+	}, nil
+}
+
+func (c *ConfigStruct) Init() (err configerr.ConfigError) {
+	if c.IsReady() { // 使用IsReady而不是isReady,确保上锁
+		return c.Reload()
+	}
+
+	initErr := c.init()
+	if initErr != nil {
+		return configerr.NewConfigError("init error: " + initErr.Error())
+	}
+
+	parserErr := c.Parser(c.configPath)
+	if parserErr != nil {
+		return configerr.NewConfigError("parser error: " + parserErr.Error())
+	} else if !c.yamlHasParser {
+		return configerr.NewConfigError("parser error: unknown")
+	}
+
+	c.SetDefault()
+
+	err = c.Check()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	err = c.CompileRule()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	c.configReady = true
+	return nil
+}
+
+func (c *ConfigStruct) Reload() (err configerr.ConfigError) {
+	if !c.IsReady() { // 使用IsReady而不是isReady,确保上锁
+		return c.Init()
+	}
+
+	bak := ConfigStruct{
+		configReady:    c.configReady,
+		yamlHasParser:  c.yamlHasParser,
+		sigchan:        c.sigchan,
+		configPath:     c.configPath,
+		configDir:      c.configDir,
+		configFileName: c.configFileName,
+		watcher:        c.watcher,
+		Yaml:           c.Yaml,
+		Rules:          c.Rules,
+		// 新建类型
+	}
+
+	defer func() {
+		if err != nil {
+			*c = ConfigStruct{
+				configReady:    bak.configReady,
+				yamlHasParser:  bak.yamlHasParser,
+				sigchan:        bak.sigchan,
+				configPath:     bak.configPath,
+				configDir:      bak.configDir,
+				configFileName: bak.configFileName,
+				watcher:        bak.watcher,
+				Yaml:           bak.Yaml,
+				Rules:          bak.Rules,
+				// 新建类型 Lock不需要复制
+			}
+		}
+	}()
+
+	c.ConfigLock.Lock()
+	defer c.ConfigLock.Unlock()
+
+	reloadErr := c.reload()
+	if reloadErr != nil {
+		return configerr.NewConfigError("reload error: " + reloadErr.Error())
+	}
+
+	parserErr := c.Parser(c.configPath)
+	if parserErr != nil {
+		return configerr.NewConfigError("reload parser error: " + parserErr.Error())
+	} else if !c.yamlHasParser {
+		return configerr.NewConfigError("reload parser error: unknown")
+	}
+
+	c.SetDefault()
+
+	err = c.Check()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	err = c.CompileRule()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	c.configReady = true
+	return nil
+}
+
+func (c *ConfigStruct) clear() error {
+	c.configReady = false
+	c.yamlHasParser = false
+	// sigchan和watcher 不变
+	c.Yaml = nil
+	c.Rules = nil
+	return nil
+}
+
+func (c *ConfigStruct) Parser(filepath string) configerr.ParserError {
+	err := c.Yaml.Parser(filepath)
+	if err != nil {
+		return err
+	}
+
+	c.yamlHasParser = true
+	return nil
+}
+
+func (c *ConfigStruct) SetDefault() {
+	if !c.yamlHasParser {
+		panic("yaml must parser first")
+	}
+
+	c.Yaml.SetDefault()
+}
+
+func (c *ConfigStruct) Check() (err configerr.ConfigError) {
+	err = c.Yaml.Check()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	return nil
+}
+
+func (c *ConfigStruct) CompileRule() configerr.ConfigError {
+	res, err := rulescompile.NewRuleListConfig(&c.Yaml.RuleListConfig)
+	if err != nil {
+		return configerr.NewConfigError("compile rule error: " + err.Error())
+	}
+
+	c.Rules = res
+	return nil
+}
+
+func (c *ConfigStruct) isReady() bool {
+	return c.yamlHasParser && c.configReady
+}
+
+func (c *ConfigStruct) init() error {
+	c.configReady = false
+	c.yamlHasParser = false
+
+	err := initSignal(c.sigchan)
+	if err != nil {
+		return err
+	}
+
+	c.Yaml = new(YamlConfig)
+	err = c.Yaml.Init()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (c *ConfigStruct) reload() error {
+	err := c.clear()
+	if err != nil {
+		return err
+	}
+
+	c.Yaml = new(YamlConfig)
+	err = c.Yaml.Init()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// export func
+
+func (c *ConfigStruct) IsReady() bool {
+	c.ConfigLock.Lock()
+	defer c.ConfigLock.Unlock()
+	return c.isReady()
+}
+
+func (c *ConfigStruct) GetSignalChan() chan os.Signal {
+	c.ConfigLock.Lock()
+	defer c.ConfigLock.Unlock()
+
+	return c.sigchan
+}
+
+func (c *ConfigStruct) GetConfig() *YamlConfig {
+	c.ConfigLock.Lock()
+	defer c.ConfigLock.Unlock()
+
+	if !c.isReady() {
+		panic("config is not ready")
+	}
+
+	return c.Yaml
+}
+
+func (c *ConfigStruct) GetRulesList() *rulescompile.RuleListCompileConfig {
+	c.ConfigLock.Lock()
+	defer c.ConfigLock.Unlock()
+
+	if !c.isReady() {
+		panic("config is not ready")
+	}
+
+	return c.Rules
+}
+
+func (c *ConfigStruct) GetConfigPathFile() string {
+	c.ConfigLock.Lock()
+	defer c.ConfigLock.Unlock()
+
+	// 不需要检查Ready
+
+	return c.configPath
+}
+
+func (c *ConfigStruct) GetConfigFileDir() string {
+	c.ConfigLock.Lock()
+	defer c.ConfigLock.Unlock()
+
+	// 不需要检查Ready
+
+	return c.configDir
+}
+
+func (c *ConfigStruct) GetConfigFileName() string {
+	c.ConfigLock.Lock()
+	defer c.ConfigLock.Unlock()
+
+	// 不需要检查Ready
+
+	return c.configFileName
+}

+ 83 - 0
src/config/configerr/error.go

@@ -0,0 +1,83 @@
+package configerr
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+type ConfigError interface {
+	error
+	Msg() string
+	Error() string
+	Warning() string
+	IsError() bool
+	IsWarning() bool
+}
+
+func NewConfigError(msg string) ConfigError {
+	fmt.Println(utils.FormatTextToWidth(fmt.Sprintf("config error: %s", msg), utils.NormalConsoleWidth))
+	return &configError{msg: msg, isError: true}
+}
+
+func NewConfigWarning(msg string) ConfigError {
+	fmt.Println(utils.FormatTextToWidth(fmt.Sprintf("config warning: %s", msg), utils.NormalConsoleWidth))
+	return &configError{msg: msg, isError: false}
+}
+
+type configError struct {
+	msg     string
+	isError bool
+}
+
+func (e *configError) Msg() string {
+	if e.isError {
+		return "config error: " + e.Error()
+	}
+	return "config warning: " + e.Warning()
+}
+
+func (e *configError) Error() string {
+	return e.msg
+}
+
+func (e *configError) Warning() string {
+	return e.msg
+}
+
+func (e *configError) IsError() bool {
+	return e.isError
+}
+
+func (e *configError) IsWarning() bool {
+	return !e.isError
+}
+
+type ParserError interface {
+	error
+	Error() string
+	Data() interface{}
+}
+
+type parserError struct {
+	msg  string
+	data interface{}
+}
+
+func NewParserError(data interface{}, msg ...string) ParserError {
+	if len(msg) == 1 {
+		return &parserError{msg[0], data}
+	}
+	return &parserError{"config parser error: " + fmt.Sprint(data), data}
+}
+
+func WarpParserError(err error) ParserError {
+	return &parserError{"config parser error: " + err.Error(), err}
+}
+
+func (e *parserError) Error() string {
+	return e.msg
+}
+
+func (e *parserError) Data() interface{} {
+	return e.data
+}

+ 100 - 0
src/config/configwatcher/watcher.go

@@ -0,0 +1,100 @@
+package configwatcher
+
+import (
+	"errors"
+	"github.com/SongZihuan/huan-proxy/src/config"
+	"github.com/SongZihuan/huan-proxy/src/logger"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"github.com/fsnotify/fsnotify"
+)
+
+var watcher *fsnotify.Watcher
+
+func WatcherConfigFile() error {
+	if watcher != nil {
+		return nil
+	}
+
+	if !config.IsReady() {
+		panic("config file path is empty")
+	}
+
+	_watcher, err := fsnotify.NewWatcher()
+	if err != nil {
+		return err
+	}
+
+	// Add a path.
+	watcherDir := config.GetConfigFileDir()
+	err = _watcher.Add(watcherDir)
+	if err != nil {
+		return err
+	}
+
+	// Start listening for events.
+	go func() {
+		defer func() {
+			err := closeNotifyConfigFile()
+			if err != nil {
+				logger.Warnf("Auto reload stop with error: %s", err.Error())
+				return
+			}
+
+			logger.Warnf("Auto reload stop.")
+		}()
+
+	OutSideCycle:
+		for {
+			select {
+			case event, ok := <-_watcher.Events:
+				if !ok {
+					return
+				}
+
+				// github.com/fsnotify/fsnotify v1.8.0
+				// 根据2024.1月的消息,暂时无法导出RenameFrom,无法跟着重命名
+				// issues: https://github.com/fsnotify/fsnotify/issues/630
+				if !utils.FilePathEqual(event.Name, config.GetConfigPathFile()) {
+					continue OutSideCycle
+				}
+
+				if event.Has(fsnotify.Write) || event.Has(fsnotify.Create) {
+					err := config.ReloadConfig()
+					if err != nil && err.IsError() {
+						logger.Errorf("Config file reload error: %s", err.Error())
+					} else if err != nil && err.IsWarning() {
+						logger.Warnf("Config file reload error: %s", err.Warning())
+					} else {
+						logger.Infof("%s", "Config file reload success")
+					}
+				} else if event.Has(fsnotify.Rename) {
+					logger.Warnf("%s", "Config file has been rename")
+				} else if event.Has(fsnotify.Remove) {
+					logger.Warnf("%s", "Config file has been remove")
+				}
+			case err, ok := <-_watcher.Errors:
+				if !ok || errors.Is(err, fsnotify.ErrClosed) {
+					return
+				}
+				logger.Errorf("Config file notify error: %s", err.Error())
+			}
+		}
+	}()
+
+	watcher = _watcher
+	return nil
+}
+
+func CloseNotifyConfigFile() {
+	_ = closeNotifyConfigFile()
+}
+
+func closeNotifyConfigFile() error {
+	if watcher == nil {
+		return nil
+	}
+
+	err := watcher.Close()
+	watcher = nil
+	return err
+}

+ 89 - 0
src/config/globalconfig.go

@@ -0,0 +1,89 @@
+package config
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"os"
+)
+
+const EnvModeName = "HUAN_PROXY_MODE"
+
+const (
+	DebugMode   = "debug"
+	ReleaseMode = "release"
+	TestMode    = "test"
+)
+
+type LoggerLevel string
+
+var levelMap = map[string]bool{
+	"debug": true,
+	"info":  true,
+	"warn":  true,
+	"error": true,
+	"panic": true,
+	"none":  true,
+}
+
+type GlobalConfig struct {
+	Mode     string           `yaml:"mode"`
+	LogLevel string           `yaml:"loglevel"`
+	LogTag   utils.StringBool `yaml:"logtag"`
+	NotAbort utils.StringBool `yaml:"notabort"`
+}
+
+func (g *GlobalConfig) SetDefault() {
+	if g.Mode == "" {
+		g.Mode = os.Getenv(EnvModeName)
+	}
+
+	if g.Mode == "" {
+		g.Mode = DebugMode
+	}
+
+	_ = os.Setenv(EnvModeName, g.Mode)
+
+	if g.LogLevel == "" && (g.Mode == DebugMode || g.Mode == TestMode) {
+		g.LogLevel = "debug"
+	} else if g.LogLevel == "" {
+		g.LogLevel = "warn"
+	}
+
+	if g.Mode == DebugMode || g.Mode == TestMode {
+		g.LogTag.SetDefaultEnable()
+	} else {
+		g.LogTag.SetDefaultDisable()
+	}
+
+	g.NotAbort.SetDefaultDisable()
+
+	return
+}
+
+func (g *GlobalConfig) Check() configerr.ConfigError {
+	if g.Mode != DebugMode && g.Mode != ReleaseMode && g.Mode != TestMode {
+		return configerr.NewConfigError("bad mode")
+	}
+
+	if _, ok := levelMap[g.LogLevel]; !ok {
+		return configerr.NewConfigError("log level error")
+	}
+
+	return nil
+}
+
+func (g *GlobalConfig) GetRunMode() string {
+	return g.Mode
+}
+
+func (g *GlobalConfig) IsDebug() bool {
+	return g.Mode == DebugMode
+}
+
+func (g *GlobalConfig) IsRelease() bool {
+	return g.Mode == ReleaseMode
+}
+
+func (g *GlobalConfig) IsTest() bool {
+	return g.Mode == TestMode
+}

+ 33 - 0
src/config/httpconfig.go

@@ -0,0 +1,33 @@
+package config
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"net/url"
+)
+
+type HttpConfig struct {
+	Address        string `yaml:"address"`
+	StopWaitSecond int    `yaml:"stopwaitsecond"`
+}
+
+func (h *HttpConfig) SetDefault() {
+	if h.Address == "" {
+		return
+	}
+
+	if h.StopWaitSecond <= 0 {
+		h.StopWaitSecond = 10
+	}
+}
+
+func (h *HttpConfig) Check() configerr.ConfigError {
+	if h.Address == "" {
+		return nil
+	}
+
+	if _, err := url.Parse(h.Address); err != nil {
+		return configerr.NewConfigError(fmt.Sprintf("http address error: %s", err.Error()))
+	}
+	return nil
+}

+ 69 - 0
src/config/httpsconfig.go

@@ -0,0 +1,69 @@
+package config
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"net/url"
+	"os"
+)
+
+const (
+	EnvAliyunKey    = "HP_ALIYUN_ACCESS_KEY"
+	EnvAliyunSecret = "HP_ALIYUN_ACCESS_SECRET"
+)
+
+type HttpsConfig struct {
+	Address               string `yaml:"address"`
+	SSLEmail              string `json:"sslemail"`
+	SSLDomain             string `yaml:"ssldomaain"`
+	SSLCertDir            string `yaml:"sslcertdir"`
+	AliyunDNSAccessKey    string `yaml:"aliyundnsaccesskey"`
+	AliyunDNSAccessSecret string `yaml:"aliyunDNSAccesssecret"`
+	StopWaitSecond        int    `yaml:"stopwaitsecond"`
+}
+
+func (h *HttpsConfig) SetDefault() {
+	if h.Address == "" {
+		return
+	}
+
+	if h.SSLEmail == "" {
+		h.SSLEmail = "no-reply@example.com"
+	}
+
+	if h.SSLCertDir == "" {
+		h.SSLCertDir = "./ssl-certs"
+	}
+
+	if h.AliyunDNSAccessKey == "" {
+		h.AliyunDNSAccessKey = os.Getenv(EnvAliyunKey)
+	}
+
+	if h.AliyunDNSAccessSecret == "" {
+		h.AliyunDNSAccessKey = os.Getenv(EnvAliyunSecret)
+	}
+
+	if h.StopWaitSecond <= 0 {
+		h.StopWaitSecond = 10
+	}
+}
+
+func (h *HttpsConfig) Check() configerr.ConfigError {
+	if h.Address == "" {
+		return nil
+	}
+
+	if _, err := url.Parse(h.Address); err != nil {
+		return configerr.NewConfigError(fmt.Sprintf("http address error: %s", err.Error()))
+	}
+
+	if h.SSLDomain == "" {
+		return configerr.NewConfigError("http ssl must has a domain")
+	}
+
+	if h.AliyunDNSAccessKey == "" || h.AliyunDNSAccessSecret == "" {
+		return configerr.NewConfigError("http ssl must has a aliyun access key or secret")
+	}
+
+	return nil
+}

+ 69 - 0
src/config/main.go

@@ -0,0 +1,69 @@
+package config
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile"
+	"os"
+)
+
+func InitConfig(configPath string) configerr.ConfigError {
+	var err error
+	config, err = newConfig(configPath)
+	if err != nil {
+		return configerr.NewConfigError(err.Error())
+	}
+
+	cfgErr := config.Init()
+	if cfgErr != nil && cfgErr.IsError() {
+		return cfgErr
+	}
+
+	if !config.IsReady() {
+		return configerr.NewConfigError("config not ready")
+	}
+
+	return nil
+}
+
+func ReloadConfig() configerr.ConfigError {
+	err := config.Reload()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	if !config.IsReady() {
+		return configerr.NewConfigError("config not ready")
+	}
+
+	return nil
+}
+
+func IsReady() bool {
+	return config.IsReady()
+}
+
+func GetConfig() *YamlConfig {
+	return config.GetConfig()
+}
+
+func GetRules() *rulescompile.RuleListCompileConfig {
+	return config.GetRulesList()
+}
+
+func GetSignalChan() chan os.Signal {
+	return config.GetSignalChan()
+}
+
+func GetConfigPathFile() string {
+	return config.GetConfigPathFile()
+}
+
+func GetConfigFileDir() string {
+	return config.GetConfigFileDir()
+}
+
+func GetConfigFileName() string {
+	return config.GetConfigFileName()
+}
+
+var config *ConfigStruct

+ 98 - 0
src/config/rules/action/api/api.go

@@ -0,0 +1,98 @@
+package api
+
+import (
+	"fmt"
+	resource "github.com/SongZihuan/huan-proxy"
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/rewrite"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"net/url"
+)
+
+type RuleAPIConfig struct {
+	Address   string                `yaml:"address"`
+	AddPath   string                `yaml:"addpath"`
+	SubPath   string                `yaml:"subpath"`
+	Rewrite   rewrite.RewriteConfig `yaml:"rewrite"`
+	HeaderSet []*HeaderConfig       `yaml:"headerset"`
+	HeaderAdd []*HeaderConfig       `yaml:"headeradd"`
+	HeaderDel []*HeaderDelConfig    `yaml:"headerdel"`
+	QuerySet  []*QueryConfig        `yaml:"queryset"`
+	QueryAdd  []*QueryConfig        `yaml:"queryadd"`
+	QueryDel  []*QueryDelConfig     `yaml:"querydel"`
+	Via       string                `yaml:"via"`
+}
+
+func (r *RuleAPIConfig) SetDefault() {
+	r.AddPath = utils.ProcessURLPath(r.AddPath)
+	r.SubPath = utils.ProcessURLPath(r.SubPath)
+
+	r.Rewrite.SetDefault()
+
+	for _, h := range r.HeaderSet {
+		h.SetDefault()
+	}
+
+	for _, h := range r.HeaderAdd {
+		h.SetDefault()
+	}
+
+	for _, h := range r.HeaderDel {
+		h.SetDefault()
+	}
+
+	for _, q := range r.QuerySet {
+		q.SetDefault()
+	}
+
+	for _, q := range r.QueryAdd {
+		q.SetDefault()
+	}
+
+	for _, q := range r.QueryDel {
+		q.SetDefault()
+	}
+
+	if r.Via == "" {
+		r.Via = resource.Via
+	}
+}
+
+func (r *RuleAPIConfig) Check() configerr.ConfigError {
+	targetURL, err := url.Parse(r.Address)
+	if err != nil {
+		return configerr.NewConfigError(fmt.Sprintf("Failed to parse target URL: %s", err.Error()))
+	}
+
+	if targetURL.Opaque != "" {
+		return configerr.NewConfigError("proxy address should not have Opaque")
+	}
+
+	if targetURL.Path == "/" || targetURL.RawPath == "/" {
+		targetURL.Path = ""
+		targetURL.RawPath = ""
+	}
+
+	if targetURL.Path != "" || targetURL.RawPath != "" {
+		return configerr.NewConfigError("proxy address should not have path")
+	}
+
+	if targetURL.RawQuery != "" {
+		return configerr.NewConfigError("proxy address should not have query")
+	}
+
+	if targetURL.User != nil {
+		return configerr.NewConfigError("proxy address should not have user information")
+	}
+
+	if targetURL.Fragment != "" || targetURL.RawFragment != "" {
+		return configerr.NewConfigError("proxy address should not have fragment")
+	}
+
+	cfgErr := r.Rewrite.Check()
+	if cfgErr != nil && cfgErr.IsError() {
+		return cfgErr
+	}
+
+	return nil
+}

+ 40 - 0
src/config/rules/action/api/headerconfig.go

@@ -0,0 +1,40 @@
+package api
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+type HeaderConfig struct {
+	Header string `yaml:"header"`
+	Value  string `yaml:"value"`
+}
+
+func (h *HeaderConfig) SetDefault() {
+
+}
+
+func (h *HeaderConfig) Check() configerr.ConfigError {
+	if h.Header == "" {
+		return configerr.NewConfigError("header name is empty")
+	}
+
+	if h.Header == ViaHeader || h.Header == XHuanProxyHeaer {
+		return configerr.NewConfigError(fmt.Sprintf("header %s use by http system", h.Header))
+	}
+
+	if !utils.IsValidHTTPHeaderKey(h.Header) {
+		return configerr.NewConfigError(fmt.Sprintf("header %s is not valid", h.Header))
+	}
+
+	if isNotGoodHeader(h.Header) {
+		_ = configerr.NewConfigWarning(fmt.Sprintf("header %s use by http system", h.Header))
+	}
+
+	if h.Value == "" {
+		_ = configerr.NewConfigWarning(fmt.Sprintf("the value of header %s is empty, but maybe it is not delete from requests", h.Header))
+	}
+
+	return nil
+}

+ 35 - 0
src/config/rules/action/api/headerdelconfig.go

@@ -0,0 +1,35 @@
+package api
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+type HeaderDelConfig struct {
+	Header string `yaml:"header"`
+}
+
+func (h *HeaderDelConfig) SetDefault() {
+
+}
+
+func (h *HeaderDelConfig) Check() configerr.ConfigError {
+	if h.Header == "" {
+		return configerr.NewConfigError("header name is empty")
+	}
+
+	if h.Header == ViaHeader || h.Header == XHuanProxyHeaer {
+		return configerr.NewConfigError(fmt.Sprintf("header %s use by http system", h.Header))
+	}
+
+	if !utils.IsValidHTTPHeaderKey(h.Header) {
+		return configerr.NewConfigError(fmt.Sprintf("header %s is not valid", h.Header))
+	}
+
+	if isNotGoodHeader(h.Header) {
+		_ = configerr.NewConfigWarning(fmt.Sprintf("header %s use by http system", h.Header))
+	}
+
+	return nil
+}

+ 30 - 0
src/config/rules/action/api/protectheader.go

@@ -0,0 +1,30 @@
+package api
+
+const XHuanProxyHeaer = "X-Huan-Proxy"
+const ViaHeader = "Via"
+
+var WarningHeader = []string{
+	"Host",
+	"Referer",
+	"User-Agent",
+	"Forwarded",
+	"Content-Length",
+	"Transfer-Encoding",
+	"Upgrade",
+	"Connection",
+	"X-Forwarded-For",
+	"X-Forwarded-Host",
+	"X-Forwarded-Proto",
+	"X-Real-Ip",
+	"X-Real-Port",
+}
+
+func isNotGoodHeader(header string) bool {
+	for _, h := range WarningHeader {
+		if h == header {
+			return true
+		}
+	}
+
+	return false
+}

+ 32 - 0
src/config/rules/action/api/queryconfig.go

@@ -0,0 +1,32 @@
+package api
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+type QueryConfig struct {
+	Query string `yaml:"query"`
+	Value string `yaml:"value"`
+}
+
+func (q *QueryConfig) SetDefault() {
+
+}
+
+func (q *QueryConfig) Check() configerr.ConfigError {
+	if q.Query == "" {
+		return configerr.NewConfigError("query key is empty")
+	}
+
+	if !utils.IsGoodQueryKey(q.Query) {
+		_ = configerr.NewConfigWarning(fmt.Sprintf("query %s is not good", q.Query))
+	}
+
+	if q.Value == "" {
+		_ = configerr.NewConfigWarning(fmt.Sprintf("the value of query %s is empty, but maybe it is not delete from requests", q.Query))
+	}
+
+	return nil
+}

+ 27 - 0
src/config/rules/action/api/querydelconfig.go

@@ -0,0 +1,27 @@
+package api
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+type QueryDelConfig struct {
+	Query string `yaml:"query"`
+}
+
+func (q *QueryDelConfig) SetDefault() {
+
+}
+
+func (q *QueryDelConfig) Check() configerr.ConfigError {
+	if q.Query == "" {
+		return configerr.NewConfigError("query key is empty")
+	}
+
+	if !utils.IsGoodQueryKey(q.Query) {
+		_ = configerr.NewConfigWarning(fmt.Sprintf("query %s is not good", q.Query))
+	}
+
+	return nil
+}

+ 1 - 0
src/config/rules/action/cors/README

@@ -0,0 +1 @@
+所有模式均有

+ 33 - 0
src/config/rules/action/cors/corsconfig.go

@@ -0,0 +1,33 @@
+package cors
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+const CorsMaxAgeSec = 86400
+const CorsDefaultMaxAgeSec = CorsMaxAgeSec
+
+type CorsConfig struct {
+	AllowCors      utils.StringBool `yaml:"allowcors"`
+	AllowOrigin    []string         `yaml:"alloworigin"`
+	AllowOriginReg []string         `yaml:"alloworiginres"`
+	MaxAgeSec      int              `yaml:"maxagesec"`
+}
+
+func (c *CorsConfig) SetDefault() {
+	c.AllowCors.SetDefaultDisable()
+	if c.AllowCors.IsEnable() && c.MaxAgeSec == 0 {
+		c.MaxAgeSec = CorsDefaultMaxAgeSec
+	}
+}
+
+func (c *CorsConfig) Check() configerr.ConfigError {
+	if c.AllowCors.IsEnable() {
+		if c.MaxAgeSec <= 0 || c.MaxAgeSec > CorsMaxAgeSec {
+			return configerr.NewConfigError(fmt.Sprintf("cors maxagesec %d is invalid", c.MaxAgeSec))
+		}
+	}
+	return nil
+}

+ 80 - 0
src/config/rules/action/dir/dirconfig.go

@@ -0,0 +1,80 @@
+package dir
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/cors"
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/rewrite"
+)
+
+type RuleDirConfig struct {
+	BasePath   string                `yaml:"basepath"`
+	IndexFile  []*IndexFileConfig    `yaml:"indexfile"`
+	IgnoreFile []*IgnoreFileConfig   `yaml:"ignorefile"`
+	AddPath    string                `yaml:"addpath"`
+	SubPath    string                `yaml:"subpath"`
+	Rewrite    rewrite.RewriteConfig `yaml:"rewrite"`
+	Cors       cors.CorsConfig       `yaml:"cors"`
+}
+
+func (r *RuleDirConfig) SetDefault() {
+	if len(r.IndexFile) == 0 {
+		r.IndexFile = []*IndexFileConfig{
+			{
+				Regex: "disable",
+				File:  "index.html",
+			},
+			{
+				Regex: "disable",
+				File:  "index.xml",
+			},
+			{
+				Regex: "disable",
+				File:  "index",
+			},
+			{
+				Regex: "enable",
+				File:  `^index\.\S+$`,
+			},
+		}
+	}
+
+	for _, i := range r.IndexFile {
+		i.SetDefault()
+	}
+
+	for _, i := range r.IgnoreFile {
+		i.SetDefault()
+	}
+
+	r.Rewrite.SetDefault()
+	r.Cors.SetDefault()
+}
+
+func (r *RuleDirConfig) Check() configerr.ConfigError {
+	// 不用检查目录是否存在,因为可能被rewrite
+	for _, i := range r.IndexFile {
+		err := i.Check()
+		if err != nil && err.IsError() {
+			return err
+		}
+	}
+
+	for _, i := range r.IgnoreFile {
+		err := i.Check()
+		if err != nil && err.IsError() {
+			return err
+		}
+	}
+
+	err := r.Rewrite.Check()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	err = r.Cors.Check()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	return nil
+}

+ 23 - 0
src/config/rules/action/dir/ignorefileconfig.go

@@ -0,0 +1,23 @@
+package dir
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+type IgnoreFileConfig struct {
+	Regex utils.StringBool `yaml:"regex"`
+	File  string           `yaml:"file"`
+}
+
+func (i *IgnoreFileConfig) SetDefault() {
+	i.Regex.SetDefaultDisable()
+}
+
+func (i *IgnoreFileConfig) Check() configerr.ConfigError {
+	if i.File == "" {
+		return configerr.NewConfigError("file is empty")
+	}
+
+	return nil
+}

+ 23 - 0
src/config/rules/action/dir/indexfileconfig.go

@@ -0,0 +1,23 @@
+package dir
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+type IndexFileConfig struct {
+	Regex utils.StringBool `yaml:"regex"`
+	File  string           `yaml:"file"`
+}
+
+func (i *IndexFileConfig) SetDefault() {
+	i.Regex.SetDefaultDisable()
+}
+
+func (i *IndexFileConfig) Check() configerr.ConfigError {
+	if i.File == "" {
+		return configerr.NewConfigError("file is empty")
+	}
+
+	return nil
+}

+ 33 - 0
src/config/rules/action/file/fileconfig.go

@@ -0,0 +1,33 @@
+package file
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/cors"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+type RuleFileConfig struct {
+	Path string          `yaml:"path"`
+	Cors cors.CorsConfig `yaml:"cors"`
+}
+
+func (r *RuleFileConfig) SetDefault() {
+	r.Cors.SetDefault()
+}
+
+func (r *RuleFileConfig) Check() configerr.ConfigError {
+	if r.Path == "" {
+		return configerr.NewConfigError("file is empty")
+	}
+
+	if !utils.IsFile(r.Path) {
+		return configerr.NewConfigError("file is not exists")
+	}
+
+	err := r.Cors.Check()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	return nil
+}

+ 41 - 0
src/config/rules/action/redirect/redirectconfig.go

@@ -0,0 +1,41 @@
+package redirect
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/rewrite"
+	"net/http"
+	"net/url"
+)
+
+type RuleRedirectConfig struct {
+	Address string                `yaml:"address"`
+	Rewrite rewrite.RewriteConfig `yaml:"rewrite"`
+	Code    int                   `yaml:"code"`
+}
+
+func (r *RuleRedirectConfig) SetDefault() {
+	if r.Code == 0 {
+		r.Code = http.StatusMovedPermanently
+	}
+
+	r.Rewrite.SetDefault()
+}
+
+func (r *RuleRedirectConfig) Check() configerr.ConfigError {
+	_, err := url.Parse(r.Address)
+	if err != nil {
+		return configerr.NewConfigError(fmt.Sprintf("Failed to parse target URL: %s", err.Error()))
+	}
+
+	cfgErr := r.Rewrite.Check()
+	if cfgErr != nil && cfgErr.IsError() {
+		return cfgErr
+	}
+
+	if r.Code != http.StatusMovedPermanently && r.Code != http.StatusMultipleChoices {
+		return configerr.NewConfigError(fmt.Sprintf("Redirect code must be %d %d: you use %d", http.StatusMovedPermanently, http.StatusMultipleChoices, r.Code))
+	}
+
+	return nil
+}

+ 1 - 0
src/config/rules/action/remotetrust/README

@@ -0,0 +1 @@
+File和Dir模式独有

+ 31 - 0
src/config/rules/action/remotetrust/remotetrustconfig.go

@@ -0,0 +1,31 @@
+package remotetrust
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+type RemoteTrustConfig struct {
+	RemoteTrust utils.StringBool `yaml:"remotetrust"`
+	TrustedIPs  []string         `yaml:"trustedips"`
+}
+
+func (p *RemoteTrustConfig) SetDefault() {
+	p.RemoteTrust.SetDefaultDisable()
+
+	if p.RemoteTrust.IsEnable() && len(p.TrustedIPs) == 0 {
+		p.TrustedIPs = []string{"127.0.0.0/8", "::1"}
+	}
+}
+
+func (p *RemoteTrustConfig) Check() configerr.ConfigError {
+	if p.RemoteTrust.IsEnable() {
+		for _, ip := range p.TrustedIPs {
+			if !utils.ValidIPv4(ip) && !utils.ValidIPv6(ip) && !utils.IsValidIPv4CIDR(ip) && !utils.IsValidIPv6CIDR(ip) {
+				return configerr.NewConfigError(fmt.Sprintf("bad proxy trusts ip address: %s", ip))
+			}
+		}
+	}
+	return nil
+}

+ 1 - 0
src/config/rules/action/rewrite/README

@@ -0,0 +1 @@
+File和Dir模式独有

+ 20 - 0
src/config/rules/action/rewrite/rewriteconfig.go

@@ -0,0 +1,20 @@
+package rewrite
+
+import "github.com/SongZihuan/huan-proxy/src/config/configerr"
+
+type RewriteConfig struct {
+	Regex  string `yaml:"regex"`
+	Target string `yaml:"target"`
+}
+
+func (r *RewriteConfig) SetDefault() {
+
+}
+
+func (r *RewriteConfig) Check() configerr.ConfigError {
+	if len(r.Target) != 0 && len(r.Regex) == 0 {
+		return configerr.NewConfigError("rewrite reg is empty")
+	}
+
+	return nil
+}

+ 1 - 0
src/config/rules/match/README

@@ -0,0 +1 @@
+File和Dir模式独有

+ 33 - 0
src/config/rules/match/matchconfig.go

@@ -0,0 +1,33 @@
+package match
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+const (
+	PrefixMatch    = "prefix"    // 前缀匹配
+	RegexMatch     = "regex"     // 正则匹配
+	PrecisionMatch = "precision" // 精准匹配
+)
+
+type MatchConfig struct {
+	MatchType string `yaml:"matchtype"`
+	MatchPath string `yaml:"matchpath"`
+}
+
+func (m *MatchConfig) SetDefault() {
+	if m.MatchType == "" {
+		m.MatchType = PrefixMatch
+	}
+	if m.MatchType == PrefixMatch || m.MatchType == PrecisionMatch {
+		m.MatchPath = utils.ProcessURLPath(m.MatchPath)
+	}
+}
+
+func (m *MatchConfig) Check() configerr.ConfigError {
+	if m.MatchType != PrefixMatch && m.MatchType != RegexMatch && m.MatchType != PrecisionMatch {
+		return configerr.NewConfigError("proxy mutch type must be prefix or regex or precision")
+	}
+	return nil
+}

+ 83 - 0
src/config/rules/ruleconfig.go

@@ -0,0 +1,83 @@
+package rules
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/api"
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/dir"
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/file"
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/redirect"
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/remotetrust"
+	"github.com/SongZihuan/huan-proxy/src/config/rules/match"
+)
+
+const (
+	ProxyTypeFile     = "file"
+	ProxyTypeDir      = "dir"
+	ProxyTypeAPI      = "api"
+	ProxyTypeRedirect = "redirect"
+)
+
+type RuleConfig struct {
+	Type string `yaml:"type"`
+
+	match.MatchConfig             `yaml:",inline"`
+	remotetrust.RemoteTrustConfig `yaml:",inline"`
+
+	File     file.RuleFileConfig         `yaml:"file"`
+	Dir      dir.RuleDirConfig           `yaml:"dir"`
+	Api      api.RuleAPIConfig           `yaml:"api"`
+	Redirect redirect.RuleRedirectConfig `yaml:"redirect"`
+}
+
+func (p *RuleConfig) SetDefault() {
+	p.MatchConfig.SetDefault()
+	p.RemoteTrustConfig.SetDefault()
+
+	if p.Type == ProxyTypeFile {
+		p.File.SetDefault()
+	} else if p.Type == ProxyTypeDir {
+		p.Dir.SetDefault()
+	} else if p.Type == ProxyTypeAPI {
+		p.Api.SetDefault()
+	} else if p.Type == ProxyTypeRedirect {
+		p.Redirect.SetDefault()
+	}
+}
+
+func (p *RuleConfig) Check() configerr.ConfigError {
+	err := p.MatchConfig.Check()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	err = p.RemoteTrustConfig.Check()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	if p.Type == ProxyTypeFile {
+		err := p.File.Check()
+		if err != nil && err.IsError() {
+			return err
+		}
+	} else if p.Type == ProxyTypeDir {
+		err := p.Dir.Check()
+		if err != nil && err.IsError() {
+			return err
+		}
+	} else if p.Type == ProxyTypeAPI {
+		err := p.Api.Check()
+		if err != nil && err.IsError() {
+			return err
+		}
+	} else if p.Type == ProxyTypeRedirect {
+		err := p.Redirect.Check()
+		if err != nil && err.IsError() {
+			return err
+		}
+	} else {
+		return configerr.NewConfigError("proxy type must be file or dir or api")
+	}
+
+	return nil
+}

+ 26 - 0
src/config/rules/rulelistconfig.go

@@ -0,0 +1,26 @@
+package rules
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+)
+
+type RuleListConfig struct {
+	Rules []*RuleConfig `yaml:"rules"`
+}
+
+func (r *RuleListConfig) SetDefault() {
+	for _, rule := range r.Rules {
+		rule.SetDefault()
+	}
+}
+
+func (r *RuleListConfig) Check() configerr.ConfigError {
+	for _, rule := range r.Rules {
+		err := rule.Check()
+		if err != nil && err.IsError() {
+			return err
+		}
+	}
+
+	return nil
+}

+ 110 - 0
src/config/rulescompile/actioncompile/apicompile/api.go

@@ -0,0 +1,110 @@
+package apicompile
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/api"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/actioncompile/rewritecompile"
+	"net/http/httputil"
+	"net/url"
+)
+
+const XHuanProxyHeaer = api.XHuanProxyHeaer
+const ViaHeader = api.ViaHeader
+
+type RuleAPICompileConfig struct {
+	Address   string
+	TargetURL *url.URL
+	Server    *httputil.ReverseProxy
+	AddPath   string
+	SubPath   string
+	Rewrite   *rewritecompile.RewriteCompileConfig
+	HeaderSet []*HeaderCompileConfig
+	HeaderAdd []*HeaderCompileConfig
+	HeaderDel []*HeaderDelCompileConfig
+	QuerySet  []*QueryCompileConfig
+	QueryAdd  []*QueryCompileConfig
+	QueryDel  []*QueryDelCompileConfig
+	Via       string
+}
+
+func NewRuleAPICompileConfig(r *api.RuleAPIConfig) (*RuleAPICompileConfig, error) {
+	rewrite, err := rewritecompile.NewRewriteCompileConfig(&r.Rewrite)
+	if err != nil {
+		return nil, err
+	}
+
+	targetURL, err := url.Parse(r.Address)
+	if err != nil {
+		return nil, err
+	}
+
+	server := httputil.NewSingleHostReverseProxy(targetURL)
+
+	HeaderSet := make([]*HeaderCompileConfig, 0, len(r.HeaderSet))
+	for _, v := range r.HeaderSet {
+		h, err := NewHeaderCompileConfig(v)
+		if err != nil {
+			return nil, err
+		}
+		HeaderSet = append(HeaderSet, h)
+	}
+
+	HeaderAdd := make([]*HeaderCompileConfig, 0, len(r.HeaderAdd))
+	for _, v := range r.HeaderAdd {
+		h, err := NewHeaderCompileConfig(v)
+		if err != nil {
+			return nil, err
+		}
+		HeaderAdd = append(HeaderAdd, h)
+	}
+
+	HeaderDel := make([]*HeaderDelCompileConfig, 0, len(r.HeaderDel))
+	for _, v := range r.HeaderDel {
+		h, err := NewHeaderDelCompileConfig(v)
+		if err != nil {
+			return nil, err
+		}
+		HeaderDel = append(HeaderDel, h)
+	}
+
+	QuerySet := make([]*QueryCompileConfig, 0, len(r.QuerySet))
+	for _, v := range r.QuerySet {
+		q, err := NewQueryCompileConfig(v)
+		if err != nil {
+			return nil, err
+		}
+		QuerySet = append(QuerySet, q)
+	}
+
+	QueryAdd := make([]*QueryCompileConfig, 0, len(r.QueryAdd))
+	for _, v := range r.QueryAdd {
+		q, err := NewQueryCompileConfig(v)
+		if err != nil {
+			return nil, err
+		}
+		QueryAdd = append(QueryAdd, q)
+	}
+
+	QueryDel := make([]*QueryDelCompileConfig, 0, len(r.QueryDel))
+	for _, v := range r.QueryDel {
+		q, err := NewQueryDelCompileConfig(v)
+		if err != nil {
+			return nil, err
+		}
+		QueryDel = append(QueryDel, q)
+	}
+
+	return &RuleAPICompileConfig{
+		Address:   r.Address,
+		TargetURL: targetURL,
+		Server:    server,
+		AddPath:   r.AddPath,
+		SubPath:   r.SubPath,
+		Rewrite:   rewrite,
+		HeaderSet: HeaderSet,
+		HeaderAdd: HeaderAdd,
+		HeaderDel: HeaderDel,
+		QuerySet:  QuerySet,
+		QueryAdd:  QueryAdd,
+		Via:       r.Via,
+	}, nil
+}

+ 15 - 0
src/config/rulescompile/actioncompile/apicompile/headerconfig.go

@@ -0,0 +1,15 @@
+package apicompile
+
+import "github.com/SongZihuan/huan-proxy/src/config/rules/action/api"
+
+type HeaderCompileConfig struct {
+	Header string `yaml:"header"`
+	Value  string `yaml:"value"`
+}
+
+func NewHeaderCompileConfig(h *api.HeaderConfig) (*HeaderCompileConfig, error) {
+	return &HeaderCompileConfig{
+		Header: h.Header,
+		Value:  h.Value,
+	}, nil
+}

+ 13 - 0
src/config/rulescompile/actioncompile/apicompile/headerdelconfig.go

@@ -0,0 +1,13 @@
+package apicompile
+
+import "github.com/SongZihuan/huan-proxy/src/config/rules/action/api"
+
+type HeaderDelCompileConfig struct {
+	Header string `yaml:"header"`
+}
+
+func NewHeaderDelCompileConfig(h *api.HeaderDelConfig) (*HeaderDelCompileConfig, error) {
+	return &HeaderDelCompileConfig{
+		Header: h.Header,
+	}, nil
+}

+ 15 - 0
src/config/rulescompile/actioncompile/apicompile/queryconfig.go

@@ -0,0 +1,15 @@
+package apicompile
+
+import "github.com/SongZihuan/huan-proxy/src/config/rules/action/api"
+
+type QueryCompileConfig struct {
+	Query string `yaml:"query"`
+	Value string `yaml:"value"`
+}
+
+func NewQueryCompileConfig(q *api.QueryConfig) (*QueryCompileConfig, error) {
+	return &QueryCompileConfig{
+		Query: q.Query,
+		Value: q.Value,
+	}, nil
+}

+ 13 - 0
src/config/rulescompile/actioncompile/apicompile/querydelconfig.go

@@ -0,0 +1,13 @@
+package apicompile
+
+import "github.com/SongZihuan/huan-proxy/src/config/rules/action/api"
+
+type QueryDelCompileConfig struct {
+	Query string `yaml:"query"`
+}
+
+func NewQueryDelCompileConfig(q *api.QueryDelConfig) (*QueryDelCompileConfig, error) {
+	return &QueryDelCompileConfig{
+		Query: q.Query,
+	}, nil
+}

+ 77 - 0
src/config/rulescompile/actioncompile/corscompile/corsconfig.go

@@ -0,0 +1,77 @@
+package corscompile
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/cors"
+	"regexp"
+	"strings"
+)
+
+const CorsMaxAgeSec = cors.CorsMaxAgeSec
+const CorsDefaultMaxAgeSec = CorsMaxAgeSec
+const AllowAllOrigin = "*"
+
+type CorsCompileConfig struct {
+	Ignore         bool
+	AllowOrigin    []string
+	AllowOriginReg []*regexp.Regexp
+	MaxAgeSec      int
+}
+
+func NewCorsCompileConfig(c *cors.CorsConfig) (*CorsCompileConfig, error) {
+	if c.AllowCors.IsDisable(false) {
+		return &CorsCompileConfig{
+			Ignore:         true,
+			AllowOrigin:    make([]string, 0),
+			AllowOriginReg: make([]*regexp.Regexp, 0),
+			MaxAgeSec:      0,
+		}, nil
+	}
+
+	regexps := make([]*regexp.Regexp, 0, len(c.AllowOriginReg))
+	for _, v := range c.AllowOriginReg {
+		reg, err := regexp.Compile(v)
+		if err != nil {
+			return nil, err
+		}
+		regexps = append(regexps, reg)
+	}
+
+	res := &CorsCompileConfig{
+		Ignore:         false,
+		AllowOrigin:    c.AllowOrigin,
+		AllowOriginReg: regexps,
+		MaxAgeSec:      c.MaxAgeSec,
+	}
+
+	if res.MaxAgeSec >= CorsMaxAgeSec {
+		res.MaxAgeSec = CorsMaxAgeSec
+	} else if res.MaxAgeSec < 0 {
+		res.MaxAgeSec = CorsDefaultMaxAgeSec
+	}
+
+	return res, nil
+}
+
+func (c *CorsCompileConfig) InOriginList(origin string) bool {
+	if len(c.AllowOrigin) == 0 && len(c.AllowOriginReg) == 0 {
+		return false
+	}
+
+	origin = strings.TrimSpace(origin)
+
+	for _, org := range c.AllowOrigin {
+		org = strings.TrimSpace(org)
+		if org == AllowAllOrigin {
+			return true
+		} else if org == origin {
+			return true
+		}
+	}
+
+	for _, reg := range c.AllowOriginReg {
+		if reg != nil && reg.MatchString(origin) {
+			return true
+		}
+	}
+	return false
+}

+ 65 - 0
src/config/rulescompile/actioncompile/dircompile/dirconfig.go

@@ -0,0 +1,65 @@
+package dircompile
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/dir"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/actioncompile/corscompile"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/actioncompile/rewritecompile"
+)
+
+type RuleDirCompileConfig struct {
+	BasePath   string
+	IndexFile  []*IndexFileCompileConfig
+	IgnoreFile []*IgnoreFileCompileConfig
+	AddPath    string
+	SubPath    string
+	Rewrite    *rewritecompile.RewriteCompileConfig
+	Cors       *corscompile.CorsCompileConfig
+}
+
+func NewRuleDirCompileConfig(r *dir.RuleDirConfig) (*RuleDirCompileConfig, error) {
+	Index := make([]*IndexFileCompileConfig, 0, len(r.IndexFile))
+	for _, i := range r.IndexFile {
+		file, err := NewIndexFileCompileConfig(i)
+		if err != nil {
+			return nil, err
+		}
+		Index = append(Index, file)
+	}
+
+	Ignore := make([]*IgnoreFileCompileConfig, 0, len(r.IgnoreFile))
+	for _, i := range r.IgnoreFile {
+		file, err := NewIgnoreFileCompileConfig(i)
+		if err != nil {
+			return nil, err
+		}
+		Ignore = append(Ignore, file)
+	}
+
+	rewrite, err := rewritecompile.NewRewriteCompileConfig(&r.Rewrite)
+	if err != nil {
+		return nil, err
+	}
+
+	cors, err := corscompile.NewCorsCompileConfig(&r.Cors)
+	if err != nil {
+		return nil, err
+	}
+
+	return &RuleDirCompileConfig{
+		BasePath:   r.BasePath,
+		IndexFile:  Index,
+		IgnoreFile: Ignore,
+		AddPath:    r.AddPath,
+		SubPath:    r.SubPath,
+		Rewrite:    rewrite,
+		Cors:       cors,
+	}, nil
+}
+
+func (i *IgnoreFileCompileConfig) CheckName(name string) bool {
+	if i.IsRegex {
+		return i.Regex.MatchString(name)
+	} else {
+		return name == i.File
+	}
+}

+ 32 - 0
src/config/rulescompile/actioncompile/dircompile/ignorefileconfig.go

@@ -0,0 +1,32 @@
+package dircompile
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/dir"
+	"regexp"
+)
+
+type IgnoreFileCompileConfig struct {
+	IsRegex bool
+	File    string
+	Regex   *regexp.Regexp
+}
+
+func NewIgnoreFileCompileConfig(i *dir.IgnoreFileConfig) (*IgnoreFileCompileConfig, error) {
+	if i.Regex.IsEnable(true) {
+		reg, err := regexp.Compile(i.File)
+		if err != nil {
+			return nil, err
+		}
+		return &IgnoreFileCompileConfig{
+			IsRegex: true,
+			File:    "",
+			Regex:   reg,
+		}, nil
+	} else {
+		return &IgnoreFileCompileConfig{
+			IsRegex: false,
+			File:    i.File,
+			Regex:   nil,
+		}, nil
+	}
+}

+ 40 - 0
src/config/rulescompile/actioncompile/dircompile/indexfileconfig.go

@@ -0,0 +1,40 @@
+package dircompile
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/dir"
+	"regexp"
+)
+
+type IndexFileCompileConfig struct {
+	IsRegex bool
+	File    string
+	Regex   *regexp.Regexp
+}
+
+func NewIndexFileCompileConfig(i *dir.IndexFileConfig) (*IndexFileCompileConfig, error) {
+	if i.Regex.IsEnable(true) {
+		reg, err := regexp.Compile(i.File)
+		if err != nil {
+			return nil, err
+		}
+		return &IndexFileCompileConfig{
+			IsRegex: true,
+			File:    "",
+			Regex:   reg,
+		}, nil
+	} else {
+		return &IndexFileCompileConfig{
+			IsRegex: false,
+			File:    i.File,
+			Regex:   nil,
+		}, nil
+	}
+}
+
+func (i *IndexFileCompileConfig) CheckName(name string) bool {
+	if i.IsRegex {
+		return i.Regex.MatchString(name)
+	} else {
+		return name == i.File
+	}
+}

+ 23 - 0
src/config/rulescompile/actioncompile/filecompile/fileconfig.go

@@ -0,0 +1,23 @@
+package filecompile
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/file"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/actioncompile/corscompile"
+)
+
+type RuleFileCompileConfig struct {
+	Path string
+	Cors *corscompile.CorsCompileConfig
+}
+
+func NewRuleFileCompileConfig(f *file.RuleFileConfig) (*RuleFileCompileConfig, error) {
+	cors, err := corscompile.NewCorsCompileConfig(&f.Cors)
+	if err != nil {
+		return nil, err
+	}
+
+	return &RuleFileCompileConfig{
+		Path: f.Path,
+		Cors: cors,
+	}, nil
+}

+ 33 - 0
src/config/rulescompile/actioncompile/redirectcompile/redirectcompile.go

@@ -0,0 +1,33 @@
+package redirectcompile
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/redirect"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/actioncompile/rewritecompile"
+	"net/url"
+)
+
+type RuleRedirectCompileConfig struct {
+	Address   string
+	TargetURL *url.URL
+	Rewrite   *rewritecompile.RewriteCompileConfig
+	Code      int
+}
+
+func NewRuleAPICompileConfig(r *redirect.RuleRedirectConfig) (*RuleRedirectCompileConfig, error) {
+	rewrite, err := rewritecompile.NewRewriteCompileConfig(&r.Rewrite)
+	if err != nil {
+		return nil, err
+	}
+
+	targetURL, err := url.Parse(r.Address)
+	if err != nil {
+		return nil, err
+	}
+
+	return &RuleRedirectCompileConfig{
+		Address:   r.Address,
+		TargetURL: targetURL,
+		Rewrite:   rewrite,
+		Code:      r.Code,
+	}, nil
+}

+ 24 - 0
src/config/rulescompile/actioncompile/remotetrustcompile/remotetrustcompileconfig.go

@@ -0,0 +1,24 @@
+package remotetrustcompile
+
+import "github.com/SongZihuan/huan-proxy/src/config/rules/action/remotetrust"
+
+type RemoteTrustCompileConfig struct {
+	UseTrustedIPs bool
+	TrustedIPs    []string
+}
+
+func NewRemoteTrustCompileConfig(r *remotetrust.RemoteTrustConfig) (*RemoteTrustCompileConfig, error) {
+	if r.RemoteTrust.IsDisable(false) {
+		return &RemoteTrustCompileConfig{
+			UseTrustedIPs: false,
+			TrustedIPs:    make([]string, 0),
+		}, nil
+	} else {
+		trustedIPs := make([]string, len(r.TrustedIPs))
+		copy(trustedIPs, r.TrustedIPs)
+		return &RemoteTrustCompileConfig{
+			UseTrustedIPs: true,
+			TrustedIPs:    trustedIPs,
+		}, nil
+	}
+}

+ 33 - 0
src/config/rulescompile/actioncompile/rewritecompile/rewriteconfig.go

@@ -0,0 +1,33 @@
+package rewritecompile
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/rules/action/rewrite"
+	"regexp"
+)
+
+type RewriteCompileConfig struct {
+	Use    bool
+	Regex  *regexp.Regexp
+	Target string
+}
+
+func NewRewriteCompileConfig(r *rewrite.RewriteConfig) (*RewriteCompileConfig, error) {
+	if r.Regex == "" {
+		return &RewriteCompileConfig{
+			Use:    false,
+			Regex:  nil,
+			Target: "",
+		}, nil
+	}
+
+	reg, err := regexp.Compile(r.Regex)
+	if err != nil {
+		return nil, err
+	}
+
+	return &RewriteCompileConfig{
+		Use:    true,
+		Regex:  reg,
+		Target: r.Target,
+	}, nil
+}

+ 61 - 0
src/config/rulescompile/matchcompile/matchconfig.go

@@ -0,0 +1,61 @@
+package matchcompile
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config/rules/match"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"regexp"
+)
+
+const (
+	RulesPrefixMatch    = match.PrefixMatch    // 前缀匹配
+	RulesRegexMatch     = match.RegexMatch     // 正则匹配
+	RulesPrecisionMatch = match.PrecisionMatch // 精准匹配
+)
+
+const (
+	PrefixMatch    = iota // 前缀匹配
+	RegexMatch            // 正则匹配
+	PrecisionMatch        // 精准匹配
+)
+
+var MatchTypeMap = map[string]int{
+	RulesPrefixMatch:    PrefixMatch,
+	RulesRegexMatch:     RegexMatch,
+	RulesPrecisionMatch: PrecisionMatch,
+}
+
+type MatchCompileConfig struct {
+	MatchType  int
+	MatchPath  string         // Prefix和Precision使用
+	MatchRegex *regexp.Regexp // regex使用
+}
+
+func NewMatchConfig(m *match.MatchConfig) (*MatchCompileConfig, error) {
+	res := new(MatchCompileConfig)
+
+	matchType, ok := MatchTypeMap[m.MatchType]
+	if !ok {
+		return nil, fmt.Errorf("bad match type")
+	}
+
+	res.MatchType = matchType
+
+	if matchType == RegexMatch {
+		reg, err := regexp.Compile(m.MatchPath)
+		if err != nil {
+			return nil, err
+		}
+		res.MatchRegex = reg
+	} else if matchType == PrefixMatch || matchType == PrecisionMatch {
+		if !utils.IsValidURLPath(m.MatchPath) {
+			return nil, fmt.Errorf("bad path")
+		}
+
+		res.MatchPath = utils.ProcessURLPath(m.MatchPath)
+	} else {
+		return nil, fmt.Errorf("bad match type")
+	}
+
+	return res, nil
+}

+ 114 - 0
src/config/rulescompile/ruleconfig.go

@@ -0,0 +1,114 @@
+package rulescompile
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config/rules"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/actioncompile/apicompile"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/actioncompile/dircompile"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/actioncompile/filecompile"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/actioncompile/redirectcompile"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/actioncompile/remotetrustcompile"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/matchcompile"
+)
+
+const (
+	RulesProxyTypeFile     = rules.ProxyTypeFile
+	RulesProxyTypeDir      = rules.ProxyTypeDir
+	RulesProxyTypeAPI      = rules.ProxyTypeAPI
+	RulesProxyTypeRedirect = rules.ProxyTypeRedirect
+)
+
+const (
+	ProxyTypeFile = iota
+	ProxyTypeDir
+	ProxyTypeAPI
+	ProxyTypeRedirect
+)
+
+var ProxyTypeMap = map[string]int{
+	RulesProxyTypeFile:     ProxyTypeFile,
+	RulesProxyTypeDir:      ProxyTypeDir,
+	RulesProxyTypeAPI:      ProxyTypeAPI,
+	RulesProxyTypeRedirect: ProxyTypeRedirect,
+}
+
+type RuleCompileConfig struct {
+	Type int
+
+	*matchcompile.MatchCompileConfig
+	*remotetrustcompile.RemoteTrustCompileConfig
+
+	File     *filecompile.RuleFileCompileConfig
+	Dir      *dircompile.RuleDirCompileConfig
+	Api      *apicompile.RuleAPICompileConfig
+	Redirect *redirectcompile.RuleRedirectCompileConfig
+}
+
+func NewRuleCompileConfig(r *rules.RuleConfig) (*RuleCompileConfig, error) {
+	typeID, ok := ProxyTypeMap[r.Type]
+	if !ok {
+		return nil, fmt.Errorf("error rule type")
+	}
+
+	match, err := matchcompile.NewMatchConfig(&r.MatchConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	remoteTrusts, err := remotetrustcompile.NewRemoteTrustCompileConfig(&r.RemoteTrustConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	if typeID == ProxyTypeFile {
+		file, err := filecompile.NewRuleFileCompileConfig(&r.File)
+		if err != nil {
+			return nil, err
+		}
+
+		return &RuleCompileConfig{
+			Type:                     typeID,
+			MatchCompileConfig:       match,
+			RemoteTrustCompileConfig: remoteTrusts,
+			File:                     file,
+		}, nil
+	} else if typeID == ProxyTypeDir {
+		dir, err := dircompile.NewRuleDirCompileConfig(&r.Dir)
+		if err != nil {
+			return nil, err
+		}
+
+		return &RuleCompileConfig{
+			Type:                     typeID,
+			MatchCompileConfig:       match,
+			RemoteTrustCompileConfig: remoteTrusts,
+			Dir:                      dir,
+		}, nil
+	} else if typeID == ProxyTypeAPI {
+		api, err := apicompile.NewRuleAPICompileConfig(&r.Api)
+		if err != nil {
+			return nil, err
+		}
+
+		return &RuleCompileConfig{
+			Type:                     typeID,
+			MatchCompileConfig:       match,
+			RemoteTrustCompileConfig: remoteTrusts,
+			Api:                      api,
+		}, nil
+	} else if typeID == ProxyTypeRedirect {
+		redirect, err := redirectcompile.NewRuleAPICompileConfig(&r.Redirect)
+		if err != nil {
+			return nil, err
+		}
+
+		return &RuleCompileConfig{
+			Type:                     typeID,
+			MatchCompileConfig:       match,
+			RemoteTrustCompileConfig: remoteTrusts,
+			Redirect:                 redirect,
+		}, nil
+	} else {
+		return nil, fmt.Errorf("error rule type")
+	}
+}

+ 22 - 0
src/config/rulescompile/rulelistconfig.go

@@ -0,0 +1,22 @@
+package rulescompile
+
+import "github.com/SongZihuan/huan-proxy/src/config/rules"
+
+type RuleListCompileConfig struct {
+	Rules []*RuleCompileConfig `yaml:"rules"`
+}
+
+func NewRuleListConfig(rs *rules.RuleListConfig) (*RuleListCompileConfig, error) {
+	res := make([]*RuleCompileConfig, 0, len(rs.Rules))
+	for _, v := range rs.Rules {
+		r, err := NewRuleCompileConfig(v)
+		if err != nil {
+			return nil, err
+		}
+		res = append(res, r)
+	}
+
+	return &RuleListCompileConfig{
+		Rules: res,
+	}, nil
+}

+ 19 - 0
src/config/signalconfig.go

@@ -0,0 +1,19 @@
+package config
+
+import (
+	"fmt"
+	"os"
+	"os/signal"
+	"syscall"
+)
+
+func initSignal(sigChan chan os.Signal) (err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			err = fmt.Errorf("init signal error: %v", r)
+		}
+	}()
+
+	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+	return nil
+}

+ 58 - 0
src/config/yamlconfig.go

@@ -0,0 +1,58 @@
+package config
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/configerr"
+	"github.com/SongZihuan/huan-proxy/src/config/rules"
+	"gopkg.in/yaml.v3"
+	"os"
+)
+
+type YamlConfig struct {
+	GlobalConfig         `yaml:",inline"`
+	Http                 HttpConfig  `yaml:"http"`
+	Https                HttpsConfig `yaml:"https"`
+	rules.RuleListConfig `yaml:",inline"`
+}
+
+func (y *YamlConfig) Init() error {
+	return nil
+}
+
+func (y *YamlConfig) SetDefault() {
+	y.GlobalConfig.SetDefault()
+	y.Http.SetDefault()
+	y.RuleListConfig.SetDefault()
+}
+
+func (y *YamlConfig) Check() (err configerr.ConfigError) {
+	err = y.GlobalConfig.Check()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	err = y.Http.Check()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	err = y.RuleListConfig.Check()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	return nil
+}
+
+func (y *YamlConfig) Parser(filepath string) configerr.ParserError {
+	file, err := os.ReadFile(filepath)
+	if err != nil {
+		return configerr.NewParserError(err, err.Error())
+	}
+
+	err = yaml.Unmarshal(file, y)
+	if err != nil {
+		return configerr.NewParserError(err, err.Error())
+	}
+
+	return nil
+}

+ 370 - 0
src/flagparser/data.go

@@ -0,0 +1,370 @@
+package flagparser
+
+import (
+	"flag"
+	"fmt"
+	resource "github.com/SongZihuan/huan-proxy"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"io"
+	"reflect"
+	"strings"
+)
+
+const OptionIdent = "  "
+const OptionPrefix = "--"
+const UsagePrefixWidth = 10
+
+type flagData struct {
+	flagReady  bool
+	flagSet    bool
+	flagParser bool
+
+	HelpData               bool
+	HelpName               string
+	HelpUsage              string
+	VersionData            bool
+	VersionName            string
+	VersionUsage           string
+	LicenseData            bool
+	LicenseName            string
+	LicenseUsage           string
+	ReportData             bool
+	ReportName             string
+	ReportUsage            string
+	ConfigFileData         string
+	ConfigFileName         string
+	ConfigFileUsage        string
+	NotAutoReloadData      bool
+	NotAutoReloadName      string
+	NotAutoReloadShortName string
+	NotAutoReloadUsage     string
+
+	Usage string
+}
+
+func initData() {
+	data = flagData{
+		flagReady:  false,
+		flagSet:    false,
+		flagParser: false,
+
+		HelpData:               false,
+		HelpName:               "help",
+		HelpUsage:              fmt.Sprintf("Show usage of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
+		VersionData:            false,
+		VersionName:            "version",
+		VersionUsage:           fmt.Sprintf("Show version of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
+		LicenseData:            false,
+		LicenseName:            "license",
+		LicenseUsage:           fmt.Sprintf("Show license of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
+		ReportData:             false,
+		ReportName:             "report",
+		ReportUsage:            fmt.Sprintf("Show how to report questions/errors of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
+		ConfigFileData:         "config.yaml",
+		ConfigFileName:         "config",
+		ConfigFileUsage:        fmt.Sprintf("%s", "The location of the running configuration file of the backend service. The option is a string, the default value is config.yaml in the running directory."),
+		NotAutoReloadData:      false,
+		NotAutoReloadName:      "not-auto-reload",
+		NotAutoReloadShortName: "",
+		NotAutoReloadUsage:     fmt.Sprintf("%s", "Disable automatic detection of configuration file changes and reloading of system programs. This feature is enabled by default. This feature consumes a certain amount of performance. If your performance is not enough, you can choose to disable it."),
+		Usage:                  "",
+	}
+
+	data.ready()
+}
+
+func (d *flagData) writeUsage() {
+	if len(d.Usage) != 0 {
+		return
+	}
+
+	if d.isFlagSet() || d.isFlagParser() {
+		panic("flag is parser")
+	}
+
+	var result strings.Builder
+	result.WriteString(utils.FormatTextToWidth(fmt.Sprintf("Usage of %s:", utils.GetArgs0Name()), utils.NormalConsoleWidth))
+	result.WriteString("\n")
+
+	val := reflect.ValueOf(*d)
+	typ := val.Type()
+
+	for i := 0; i < val.NumField(); i++ {
+		field := typ.Field(i)
+
+		if !strings.HasSuffix(field.Name, "Data") {
+			continue
+		}
+
+		option := field.Name[:len(field.Name)-4]
+		optionName := ""
+		optionShortName := ""
+		optionUsage := ""
+
+		if utils.HasFieldByReflect(typ, option+"Name") {
+			var ok bool
+			optionName, ok = val.FieldByName(option + "Name").Interface().(string)
+			if !ok {
+				panic("can not get option name")
+			}
+		}
+
+		if utils.HasFieldByReflect(typ, option+"ShortName") {
+			var ok bool
+			optionShortName, ok = val.FieldByName(option + "ShortName").Interface().(string)
+			if !ok {
+				panic("can not get option short name")
+			}
+		} else if len(optionName) > 1 {
+			optionShortName = optionName[:1]
+		}
+
+		if utils.HasFieldByReflect(typ, option+"Usage") {
+			var ok bool
+			optionUsage, ok = val.FieldByName(option + "Usage").Interface().(string)
+			if !ok {
+				panic("can not get option usage")
+			}
+		}
+
+		var title string
+		var title1 string
+		var title2 string
+		if field.Type.Kind() == reflect.Bool {
+			var optionData bool
+			if utils.HasFieldByReflect(typ, option+"Data") {
+				var ok bool
+				optionData, ok = val.FieldByName(option + "Data").Interface().(bool)
+				if !ok {
+					panic("can not get option data")
+				}
+			}
+
+			if optionData == true {
+				panic("bool option can not be true")
+			}
+
+			if optionName != "" {
+				title1 = fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(optionName, utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			}
+
+			if optionShortName != "" {
+				title2 = fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(optionShortName, utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			}
+		} else if field.Type.Kind() == reflect.String {
+			var optionData string
+			if utils.HasFieldByReflect(typ, option+"Data") {
+				var ok bool
+				optionData, ok = val.FieldByName(option + "Data").Interface().(string)
+				if !ok {
+					panic("can not get option data")
+				}
+			}
+
+			if optionName != "" && optionData != "" {
+				title1 = fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(fmt.Sprintf("%s string, default: '%s'", optionName, optionData), utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			} else if optionName != "" && optionData == "" {
+				title1 = fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(fmt.Sprintf("%s string", optionName), utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			}
+
+			if optionShortName != "" && optionData != "" {
+				title2 = fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(fmt.Sprintf("%s string, default: '%s'", optionShortName, optionData), utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			} else if optionShortName != "" && optionData == "" {
+				title2 = fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(fmt.Sprintf("%s string", optionShortName), utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			}
+		} else if field.Type.Kind() == reflect.Uint {
+			var optionData uint
+			if utils.HasFieldByReflect(typ, option+"Data") {
+				var ok bool
+				optionData, ok = val.FieldByName(option + "Data").Interface().(uint)
+				if !ok {
+					panic("can not get option data")
+				}
+			}
+
+			if optionName != "" && optionData != 0 {
+				title1 = fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(fmt.Sprintf("%s number, default: %d", optionName, optionData), utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			} else if optionName != "" && optionData == 0 {
+				title1 = fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(fmt.Sprintf("%s number", optionName), utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			}
+
+			if optionShortName != "" && optionData != 0 {
+				title2 = fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(fmt.Sprintf("%s number, default: %d", optionShortName, optionData), utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			} else if optionShortName != "" && optionData == 0 {
+				title2 = fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(fmt.Sprintf("%s number", optionShortName), utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			}
+		} else {
+			panic("error flag type")
+		}
+
+		if title1 == "" && title2 == "" {
+			continue
+		} else if title1 != "" && title2 == "" {
+			title = title1
+		} else if title1 == "" {
+			title = title2
+		} else {
+			title = fmt.Sprintf("%s\n%s", title1, title2)
+		}
+
+		result.WriteString(title)
+		result.WriteString("\n")
+
+		usegae := utils.FormatTextToWidthAndPrefix(optionUsage, UsagePrefixWidth, utils.NormalConsoleWidth)
+		result.WriteString(usegae)
+		result.WriteString("\n\n")
+	}
+
+	d.Usage = strings.TrimRight(result.String(), "\n")
+}
+
+func (d *flagData) setFlag() {
+	if d.isFlagSet() {
+		return
+	}
+
+	flag.BoolVar(&d.HelpData, data.HelpName, data.HelpData, data.HelpUsage)
+	flag.BoolVar(&d.HelpData, data.HelpName[0:1], data.HelpData, data.HelpUsage)
+
+	flag.BoolVar(&d.VersionData, data.VersionName, data.VersionData, data.VersionUsage)
+	flag.BoolVar(&d.VersionData, data.VersionName[0:1], data.VersionData, data.VersionUsage)
+
+	flag.BoolVar(&d.LicenseData, data.LicenseName, data.LicenseData, data.LicenseUsage)
+	flag.BoolVar(&d.LicenseData, data.LicenseName[0:1], data.LicenseData, data.LicenseUsage)
+
+	flag.BoolVar(&d.ReportData, data.ReportName, data.ReportData, data.ReportUsage)
+	flag.BoolVar(&d.ReportData, data.ReportName[0:1], data.ReportData, data.ReportUsage)
+
+	flag.StringVar(&d.ConfigFileData, data.ConfigFileName, data.ConfigFileData, data.ConfigFileUsage)
+	flag.StringVar(&d.ConfigFileData, data.ConfigFileName[0:1], data.ConfigFileData, data.ConfigFileUsage)
+
+	flag.Usage = func() {
+		_, _ = d.PrintUsage()
+	}
+	d.flagSet = true
+}
+
+func (d *flagData) parser() {
+	if d.flagParser {
+		return
+	}
+
+	if !d.isFlagSet() {
+		panic("flag not set")
+	}
+
+	flag.Parse()
+	d.flagParser = true
+}
+
+func (d *flagData) ready() {
+	if d.isReady() {
+		return
+	}
+
+	d.writeUsage()
+	d.setFlag()
+	d.parser()
+	d.flagReady = true
+}
+
+func (d *flagData) isReady() bool {
+	return d.isFlagSet() && d.isFlagParser() && d.flagReady
+}
+
+func (d *flagData) isFlagSet() bool {
+	return d.flagSet
+}
+
+func (d *flagData) isFlagParser() bool {
+	return d.flagParser
+}
+
+func (d *flagData) Help() bool {
+	if !d.isReady() {
+		panic("flag not ready")
+	}
+
+	return d.HelpData
+}
+
+func (d *flagData) FprintUsage(writer io.Writer) (int, error) {
+	return fmt.Fprintf(writer, "%s\n", d.Usage)
+}
+
+func (d *flagData) PrintUsage() (int, error) {
+	return d.FprintUsage(flag.CommandLine.Output())
+}
+
+func (d *flagData) Version() bool {
+	if !d.isReady() {
+		panic("flag not ready")
+	}
+
+	return d.VersionData
+}
+
+func (d *flagData) FprintVersion(writer io.Writer) (int, error) {
+	version := utils.FormatTextToWidth(fmt.Sprintf("Version of %s: %s", utils.GetArgs0Name(), resource.Version), utils.NormalConsoleWidth)
+	return fmt.Fprintf(writer, "%s\n", version)
+}
+
+func (d *flagData) PrintVersion() (int, error) {
+	return d.FprintVersion(flag.CommandLine.Output())
+}
+
+func (d *flagData) FprintLicense(writer io.Writer) (int, error) {
+	title := utils.FormatTextToWidth(fmt.Sprintf("License of %s:", utils.GetArgs0Name()), utils.NormalConsoleWidth)
+	license := utils.FormatTextToWidth(resource.License, utils.NormalConsoleWidth)
+	return fmt.Fprintf(writer, "%s\n%s\n", title, license)
+}
+
+func (d *flagData) PrintLicense() (int, error) {
+	return d.FprintLicense(flag.CommandLine.Output())
+}
+
+func (d *flagData) FprintReport(writer io.Writer) (int, error) {
+	// 不需要title
+	report := utils.FormatTextToWidth(resource.Report, utils.NormalConsoleWidth)
+	return fmt.Fprintf(writer, "%s\n", report)
+}
+
+func (d *flagData) PrintReport() (int, error) {
+	return d.FprintReport(flag.CommandLine.Output())
+}
+
+func (d *flagData) FprintLF(writer io.Writer) (int, error) {
+	return fmt.Fprintf(writer, "\n")
+}
+
+func (d *flagData) PrintLF() (int, error) {
+	return d.FprintLF(flag.CommandLine.Output())
+}
+
+func (d *flagData) License() bool {
+	if !d.isReady() {
+		panic("flag not ready")
+	}
+
+	return d.LicenseData
+}
+
+func (d *flagData) Report() bool {
+	if !d.isReady() {
+		panic("flag not ready")
+	}
+
+	return d.ReportData
+}
+
+func (d *flagData) ConfigFile() string {
+	if !d.isReady() {
+		panic("flag not ready")
+	}
+
+	return d.ConfigFileData
+}
+
+func (d *flagData) SetOutput(writer io.Writer) {
+	flag.CommandLine.SetOutput(writer)
+}

+ 29 - 0
src/flagparser/error.go

@@ -0,0 +1,29 @@
+package flagparser
+
+import "fmt"
+
+type FlagError interface {
+	error
+	Error() string
+	Data() interface{}
+}
+
+type flagError struct {
+	msg  string
+	data interface{}
+}
+
+func NewFlagError(data interface{}, msg ...string) FlagError {
+	if len(msg) == 1 {
+		return &flagError{msg[0], data}
+	}
+	return &flagError{"flag error: " + fmt.Sprint(data), data}
+}
+
+func (e *flagError) Error() string {
+	return e.msg
+}
+
+func (e *flagError) Data() interface{} {
+	return e.data
+}

+ 82 - 0
src/flagparser/flag.go

@@ -0,0 +1,82 @@
+package flagparser
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"os"
+)
+
+var isReady = false
+
+func IsReady() bool {
+	return data.isReady() && isReady
+}
+
+var StopFlag = fmt.Errorf("stop")
+
+func InitFlag() (err error) {
+	if isReady {
+		return nil
+	}
+
+	defer func() {
+		if e := recover(); e != nil {
+			err = NewFlagError(e)
+			return
+		}
+	}()
+
+	initData()
+
+	SetOutput(os.Stdout)
+
+	var hasPrint = false
+
+	if Version() {
+		_, _ = PrintVersion()
+		hasPrint = true
+	}
+
+	if License() {
+		if hasPrint {
+			_, _ = PrintLF()
+		}
+		_, _ = PrintLicense()
+		hasPrint = true
+	}
+
+	if Report() {
+		if hasPrint {
+			_, _ = PrintLF()
+		}
+		_, _ = PrintReport()
+	}
+
+	if Help() {
+		if hasPrint {
+			_, _ = PrintLF()
+		}
+		_, _ = PrintUsage()
+		hasPrint = true
+	}
+
+	if NotRunMode() {
+		return StopFlag
+	}
+
+	err = checkFlag()
+	if err != nil {
+		return err
+	}
+
+	isReady = true
+	return nil
+}
+
+func checkFlag() error {
+	if !utils.IsExists(ConfigFile()) {
+		return fmt.Errorf("config file not exists")
+	}
+
+	return nil
+}

+ 111 - 0
src/flagparser/main.go

@@ -0,0 +1,111 @@
+package flagparser
+
+import (
+	"fmt"
+	"io"
+	"strings"
+)
+
+var data flagData
+
+func Help() bool {
+	return data.Help()
+}
+
+func FprintUsage(writer io.Writer) (int, error) {
+	return data.FprintUsage(writer)
+}
+
+func PrintUsage() (int, error) {
+	return data.PrintUsage()
+}
+
+func FprintVersion(writer io.Writer) (int, error) {
+	return data.FprintVersion(writer)
+}
+
+func PrintVersion() (int, error) {
+	return data.PrintVersion()
+}
+
+func FprintLicense(writer io.Writer) (int, error) {
+	return data.FprintLicense(writer)
+}
+
+func PrintLicense() (int, error) {
+	return data.PrintLicense()
+}
+
+func FprintReport(writer io.Writer) (int, error) {
+	return data.FprintReport(writer)
+}
+
+func PrintReport() (int, error) {
+	return data.PrintReport()
+}
+
+func FprintLF(writer io.Writer) (int, error) {
+	return data.FprintLF(writer)
+}
+
+func PrintLF() (int, error) {
+	return data.PrintLF()
+}
+
+func Version() bool {
+	return data.Version()
+}
+
+func License() bool {
+	return data.License()
+}
+
+func Report() bool {
+	return data.Report()
+}
+
+func NotRunMode() bool {
+	return Help() || Version() || License() || Report()
+}
+
+func NotRunModeOption() string {
+	if !NotRunMode() {
+		return ""
+	}
+
+	var result strings.Builder
+
+	if data.Help() {
+		result.WriteString(fmt.Sprintf("%s%s, ", OptionPrefix, data.HelpName))
+	}
+
+	if data.Version() {
+		result.WriteString(fmt.Sprintf("%s%s, ", OptionPrefix, data.VersionName))
+	}
+
+	if data.License() {
+		result.WriteString(fmt.Sprintf("%s%s, ", OptionPrefix, data.LicenseName))
+	}
+
+	if data.Report() {
+		result.WriteString(fmt.Sprintf("%s%s, ", OptionPrefix, data.ReportName))
+	}
+
+	return strings.TrimSuffix(result.String(), ", ")
+}
+
+func ConfigFile() string {
+	return data.ConfigFile()
+}
+
+func NotRunAutoReload() bool {
+	return data.NotAutoReloadData
+}
+
+func RunAutoReload() bool {
+	return !NotRunAutoReload()
+}
+
+func SetOutput(writer io.Writer) {
+	data.SetOutput(writer)
+}

+ 373 - 0
src/logger/logger.go

@@ -0,0 +1,373 @@
+package logger
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"github.com/mattn/go-isatty"
+	"io"
+	"os"
+)
+
+type LoggerLevel string
+
+const (
+	LevelDebug LoggerLevel = "debug"
+	LevelInfo  LoggerLevel = "info"
+	LevelWarn  LoggerLevel = "warn"
+	LevelError LoggerLevel = "error"
+	LevelPanic LoggerLevel = "panic"
+	LevelNone  LoggerLevel = "none"
+)
+
+type loggerLevel int64
+
+const (
+	levelDebug loggerLevel = 1
+	levelInfo  loggerLevel = 2
+	levelWarn  loggerLevel = 3
+	levelError loggerLevel = 4
+	levelPanic loggerLevel = 5
+	levelNone  loggerLevel = 6
+)
+
+var levelMap = map[LoggerLevel]loggerLevel{
+	LevelDebug: levelDebug,
+	LevelInfo:  levelInfo,
+	LevelWarn:  levelWarn,
+	LevelError: levelError,
+	LevelPanic: levelPanic,
+	LevelNone:  levelNone,
+}
+
+type Logger struct {
+	level      LoggerLevel
+	logLevel   loggerLevel
+	logTag     bool
+	warnWriter io.Writer
+	errWriter  io.Writer
+	args0      string
+	args0Name  string
+}
+
+var globalLogger *Logger = nil
+var DefaultWarnWriter = os.Stdout
+var DefaultErrorWriter = os.Stderr
+
+func InitLogger(warnWriter, errWriter io.Writer) error {
+	if !config.IsReady() {
+		panic("config is not ready")
+	}
+
+	level := LoggerLevel(config.GetConfig().GlobalConfig.LogLevel)
+	logLevel, ok := levelMap[level]
+	if !ok {
+		return fmt.Errorf("invalid log level: %s", level)
+	}
+
+	if warnWriter == nil {
+		warnWriter = DefaultWarnWriter
+	}
+
+	if errWriter == nil {
+		errWriter = DefaultErrorWriter
+	}
+
+	logger := &Logger{
+		level:      level,
+		logLevel:   logLevel,
+		logTag:     config.GetConfig().LogTag.ToBool(true),
+		warnWriter: os.Stdout,
+		errWriter:  os.Stderr,
+		args0:      utils.GetArgs0(),
+		args0Name:  utils.GetArgs0Name(),
+	}
+
+	globalLogger = logger
+	return nil
+}
+
+func IsReady() bool {
+	return globalLogger != nil
+}
+
+func (l *Logger) Executablef(format string, args ...interface{}) string {
+	str := fmt.Sprintf(format, args...)
+	if str == "" {
+		_, _ = fmt.Fprintf(l.warnWriter, "[Executable]: %s\n", l.args0)
+	} else {
+		_, _ = fmt.Fprintf(l.warnWriter, "{Executable %s]: %s\n", l.args0, str)
+	}
+	return l.args0
+}
+
+func (l *Logger) Tagf(format string, args ...interface{}) {
+	l.TagSkipf(1, format, args...)
+}
+
+func (l *Logger) TagSkipf(skip int, format string, args ...interface{}) {
+	if !l.logTag {
+		return
+	}
+
+	funcName, file, _, line := utils.GetCallingFunctionInfo(skip + 1)
+
+	str := fmt.Sprintf(format, args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "[Tag %s]: %s %s %s:%d\n", l.args0Name, str, funcName, file, line)
+}
+
+func (l *Logger) Debugf(format string, args ...interface{}) {
+	if l.logLevel > levelDebug {
+		return
+	}
+
+	str := fmt.Sprintf(format, args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "[Debug %s]: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Infof(format string, args ...interface{}) {
+	if l.logLevel > levelInfo {
+		return
+	}
+
+	str := fmt.Sprintf(format, args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "[Info %s]: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Warnf(format string, args ...interface{}) {
+	if l.logLevel > levelWarn {
+		return
+	}
+
+	str := fmt.Sprintf(format, args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "[Warning %s]: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Errorf(format string, args ...interface{}) {
+	if l.logLevel > levelError {
+		return
+	}
+
+	str := fmt.Sprintf(format, args...)
+	_, _ = fmt.Fprintf(l.errWriter, "[Error %s]: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Panicf(format string, args ...interface{}) {
+	if l.logLevel > levelPanic {
+		return
+	}
+
+	str := fmt.Sprintf(format, args...)
+	_, _ = fmt.Fprintf(l.errWriter, "[Panic %s]: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Tag(args ...interface{}) {
+	l.TagSkip(1, args...)
+}
+
+func (l *Logger) TagSkip(skip int, args ...interface{}) {
+	if !l.logTag {
+		return
+	}
+
+	funcName, file, _, line := utils.GetCallingFunctionInfo(skip + 1)
+
+	str := fmt.Sprint(args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "[Tag %s]: %s %s %s:%d\n", l.args0Name, str, funcName, file, line)
+}
+
+func (l *Logger) Debug(args ...interface{}) {
+	if l.logLevel > levelDebug {
+		return
+	}
+
+	str := fmt.Sprint(args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "[Debug %s]: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Info(args ...interface{}) {
+	if l.logLevel > levelInfo {
+		return
+	}
+
+	str := fmt.Sprint(args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "[Info %s]: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Warn(args ...interface{}) {
+	if l.logLevel > levelWarn {
+		return
+	}
+
+	str := fmt.Sprint(args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "[Warning %s]: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Error(args ...interface{}) {
+	if l.logLevel > levelError {
+		return
+	}
+
+	str := fmt.Sprint(args...)
+	_, _ = fmt.Fprintf(l.errWriter, "[Error %s]: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Panic(args ...interface{}) {
+	if l.logLevel > levelPanic {
+		return
+	}
+
+	str := fmt.Sprint(args...)
+	_, _ = fmt.Fprintf(l.errWriter, "[Panic %s]: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) TagWrite(msg string) {
+	l.TagSkipWrite(1, msg)
+}
+
+func (l *Logger) TagSkipWrite(skip int, msg string) {
+	if !l.logTag {
+		return
+	}
+
+	funcName, file, _, line := utils.GetCallingFunctionInfo(skip + 1)
+
+	_, _ = fmt.Fprintf(l.warnWriter, "[Debug %s]: %s %s %s:%d\n", l.args0Name, msg, funcName, file, line)
+}
+
+func (l *Logger) DebugWrite(msg string) {
+	if l.logLevel > levelDebug {
+		return
+	}
+
+	_, _ = fmt.Fprintf(l.warnWriter, "[Debug %s]: %s\n", l.args0Name, msg)
+}
+
+func (l *Logger) InfoWrite(msg string) {
+	if l.logLevel > levelInfo {
+		return
+	}
+
+	_, _ = fmt.Fprintf(l.warnWriter, "[Info %s]: %s\n", l.args0Name, msg)
+}
+
+func (l *Logger) WarnWrite(msg string) {
+	if l.logLevel > levelWarn {
+		return
+	}
+
+	_, _ = fmt.Fprintf(l.warnWriter, "[Warning %s]: %s\n", l.args0Name, msg)
+}
+
+func (l *Logger) ErrorWrite(msg string) {
+	if l.logLevel > levelError {
+		return
+	}
+
+	_, _ = fmt.Fprintf(l.errWriter, "[Error %s]: %s\n", l.args0Name, msg)
+}
+
+func (l *Logger) PanicWrite(msg string) {
+	if l.logLevel > levelPanic {
+		return
+	}
+
+	_, _ = fmt.Fprintf(l.errWriter, "[Panic %s]: %s\n", l.args0Name, msg)
+}
+
+func (l *Logger) GetDebugWriter() io.Writer {
+	return l.warnWriter
+}
+
+func (l *Logger) GetInfoWriter() io.Writer {
+	return l.warnWriter
+}
+
+func (l *Logger) GetWarningWriter() io.Writer {
+	return l.warnWriter
+}
+
+func (l *Logger) GetTagWriter() io.Writer {
+	return l.warnWriter
+}
+
+func (l *Logger) GetErrorWriter() io.Writer {
+	return l.errWriter
+}
+
+func (l *Logger) GetPanicWriter() io.Writer {
+	return l.errWriter
+}
+
+func (l *Logger) isWarnWriterTerm() bool {
+	w, ok := l.warnWriter.(*os.File)
+	if !ok {
+		return false
+	} else if !isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()) { // 非终端
+		return false
+	}
+	return true
+}
+
+func (l *Logger) isErrWriterTerm() bool {
+	w, ok := l.errWriter.(*os.File)
+	if !ok {
+		return false
+	} else if !isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()) { // 非终端
+		return false
+	}
+	return true
+}
+
+func (l *Logger) isTermDump() bool {
+	// TERM为dump表示终端为基础模式,不支持高级显示
+	return os.Getenv("TERM") == "dumb"
+}
+
+func (l *Logger) IsDebugTerm() bool {
+	return l.isWarnWriterTerm()
+}
+
+func (l *Logger) IsInfoTerm() bool {
+	return l.isWarnWriterTerm()
+}
+
+func (l *Logger) IsWarnTerm() bool {
+	return l.isWarnWriterTerm()
+}
+
+func (l *Logger) IsTagTerm() bool {
+	return l.isWarnWriterTerm()
+}
+
+func (l *Logger) IsErrorTerm() bool {
+	return l.isErrWriterTerm()
+}
+
+func (l *Logger) IsPanicTerm() bool {
+	return l.isErrWriterTerm()
+}
+
+func (l *Logger) IsDebugTermNotDumb() bool {
+	return l.isWarnWriterTerm() && !l.isTermDump()
+}
+
+func (l *Logger) IsInfoTermNotDumb() bool {
+	return l.isWarnWriterTerm() && !l.isTermDump()
+}
+
+func (l *Logger) IsWarnTermNotDumb() bool {
+	return l.isWarnWriterTerm() && !l.isTermDump()
+}
+
+func (l *Logger) IsTagTermNotDumb() bool {
+	return l.isWarnWriterTerm() && !l.isTermDump()
+}
+
+func (l *Logger) IsErrorTermNotDumb() bool {
+	return l.isErrWriterTerm() && !l.isTermDump()
+}
+
+func (l *Logger) IsPanicTermNotDumb() bool {
+	return l.isErrWriterTerm() && !l.isTermDump()
+}

+ 264 - 0
src/logger/main.go

@@ -0,0 +1,264 @@
+package logger
+
+import (
+	"io"
+)
+
+func Executablef(format string, args ...interface{}) string {
+	if !IsReady() {
+		return ""
+	}
+	return globalLogger.Executablef(format, args...)
+}
+
+func Tagf(format string, args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.TagSkipf(1, format, args...)
+}
+
+func Debugf(format string, args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Debugf(format, args...)
+}
+
+func Infof(format string, args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Infof(format, args...)
+}
+
+func Warnf(format string, args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Warnf(format, args...)
+}
+
+func Errorf(format string, args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Errorf(format, args...)
+}
+
+func Panicf(format string, args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Panicf(format, args...)
+}
+
+func Tag(args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.TagSkip(1, args...)
+}
+
+func Debug(args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Debug(args...)
+}
+
+func Info(args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Info(args...)
+}
+
+func Warn(args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Warn(args...)
+}
+
+func Error(args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Error(args...)
+}
+
+func Panic(args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Panic(args...)
+}
+
+func TagWrite(msg string) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.TagSkip(1, msg)
+}
+
+func DebugWrite(msg string) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.DebugWrite(msg)
+}
+
+func InfoWrite(msg string) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.InfoWrite(msg)
+}
+
+func WarnWrite(msg string) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.WarnWrite(msg)
+}
+
+func ErrorWrite(msg string) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.ErrorWrite(msg)
+}
+
+func PanicWrite(msg string) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.PanicWrite(msg)
+}
+
+func GetDebugWriter() io.Writer {
+	if !IsReady() {
+		return DefaultWarnWriter
+	}
+	return globalLogger.GetDebugWriter()
+}
+
+func GetInfoWriter() io.Writer {
+	if !IsReady() {
+		return DefaultWarnWriter
+	}
+	return globalLogger.GetInfoWriter()
+}
+
+func GetWarningWriter() io.Writer {
+	if !IsReady() {
+		return DefaultWarnWriter
+	}
+	return globalLogger.GetWarningWriter()
+}
+
+func GetTagWriter() io.Writer {
+	if !IsReady() {
+		return DefaultWarnWriter
+	}
+	return globalLogger.GetTagWriter()
+}
+
+func GetErrorWriter() io.Writer {
+	if !IsReady() {
+		return DefaultWarnWriter
+	}
+	return globalLogger.GetErrorWriter()
+}
+
+func GetPanicWriter() io.Writer {
+	if !IsReady() {
+		return DefaultWarnWriter
+	}
+	return globalLogger.GetPanicWriter()
+}
+
+func IsDebugTerm() bool {
+	if !IsReady() {
+		return false
+	}
+	return globalLogger.IsDebugTerm()
+}
+
+func IsInfoTerm() bool {
+	if !IsReady() {
+		return false
+	}
+	return globalLogger.IsDebugTerm()
+}
+
+func IsTagTerm() bool {
+	if !IsReady() {
+		return false
+	}
+	return globalLogger.IsTagTerm()
+}
+
+func IsWarnTerm() bool {
+	if !IsReady() {
+		return false
+	}
+	return globalLogger.IsWarnTerm()
+}
+
+func IsErrorTerm() bool {
+	if !IsReady() {
+		return false
+	}
+	return globalLogger.IsErrorTerm()
+}
+
+func IsPanicTerm() bool {
+	if !IsReady() {
+		return false
+	}
+	return globalLogger.IsPanicTerm()
+}
+
+func IsDebugTermNotDumb() bool {
+	if !IsReady() {
+		return false
+	}
+	return globalLogger.IsDebugTerm()
+}
+
+func IsInfoTermNotDumb() bool {
+	if !IsReady() {
+		return false
+	}
+	return globalLogger.IsInfoTermNotDumb()
+}
+
+func IsTagTermNotDumb() bool {
+	if !IsReady() {
+		return false
+	}
+	return globalLogger.IsTagTermNotDumb()
+}
+
+func IsWarnTermNotDumb() bool {
+	if !IsReady() {
+		return false
+	}
+	return globalLogger.IsWarnTermNotDumb()
+}
+
+func IsErrorTermNotDumb() bool {
+	if !IsReady() {
+		return false
+	}
+	return globalLogger.IsErrorTermNotDumb()
+}
+
+func IsPanicTermNotDumb() bool {
+	if !IsReady() {
+		return false
+	}
+	return globalLogger.IsPanicTermNotDumb()
+}

+ 99 - 0
src/mainfunc/v1.go

@@ -0,0 +1,99 @@
+package mainfunc
+
+import (
+	"errors"
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config"
+	"github.com/SongZihuan/huan-proxy/src/config/configwatcher"
+	"github.com/SongZihuan/huan-proxy/src/flagparser"
+	"github.com/SongZihuan/huan-proxy/src/logger"
+	"github.com/SongZihuan/huan-proxy/src/server"
+	"github.com/SongZihuan/huan-proxy/src/server/httpserver"
+	"github.com/SongZihuan/huan-proxy/src/server/httpsserver"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"os"
+)
+
+func MainV1() int {
+	var err error
+
+	err = flagparser.InitFlag()
+	if errors.Is(err, flagparser.StopFlag) {
+		return 0
+	} else if err != nil {
+		return utils.ExitByError(err)
+	}
+
+	if !flagparser.IsReady() {
+		return utils.ExitByErrorMsg("flag parser unknown error")
+	}
+
+	utils.SayHellof("%s", "The backend service program starts normally, thank you.")
+	defer func() {
+		utils.SayGoodByef("%s", "The backend service program is offline/shutdown normally, thank you.")
+	}()
+
+	cfgErr := config.InitConfig(flagparser.ConfigFile())
+	if cfgErr != nil && cfgErr.IsError() {
+		return utils.ExitByError(cfgErr)
+	}
+
+	if !config.IsReady() {
+		return utils.ExitByErrorMsg("config parser unknown error")
+	}
+
+	err = logger.InitLogger(os.Stdout, os.Stderr)
+	if err != nil {
+		return utils.ExitByError(err)
+	}
+
+	if !logger.IsReady() {
+		return utils.ExitByErrorMsg("logger unknown error")
+	}
+
+	if flagparser.RunAutoReload() {
+		err = configwatcher.WatcherConfigFile()
+		if err != nil {
+			return utils.ExitByError(err)
+		}
+		defer configwatcher.CloseNotifyConfigFile()
+
+		logger.Infof("Auto reload enable.")
+	} else {
+		logger.Infof("Auto reload disable.")
+	}
+
+	logger.Executablef("%s", "ready")
+	logger.Infof("run mode: %s", config.GetConfig().GlobalConfig.GetRunMode())
+
+	ser := server.NewHuanProxyServer()
+
+	httpschan := make(chan error)
+	httpchan := make(chan error)
+
+	err = ser.Run(httpschan, httpchan)
+	if err != nil {
+		return utils.ExitByErrorMsg(fmt.Sprintf("run http/https error: %s", err.Error()))
+	}
+
+	select {
+	case <-config.GetSignalChan():
+		return 0
+	case err := <-httpchan:
+		if errors.Is(err, httpserver.ServerStop) {
+			return 0
+		} else if err != nil {
+			return utils.ExitByError(err)
+		} else {
+			return 0
+		}
+	case err := <-httpschan:
+		if errors.Is(err, httpsserver.ServerStop) {
+			return 0
+		} else if err != nil {
+			return utils.ExitByError(err)
+		} else {
+			return 0
+		}
+	}
+}

+ 89 - 0
src/server/context/context.go

@@ -0,0 +1,89 @@
+package context
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile"
+	"github.com/SongZihuan/huan-proxy/src/server/request/proxyrequest"
+	"github.com/SongZihuan/huan-proxy/src/server/request/readonlyrequest"
+	"github.com/SongZihuan/huan-proxy/src/server/responsewriter"
+	"net/http"
+)
+
+type Context struct {
+	Abort        bool
+	resp         http.ResponseWriter
+	req          *http.Request
+	Writer       *responsewriter.ResponseWriter
+	Request      *readonlyrequest.ReadOnlyRequest
+	ProxyRequest *proxyrequest.ProxyRequest
+	Rule         *rulescompile.RuleCompileConfig
+}
+
+func NewContext(rule *rulescompile.RuleCompileConfig, w http.ResponseWriter, r *http.Request) *Context {
+	var proxyRequest *proxyrequest.ProxyRequest = nil
+	if rule.Type == rulescompile.ProxyTypeAPI {
+		proxyRequest = proxyrequest.NewRequest(r)
+	}
+
+	return &Context{
+		resp:         w,
+		req:          r,
+		Writer:       responsewriter.NewResponseWriter(w),
+		Request:      readonlyrequest.NewReadOnlyRequest(r),
+		ProxyRequest: proxyRequest,
+		Rule:         rule,
+	}
+}
+
+func (ctx *Context) ProxyWriteToHttpRRequest() (*http.Request, error) {
+	if ctx.ProxyRequest == nil {
+		return nil, fmt.Errorf("proxy request is nil")
+	}
+
+	req, err := ctx.ProxyRequest.WriteToHttpRRequest()
+	if err != nil {
+		return nil, err
+	}
+
+	return req, nil
+}
+
+func (ctx *Context) WriteToResponse() error {
+	err := ctx.Writer.WriteToResponse()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (ctx *Context) MustWriteToResponse() {
+	ctx.Writer.MustWriteToResponse()
+}
+
+func (ctx *Context) Reset() error {
+	ctx.Abort = false
+
+	_ = ctx.Writer.Reset()
+
+	if ctx.ProxyRequest != nil {
+		_ = ctx.ProxyRequest.Reset()
+	}
+
+	return nil
+}
+
+func (ctx *Context) StatusOK() {
+	if ctx.Abort {
+		return
+	}
+
+	ctx.Writer.WriteHeader(http.StatusOK)
+}
+
+func (ctx *Context) Redirect(target string, code int) {
+	if ctx.Abort {
+		return
+	}
+
+	http.Redirect(ctx.Writer, ctx.req, target, code)
+}

+ 44 - 0
src/server/core/abort.go

@@ -0,0 +1,44 @@
+package core
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/server/context"
+	"net/http"
+)
+
+func (c *CoreServer) abort(ctx *context.Context, code int) {
+	if ctx.Abort {
+		return
+	}
+
+	ctx.Abort = true
+	err := ctx.Writer.Reset()
+	if err != nil {
+		ctx.Writer.WriteHeader(http.StatusInternalServerError)
+	} else {
+		ctx.Writer.WriteHeader(code)
+	}
+}
+
+func (c *CoreServer) abortForbidden(ctx *context.Context) {
+	c.abort(ctx, http.StatusForbidden)
+}
+
+func (c *CoreServer) abortNotFound(ctx *context.Context) {
+	c.abort(ctx, http.StatusNotFound)
+}
+
+func (c *CoreServer) abortNotAcceptable(ctx *context.Context) {
+	c.abort(ctx, http.StatusNotAcceptable)
+}
+
+func (c *CoreServer) abortMethodNotAllowed(ctx *context.Context) {
+	c.abort(ctx, http.StatusMethodNotAllowed)
+}
+
+func (c *CoreServer) abortServerError(ctx *context.Context) {
+	c.abort(ctx, http.StatusInternalServerError)
+}
+
+func (c *CoreServer) abortNoContent(ctx *context.Context) {
+	c.abort(ctx, http.StatusNoContent)
+}

+ 217 - 0
src/server/core/api.go

@@ -0,0 +1,217 @@
+package core
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/actioncompile/rewritecompile"
+	"github.com/SongZihuan/huan-proxy/src/server/context"
+	"github.com/SongZihuan/huan-proxy/src/server/request/readonlyrequest"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"net"
+	"strings"
+)
+
+func (c *CoreServer) apiServer(ctx *context.Context) {
+	proxy := ctx.Rule.Api.Server
+	if proxy == nil {
+		c.abortServerError(ctx)
+		return
+	}
+
+	targetURL := ctx.Rule.Api.TargetURL
+	ctx.ProxyRequest.URL.Scheme = targetURL.Scheme
+	ctx.ProxyRequest.URL.Host = targetURL.Host
+
+	c.processProxyHeader(ctx)
+
+	ctx.ProxyRequest.URL.Path = c.apiRewrite(utils.ProcessURLPath(ctx.ProxyRequest.URL.Path), ctx.Rule.Api.AddPath, ctx.Rule.Api.SubPath, ctx.Rule.Api.Rewrite)
+
+	for _, h := range ctx.Rule.Api.HeaderSet {
+		ctx.ProxyRequest.Header.Set(h.Header, h.Value)
+	}
+
+	for _, h := range ctx.Rule.Api.HeaderAdd {
+		ctx.ProxyRequest.Header.Add(h.Header, h.Value)
+	}
+
+	for _, h := range ctx.Rule.Api.HeaderDel {
+		ctx.ProxyRequest.Header.Del(h.Header)
+	}
+
+	query := ctx.ProxyRequest.URL.Query()
+
+	for _, q := range ctx.Rule.Api.QuerySet {
+		query.Set(q.Query, q.Value)
+	}
+
+	for _, q := range ctx.Rule.Api.QueryAdd {
+		query.Add(q.Query, q.Value)
+	}
+
+	for _, q := range ctx.Rule.Api.QueryDel {
+		query.Del(q.Query)
+	}
+
+	ctx.ProxyRequest.URL.RawQuery = query.Encode()
+
+	c.writeViaHeader(ctx)
+
+	req, err := ctx.ProxyWriteToHttpRRequest()
+	if err != nil {
+		c.abortServerError(ctx)
+		return
+	}
+
+	proxy.ServeHTTP(ctx.Writer, req) // 反向代理
+}
+
+func (c *CoreServer) apiRewrite(srcpath string, prefix string, suffix string, rewrite *rewritecompile.RewriteCompileConfig) string {
+	srcpath = utils.ProcessURLPath(srcpath)
+	prefix = utils.ProcessURLPath(prefix)
+	suffix = utils.ProcessURLPath(suffix)
+
+	if strings.HasPrefix(srcpath, suffix) {
+		srcpath = srcpath[len(suffix):]
+	}
+
+	srcpath = prefix + srcpath
+
+	if rewrite.Use && rewrite.Regex != nil {
+		rewrite.Regex.ReplaceAllString(srcpath, rewrite.Target)
+	}
+
+	return srcpath
+}
+
+func (c *CoreServer) processProxyHeader(ctx *context.Context) {
+	if ctx.Request.RemoteAddr() == "" {
+		return
+	}
+
+	remoteIPStr, _, err := net.SplitHostPort(ctx.Request.RemoteAddr())
+	if err != nil {
+		return
+	}
+
+	remoteIP := net.ParseIP(remoteIPStr)
+
+	var ProxyList, ForwardedList []string
+	var host, proto string
+
+	if ctx.Request.Header().Get("Forwarded") != "" {
+		ProxyList, ForwardedList, host, proto = c.getProxyListForwarder(remoteIP, ctx.Request)
+	} else if ctx.Request.Header().Get("X-Forwarded-For") != "" {
+		ProxyList, ForwardedList, host, proto = c.getProxyListFromXForwardedFor(remoteIP, ctx.Request)
+	} else {
+		host = ctx.Request.Header().Get("X-Forwarded-Host")
+		proto = ctx.Request.Header().Get("X-Forwarded-Proto")
+
+		if host == "" {
+			host = ctx.Request.Host()
+		}
+
+		host, _ = utils.SplitHostPort(host) // 去除host中的端口号
+
+		if proto == "http" || proto == "https" {
+			if ctx.Request.IsTLS() {
+				proto = "https"
+			} else {
+				proto = "http"
+			}
+		}
+
+		ProxyList = append(make([]string, 0, 1), remoteIP.String())
+		ForwardedList = append(make([]string, 0, 1),
+			fmt.Sprintf("for=%s", remoteIP.String()),
+			fmt.Sprintf("host=%s", host),
+			fmt.Sprintf("proto=%s", proto))
+	}
+
+	ctx.ProxyRequest.Header.Set("Forwarded", strings.Join(ForwardedList, ", "))
+	ctx.ProxyRequest.Header.Set("X-Forwarded-For", strings.Join(ProxyList, ", "))
+	ctx.ProxyRequest.Header.Set("X-Forwarded-Host", host)
+	ctx.ProxyRequest.Header.Set("X-Forwarded-Proto", proto)
+}
+
+func (c *CoreServer) getProxyListForwarder(remoteIP net.IP, r *readonlyrequest.ReadOnlyRequest) ([]string, []string, string, string) {
+	ForwardedList := strings.Split(r.Header().Get("Forwarded"), ",")
+	ProxyList := make([]string, 0, len(ForwardedList)+1)
+	NewForwardedList := make([]string, 0, len(ForwardedList)+1)
+
+	host, _ := utils.SplitHostPort(r.Host()) // 去除host中的端口号
+	proto := "http"
+	if r.IsTLS() {
+		proto = "https"
+	}
+
+	for _, keyStr := range ForwardedList {
+		kv := strings.Split(strings.ReplaceAll(keyStr, " ", ""), "=")
+		if len(kv) != 2 {
+			continue
+		}
+
+		if kv[0] == "for" {
+			forIP := net.ParseIP(strings.TrimSpace(kv[1]))
+			if forIP != nil {
+				NewForwardedList = append(NewForwardedList, keyStr)
+				ProxyList = append(ProxyList, forIP.String())
+			} else if kv[1] == "_hidden" || kv[1] == "_secret" || kv[1] == "unknown" {
+				NewForwardedList = append(NewForwardedList, keyStr)
+			}
+		} else if kv[0] == "by" {
+			byIP := net.ParseIP(strings.TrimSpace(kv[1]))
+			if byIP != nil || kv[1] == "_hidden" || kv[1] == "_secret" || kv[1] == "unknown" {
+				NewForwardedList = append(NewForwardedList, keyStr)
+			}
+		} else if kv[0] == "host" {
+			host = kv[1]
+		} else if kv[0] == "proto" {
+			proto = kv[1]
+		}
+	}
+
+	ProxyList = append(ProxyList, remoteIP.String())
+	NewForwardedList = append(NewForwardedList, fmt.Sprintf("for=%s", remoteIP.String()))
+	NewForwardedList = append(NewForwardedList, fmt.Sprintf("host=%s", host))
+	NewForwardedList = append(NewForwardedList, fmt.Sprintf("proto=%s", proto))
+	return ProxyList, NewForwardedList, host, proto
+}
+
+func (c *CoreServer) getProxyListFromXForwardedFor(remoteIP net.IP, r *readonlyrequest.ReadOnlyRequest) ([]string, []string, string, string) {
+	XFroWardedForList := strings.Split(r.Header().Get("X-Forwarded-For"), ",")
+	ProxyList := make([]string, 0, len(XFroWardedForList)+1)
+	NewForwardedList := make([]string, 0, len(XFroWardedForList)+1)
+
+	for _, forIPStr := range XFroWardedForList {
+		forIP := net.ParseIP(strings.TrimSpace(forIPStr))
+		if forIP != nil {
+			ProxyList = append(ProxyList, forIP.String())
+		}
+	}
+
+	host := r.Header().Get("X-Forwarded-Host")
+	proto := r.Header().Get("X-Forwarded-Proto")
+
+	if host == "" {
+		host = r.Host()
+	}
+
+	host, _ = utils.SplitHostPort(host) // 去除host中的端口号
+
+	if proto == "http" || proto == "https" {
+		if r.IsTLS() {
+			proto = "https"
+		} else {
+			proto = "http"
+		}
+	}
+
+	ProxyList = append(ProxyList, remoteIP.String())
+
+	for _, ip := range ProxyList {
+		NewForwardedList = append(NewForwardedList, fmt.Sprintf("for=%s", ip))
+	}
+	NewForwardedList = append(NewForwardedList, fmt.Sprintf("host=%s", host))
+	NewForwardedList = append(NewForwardedList, fmt.Sprintf("proto=%s", proto))
+
+	return ProxyList, NewForwardedList, host, proto
+}

+ 36 - 0
src/server/core/cors.go

@@ -0,0 +1,36 @@
+package core
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/actioncompile/corscompile"
+	"github.com/SongZihuan/huan-proxy/src/server/context"
+	"net/http"
+)
+
+func (c *CoreServer) cors(corsRule *corscompile.CorsCompileConfig, ctx *context.Context) bool {
+	if corsRule.Ignore {
+		if ctx.Request.Method() == http.MethodOptions {
+			c.abortMethodNotAllowed(ctx)
+			return false
+		} else {
+			return true
+		}
+	}
+
+	origin := ctx.Request.Header().Get("Origin")
+	if origin == "" {
+		c.abortForbidden(ctx)
+		return false
+	}
+
+	if !corsRule.InOriginList(origin) {
+		c.abortForbidden(ctx)
+		return false
+	}
+
+	ctx.Writer.Header().Set("Access-Control-Allow-Origin", origin)
+	ctx.Writer.Header().Set("Access-Control-Allow-Methods", "GET,OPTIONS")
+	ctx.Writer.Header().Set("Access-Control-Max-Age", fmt.Sprintf("%d", corsRule.MaxAgeSec))
+
+	return true
+}

+ 7 - 0
src/server/core/default.go

@@ -0,0 +1,7 @@
+package core
+
+import "net/http"
+
+func (c *CoreServer) defaultResponse(w http.ResponseWriter) {
+	w.WriteHeader(http.StatusNotFound)
+}

+ 178 - 0
src/server/core/dir.go

@@ -0,0 +1,178 @@
+package core
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/actioncompile/rewritecompile"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/matchcompile"
+	"github.com/SongZihuan/huan-proxy/src/server/context"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"github.com/gabriel-vasile/mimetype"
+	"net/http"
+	"os"
+	"path"
+	"strings"
+)
+
+const IndexMaxDeep = 5
+const DefaultIgnoreFileMap = 20
+
+func (c *CoreServer) dirServer(ctx *context.Context) {
+	if !c.cors(ctx.Rule.Dir.Cors, ctx) {
+		return
+	}
+
+	if ctx.Request.Method() != http.MethodGet {
+		c.abortMethodNotAllowed(ctx)
+		return
+	}
+
+	dirBasePath := ctx.Rule.Dir.BasePath // 根部目录
+	fileAccess := ""                     // 访问目录
+	filePath := ""                       // 根部目录+访问目录=实际目录
+
+	url := utils.ProcessURLPath(ctx.Request.URL().Path)
+	if ctx.Rule.MatchType == matchcompile.RegexMatch {
+		fileAccess = c.dirRewrite("", ctx.Rule.Dir.AddPath, ctx.Rule.Dir.SubPath, ctx.Rule.Dir.Rewrite)
+		filePath = path.Join(dirBasePath, fileAccess)
+	} else {
+		if url == ctx.Rule.MatchPath {
+			fileAccess = c.dirRewrite("", ctx.Rule.Dir.AddPath, ctx.Rule.Dir.SubPath, ctx.Rule.Dir.Rewrite)
+			filePath = path.Join(dirBasePath, fileAccess)
+		} else if strings.HasPrefix(url, ctx.Rule.MatchPath+"/") {
+			fileAccess = c.dirRewrite(url[len(ctx.Rule.MatchPath+"/"):], ctx.Rule.Dir.AddPath, ctx.Rule.Dir.SubPath, ctx.Rule.Dir.Rewrite)
+			filePath = path.Join(dirBasePath, fileAccess)
+		} else {
+			c.abortNotFound(ctx)
+			return
+		}
+	}
+
+	if filePath == "" {
+		c.abortNotFound(ctx) // 正常清空不会走到这个流程
+		return
+	}
+
+	if utils.IsFile(filePath) {
+		// 判断这个文件是否被ignore,因为ignore是从dirBasePath写起,也可以是完整路径,因此filePath和fileAccess都要判断
+		for _, ignore := range ctx.Rule.Dir.IgnoreFile {
+			if ignore.CheckName(fileAccess) || ignore.CheckName(filePath) {
+				c.abortNotFound(ctx)
+				return
+			}
+		}
+	} else {
+		filePath = c.getIndexFile(ctx.Rule, filePath)
+		if filePath == "" || !utils.IsFile(filePath) {
+			c.abortNotFound(ctx)
+			return
+		}
+	}
+
+	if !utils.CheckIfSubPath(dirBasePath, filePath) {
+		c.abortForbidden(ctx)
+		return
+	}
+
+	file, err := os.ReadFile(filePath)
+	if err != nil {
+		c.abortNotFound(ctx)
+		return
+	}
+
+	mimeType := mimetype.Detect(file)
+	accept := ctx.Request.Header().Get("Accept")
+	if !utils.AcceptMimeType(accept, mimeType.String()) {
+		c.abortNotAcceptable(ctx)
+		return
+	}
+
+	_, err = ctx.Writer.Write(file)
+	if err != nil {
+		c.abortServerError(ctx)
+		return
+	}
+	ctx.Writer.Header().Set("Content-Type", mimeType.String())
+	c.statusOK(ctx)
+}
+
+func (c *CoreServer) dirRewrite(srcpath string, prefix string, suffix string, rewrite *rewritecompile.RewriteCompileConfig) string {
+	if strings.HasPrefix(srcpath, suffix) {
+		srcpath = srcpath[len(suffix):]
+	}
+
+	srcpath = path.Join(prefix, srcpath)
+
+	if rewrite.Use && rewrite.Regex != nil {
+		rewrite.Regex.ReplaceAllString(srcpath, rewrite.Target)
+	}
+
+	return srcpath
+}
+
+func (c *CoreServer) getIndexFile(rule *rulescompile.RuleCompileConfig, dir string) string {
+	return c._getIndexFile(rule, dir, "", IndexMaxDeep)
+}
+
+func (c *CoreServer) _getIndexFile(rule *rulescompile.RuleCompileConfig, baseDir string, nextDir string, deep int) string {
+	if deep == 0 {
+		return ""
+	}
+
+	dir := path.Join(baseDir, nextDir)
+	if !utils.IsDir(dir) {
+		return ""
+	}
+
+	fileList, err := os.ReadDir(dir)
+	if err != nil {
+		return ""
+	}
+
+	var ignoreFileMap = make(map[string]bool, DefaultIgnoreFileMap)
+	for _, ignore := range rule.Dir.IgnoreFile {
+		for _, file := range fileList {
+			fileName := path.Join(nextDir, file.Name())
+			if ignore.CheckName(fileName) {
+				ignoreFileMap[fileName] = true
+			}
+		}
+	}
+
+	var indexDirNum = -1
+	var indexDir os.DirEntry = nil
+
+	var indexFileNum = -1
+	var indexFile os.DirEntry = nil
+
+	for indexID, index := range rule.Dir.IndexFile {
+		for _, file := range fileList {
+			fileName := path.Join(nextDir, file.Name())
+
+			if _, ok := ignoreFileMap[fileName]; ok {
+				continue
+			}
+
+			if index.CheckName(file.Name()) {
+				if file.IsDir() {
+					if indexDirNum == -1 || indexID < indexDirNum {
+						indexDirNum = indexID
+						indexDir = file
+					}
+				} else {
+					if indexFileNum == -1 || indexID < indexFileNum {
+						indexFileNum = indexID
+						indexFile = file
+					}
+				}
+			}
+		}
+	}
+
+	if indexFile != nil {
+		return path.Join(dir, indexFile.Name())
+	} else if indexDir != nil {
+		return c._getIndexFile(rule, dir, indexDir.Name(), deep-1)
+	} else {
+		return ""
+	}
+}

+ 41 - 0
src/server/core/file.go

@@ -0,0 +1,41 @@
+package core
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/server/context"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"github.com/gabriel-vasile/mimetype"
+	"net/http"
+	"os"
+)
+
+func (c *CoreServer) fileServer(ctx *context.Context) {
+	if !c.cors(ctx.Rule.File.Cors, ctx) {
+		return
+	}
+
+	if ctx.Request.Method() != http.MethodGet {
+		c.abortMethodNotAllowed(ctx)
+		return
+	}
+
+	file, err := os.ReadFile(ctx.Rule.File.Path)
+	if err != nil {
+		c.abortServerError(ctx)
+		return
+	}
+
+	mimeType := mimetype.Detect(file)
+	accept := ctx.Request.Header().Get("Accept")
+	if !utils.AcceptMimeType(accept, mimeType.String()) {
+		c.abortNotAcceptable(ctx)
+		return
+	}
+
+	_, err = ctx.Writer.Write(file)
+	if err != nil {
+		c.abortServerError(ctx)
+		return
+	}
+	ctx.Writer.Header().Set("Content-Type", mimeType.String())
+	c.statusOK(ctx)
+}

+ 49 - 0
src/server/core/hptag.go

@@ -0,0 +1,49 @@
+package core
+
+import (
+	"fmt"
+	resource "github.com/SongZihuan/huan-proxy"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/actioncompile/apicompile"
+	"github.com/SongZihuan/huan-proxy/src/server/context"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"strings"
+)
+
+const XHuanProxyHeaer = apicompile.XHuanProxyHeaer
+const ViaHeader = apicompile.ViaHeader
+
+func (c *CoreServer) writeHuanProxyHeader(ctx *context.Context) {
+	version := strings.TrimSpace(utils.StringToOnlyPrint(resource.Version))
+	ctx.Writer.Header().Set(XHuanProxyHeaer, version)
+	if ctx.ProxyRequest != nil {
+		ctx.ProxyRequest.Header.Set(XHuanProxyHeaer, version)
+	}
+}
+
+func (c *CoreServer) writeViaHeader(ctx *context.Context) {
+	info := fmt.Sprintf("%s %s", ctx.Request.MustProto(), ctx.Rule.Api.Via)
+
+	reqHeader := ctx.Request.Header().Get(ViaHeader)
+	if reqHeader == "" {
+		reqHeader = info
+	} else {
+		reqHeader = fmt.Sprintf("%s, %s", reqHeader, info)
+	}
+	ctx.Request.Header().Set(ViaHeader, reqHeader)
+
+	respHeader := ctx.Writer.Header().Get(ViaHeader)
+	if respHeader == "" {
+		respHeader = info
+	} else if !strings.Contains(respHeader, info) {
+		respHeader = fmt.Sprintf("%s, %s", respHeader, info)
+	}
+	ctx.Writer.Header().Set(ViaHeader, respHeader)
+}
+
+func (c *CoreServer) statusOK(ctx *context.Context) {
+	ctx.StatusOK()
+}
+
+func (c *CoreServer) statusRedirect(ctx *context.Context, url string, code int) {
+	ctx.Redirect(url, code)
+}

+ 29 - 0
src/server/core/match.go

@@ -0,0 +1,29 @@
+package core
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/matchcompile"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"net/http"
+	"strings"
+)
+
+func (c *CoreServer) matchURL(rule *rulescompile.RuleCompileConfig, r *http.Request) bool {
+	url := utils.ProcessURLPath(r.URL.Path)
+	if rule.MatchType == matchcompile.RegexMatch {
+		if rule.MatchRegex.MatchString(url) || rule.MatchRegex.MatchString(url+"/") {
+			return true
+		}
+	} else if rule.MatchType == matchcompile.PrefixMatch {
+		path := utils.ProcessURLPath(rule.MatchPath)
+		if url == path || strings.HasPrefix(url, path+"/") {
+			return true
+		}
+	} else if rule.MatchType == matchcompile.PrecisionMatch {
+		path := utils.ProcessURLPath(rule.MatchPath)
+		if url == path {
+			return true
+		}
+	}
+	return false
+}

+ 51 - 0
src/server/core/normalserver.go

@@ -0,0 +1,51 @@
+package core
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile"
+	"github.com/SongZihuan/huan-proxy/src/server/context"
+	"net/http"
+)
+
+func (c *CoreServer) CoreServeHTTP(writer http.ResponseWriter, r *http.Request) {
+	func() {
+	RuleCycle:
+		for _, rule := range c.GetRulesList() {
+			if !c.matchURL(rule, r) {
+				continue RuleCycle
+			}
+
+			ctx := context.NewContext(rule, writer, r)
+
+			if !c.checkProxyTrust(ctx) {
+				return
+			}
+
+			c.writeHuanProxyHeader(ctx)
+
+			if rule.Type == rulescompile.ProxyTypeFile {
+				c.fileServer(ctx)
+			} else if rule.Type == rulescompile.ProxyTypeDir {
+				c.dirServer(ctx)
+			} else if rule.Type == rulescompile.ProxyTypeAPI {
+				c.apiServer(ctx)
+			} else if rule.Type == rulescompile.ProxyTypeRedirect {
+				c.redirectServer(ctx)
+			} else {
+				c.abortServerError(ctx)
+			}
+
+			if ctx.Abort && config.GetConfig().NotAbort.IsEnable(false) {
+				_ = ctx.Reset()
+				continue RuleCycle
+			}
+
+			ctx.MustWriteToResponse()
+			return
+
+		}
+
+		c.defaultResponse(writer)
+		// 此处虽然w为Writer,但应该交由LoggerServer来处理写入
+	}()
+}

+ 51 - 0
src/server/core/proxytrust.go

@@ -0,0 +1,51 @@
+package core
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/server/context"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"net"
+)
+
+func (c *CoreServer) checkProxyTrust(ctx *context.Context) bool {
+	if !ctx.Rule.UseTrustedIPs {
+		return true
+	}
+
+	if ctx.Request.RemoteAddr() == "" {
+		c.abortForbidden(ctx)
+		return false
+	}
+
+	remoteIPStr, _, err := net.SplitHostPort(ctx.Request.RemoteAddr())
+	if err != nil {
+		c.abortForbidden(ctx)
+		return false
+	}
+
+	remoteIP := net.ParseIP(remoteIPStr)
+	if remoteIP == nil {
+		c.abortForbidden(ctx)
+		return false
+	}
+
+	for _, t := range ctx.Rule.TrustedIPs {
+		if utils.ValidIPv4(t) || utils.ValidIPv6(t) {
+			trustIP := net.ParseIP(t)
+			if trustIP == nil {
+				continue
+			} else if trustIP.Equal(remoteIP) {
+				return true
+			}
+		} else if utils.IsValidIPv4CIDR(t) || utils.IsValidIPv6CIDR(t) {
+			_, trustCIDR, err := net.ParseCIDR(t)
+			if err != nil || trustCIDR == nil {
+				continue
+			} else if trustCIDR.Contains(remoteIP) {
+				return true
+			}
+		}
+	}
+
+	c.abortForbidden(ctx)
+	return false
+}

+ 26 - 0
src/server/core/redirect.go

@@ -0,0 +1,26 @@
+package core
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile/actioncompile/rewritecompile"
+	"github.com/SongZihuan/huan-proxy/src/server/context"
+	"net/url"
+)
+
+func (c *CoreServer) redirectServer(ctx *context.Context) {
+	target := c.redirectRewrite(ctx.Rule.Redirect.Address, ctx.Rule.Redirect.Rewrite)
+
+	if _, err := url.Parse(target); err != nil {
+		c.abortServerError(ctx)
+		return
+	}
+
+	c.statusRedirect(ctx, target, ctx.Rule.Redirect.Code)
+}
+
+func (c *CoreServer) redirectRewrite(address string, rewrite *rewritecompile.RewriteCompileConfig) string {
+	if rewrite.Use && rewrite.Regex != nil {
+		rewrite.Regex.ReplaceAllString(address, rewrite.Target)
+	}
+
+	return address
+}

+ 47 - 0
src/server/core/server.go

@@ -0,0 +1,47 @@
+package core
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config"
+	"github.com/SongZihuan/huan-proxy/src/config/rulescompile"
+	"github.com/SongZihuan/huan-proxy/src/flagparser"
+	"github.com/SongZihuan/huan-proxy/src/server/responsewriter"
+	"net/http"
+)
+
+type Middleware interface {
+	ServeHTTP(http.ResponseWriter, *http.Request, http.HandlerFunc)
+}
+
+type CoreServer struct {
+	logger Middleware
+}
+
+func NewCoreServer(logger Middleware) *CoreServer {
+	if !flagparser.IsReady() || !config.IsReady() {
+		panic("not ready")
+	}
+	return &CoreServer{
+		logger: logger,
+	}
+}
+
+func (c *CoreServer) GetConfig() *config.YamlConfig {
+	// 不用检查Ready,因为在NewServer的时候已经检查过了
+	return config.GetConfig()
+}
+
+func (c *CoreServer) GetRules() *rulescompile.RuleListCompileConfig {
+	// 不用检查Ready,因为在NewServer的时候已经检查过了
+	return config.GetRules()
+}
+
+func (c *CoreServer) GetRulesList() []*rulescompile.RuleCompileConfig {
+	// 不用检查Ready,因为在NewServer的时候已经检查过了
+	return c.GetRules().Rules
+}
+
+func (c *CoreServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	writer := responsewriter.NewResponseWriter(w)
+	c.logger.ServeHTTP(writer, r, c.CoreServeHTTP)
+	writer.MustWriteToResponse()
+}

+ 55 - 0
src/server/httpserver/server.go

@@ -0,0 +1,55 @@
+package httpserver
+
+import (
+	"errors"
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config"
+	"github.com/SongZihuan/huan-proxy/src/logger"
+	"net/http"
+)
+
+var ServerStop = fmt.Errorf("https server stop")
+
+type HTTPServer struct {
+	cfg     *config.HttpConfig
+	server  *http.Server
+	handler http.Handler
+}
+
+func NewHTTPServer(handler http.Handler) *HTTPServer {
+	httpcfg := config.GetConfig().Http
+
+	if httpcfg.Address == "" {
+		return nil
+	}
+
+	return &HTTPServer{
+		cfg:     &httpcfg,
+		server:  nil,
+		handler: handler,
+	}
+}
+
+func (s *HTTPServer) LoadHttp() error {
+	s.server = &http.Server{
+		Addr:    s.cfg.Address,
+		Handler: s.handler,
+	}
+	return nil
+}
+
+func (s *HTTPServer) RunHttp(_httpschan chan error) chan error {
+	go func(httpschan chan error) {
+		logger.Infof("start http server in %s", s.cfg.Address)
+		err := s.server.ListenAndServe()
+		if err != nil && errors.Is(err, http.ErrServerClosed) {
+			httpschan <- ServerStop
+			return
+		} else if err != nil {
+			httpschan <- err
+			return
+		}
+	}(_httpschan)
+
+	return _httpschan
+}

+ 166 - 0
src/server/httpsserver/server.go

@@ -0,0 +1,166 @@
+package httpsserver
+
+import (
+	"context"
+	"crypto"
+	"crypto/tls"
+	"crypto/x509"
+	"errors"
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/certssl"
+	"github.com/SongZihuan/huan-proxy/src/config"
+	"github.com/SongZihuan/huan-proxy/src/logger"
+	"net/http"
+	"sync"
+	"time"
+)
+
+var ServerStop = fmt.Errorf("https server stop")
+
+type HTTPSServer struct {
+	cfg         *config.HttpsConfig
+	reloadMutex sync.Mutex
+	key         crypto.PrivateKey
+	cert        *x509.Certificate
+	cacert      *x509.Certificate
+	server      *http.Server
+	handler     http.Handler
+}
+
+func NewHTTPSServer(handler http.Handler) *HTTPSServer {
+	httpscfg := config.GetConfig().Https
+
+	if httpscfg.Address == "" {
+		return nil
+	}
+
+	return &HTTPSServer{
+		cfg:     &httpscfg,
+		server:  nil,
+		handler: handler,
+	}
+}
+
+func (s *HTTPSServer) LoadHttps() error {
+	privateKey, certificate, issuerCertificate, err := certssl.GetCertificateAndPrivateKey(s.cfg.SSLCertDir, s.cfg.SSLEmail, s.cfg.AliyunDNSAccessKey, s.cfg.AliyunDNSAccessSecret, s.cfg.SSLDomain)
+	if err != nil {
+		return fmt.Errorf("init htttps cert ssl server error: %s", err.Error())
+	} else if privateKey == nil || certificate == nil || issuerCertificate == nil {
+		return fmt.Errorf("init https server error: get key and cert error, return nil, unknown reason")
+	}
+
+	s.key = privateKey
+	s.cert = certificate
+	s.cacert = issuerCertificate
+
+	err = s.reloadHttps()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *HTTPSServer) reloadHttps() error {
+	if s.key == nil || s.cert == nil || s.cacert == nil {
+		return fmt.Errorf("init https server error: get key and cert error, return nil, unknown reason")
+	}
+
+	if s.cert.Raw == nil || len(s.cert.Raw) == 0 || s.cacert.Raw == nil || len(s.cacert.Raw) == 0 {
+		return fmt.Errorf("init https server error: get cert.raw error, return nil, unknown reason")
+	}
+
+	tlsConfig := &tls.Config{
+		Certificates: []tls.Certificate{{
+			Certificate: [][]byte{s.cert.Raw, s.cacert.Raw}, // Raw包含 DER 编码的证书
+			PrivateKey:  s.key,
+			Leaf:        s.cert,
+		}},
+		MinVersion: tls.VersionTLS12,
+	}
+
+	s.server = &http.Server{
+		Addr:      s.cfg.Address,
+		Handler:   s.handler,
+		TLSConfig: tlsConfig,
+	}
+
+	return nil
+}
+
+func (s *HTTPSServer) RunHttps(_httpschan chan error) chan error {
+	_watchstopchan := make(chan bool)
+
+	s.watchCertificate(_watchstopchan)
+
+	go func(httpschan chan error, watchstopchan chan bool) {
+		defer func() {
+			watchstopchan <- true
+		}()
+	ListenCycle:
+		for {
+			logger.Infof("start https server in %s", s.cfg.Address)
+			err := s.server.ListenAndServeTLS("", "")
+			if err != nil && errors.Is(err, http.ErrServerClosed) {
+				if s.reloadMutex.TryLock() {
+					s.reloadMutex.Unlock()
+					_httpschan <- ServerStop
+					return
+				}
+				s.reloadMutex.Lock()
+				s.reloadMutex.Unlock() // 等待证书更换完毕
+				continue ListenCycle
+			} else if err != nil {
+				_httpschan <- fmt.Errorf("https server error: %s", err.Error())
+				return
+			}
+		}
+	}(_httpschan, _watchstopchan)
+
+	return _httpschan
+}
+
+func (s *HTTPSServer) watchCertificate(stopchan chan bool) {
+	newchan := make(chan certssl.NewCert)
+
+	go func() {
+		err := certssl.WatchCertificate(s.cfg.SSLCertDir, s.cfg.SSLEmail, s.cfg.AliyunDNSAccessKey, s.cfg.AliyunDNSAccessSecret, s.cfg.SSLDomain, s.cert, stopchan, newchan)
+		if err != nil {
+			fmt.Printf("watch https cert server error: %s", err.Error())
+		}
+	}()
+
+	go func() {
+		select {
+		case res := <-newchan:
+			if res.Certificate == nil && res.PrivateKey == nil && res.Error == nil {
+				close(newchan)
+				return
+			} else if res.Error != nil {
+				fmt.Printf("https cert reload server error: %s", res.Error.Error())
+			} else if res.PrivateKey != nil && res.Certificate != nil && res.IssuerCertificate != nil {
+				func() {
+					s.reloadMutex.Lock()
+					defer s.reloadMutex.Unlock()
+
+					ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+					defer cancel()
+
+					err := s.server.Shutdown(ctx)
+					if err != nil {
+						fmt.Printf("https server reload shutdown error: %s", err.Error())
+					}
+
+					s.key = res.PrivateKey
+					s.cert = res.Certificate
+					s.cacert = res.IssuerCertificate
+
+					err = s.reloadHttps()
+					if err != nil {
+						fmt.Printf("https server reload init error: %s", err.Error())
+					}
+				}()
+			}
+		}
+	}()
+}

+ 57 - 0
src/server/main.go

@@ -0,0 +1,57 @@
+package server
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config"
+	"github.com/SongZihuan/huan-proxy/src/flagparser"
+	"github.com/SongZihuan/huan-proxy/src/server/core"
+	"github.com/SongZihuan/huan-proxy/src/server/httpserver"
+	"github.com/SongZihuan/huan-proxy/src/server/httpsserver"
+	"github.com/SongZihuan/huan-proxy/src/server/middleware/loggerserver"
+)
+
+type HuanProxyServer struct {
+	http   *httpserver.HTTPServer
+	https  *httpsserver.HTTPSServer
+	logger *loggerserver.LogServer
+	server *core.CoreServer
+}
+
+func NewHuanProxyServer() *HuanProxyServer {
+	if !flagparser.IsReady() || !config.IsReady() {
+		panic("not ready")
+	}
+
+	logger := loggerserver.NewLogServer()
+	server := core.NewCoreServer(logger)
+
+	res := &HuanProxyServer{
+		logger: loggerserver.NewLogServer(),
+		server: server,
+		http:   httpserver.NewHTTPServer(server),
+		https:  httpsserver.NewHTTPSServer(server),
+	}
+
+	return res
+}
+
+func (s *HuanProxyServer) Run(httpschan chan error, httpchan chan error) (err error) {
+	if s.https != nil {
+		err := s.https.LoadHttps()
+		if err != nil {
+			return err
+		}
+
+		s.https.RunHttps(httpschan)
+	}
+
+	if s.http != nil {
+		err := s.http.LoadHttp()
+		if err != nil {
+			return err
+		}
+
+		s.http.RunHttp(httpchan)
+	}
+
+	return nil
+}

+ 199 - 0
src/server/middleware/loggerserver/loggerserver.go

@@ -0,0 +1,199 @@
+// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE.gin file.
+
+package loggerserver
+
+import (
+	"errors"
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/logger"
+	"github.com/SongZihuan/huan-proxy/src/server/responsewriter"
+	"io"
+	"net/http"
+	"time"
+)
+
+const (
+	green   = "\033[97;42m"
+	white   = "\033[90;47m"
+	yellow  = "\033[90;43m"
+	red     = "\033[97;41m"
+	blue    = "\033[97;44m"
+	magenta = "\033[97;45m"
+	cyan    = "\033[97;46m"
+	reset   = "\033[0m"
+)
+
+type LogServer struct {
+	skip      map[string]struct{}
+	isTerm    bool
+	logWriter func(msg string)
+}
+
+func NewLogServer() *LogServer {
+	return &LogServer{
+		skip:      make(map[string]struct{}, 10),
+		isTerm:    logger.IsInfoTermNotDumb(),
+		logWriter: logger.InfoWrite,
+	}
+}
+
+// LoggerConfig defines the config for Logger middleware.
+type LoggerConfig struct {
+	// Optional. Default value is gin.defaultLogFormatter
+	Formatter LogFormatter
+
+	// Output is a writer where logs are written.
+	// Optional. Default value is gin.DefaultWriter.
+	Output io.Writer
+
+	// SkipPaths is an url path array which logs are not written.
+	// Optional.
+	SkipPaths []string
+}
+
+// LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter
+type LogFormatter func(params LogFormatterParams) string
+
+// LogFormatterParams is the structure any formatter will be handed when time to log comes
+type LogFormatterParams struct {
+	Request *http.Request
+
+	// TimeStamp shows the time after the server returns a response.
+	TimeStamp time.Time
+	// StatusCode is HTTP response code.
+	StatusCode int
+	// Latency is how much time the server cost to process a certain request.
+	Latency time.Duration
+	// RemoteAddr equals Context's RemoteAddr method.
+	RemoteAddr string
+	// Method is the HTTP method given to the request.
+	Method string
+	// Path is a path the client requests.
+	Path string
+	// isTerm shows whether gin's output descriptor refers to a terminal.
+	isTerm bool
+	// BodySize is the size of the Response Body
+	BodySize int64
+	// Keys are the keys set on the request's context.
+	Keys map[string]any
+}
+
+// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
+func (p *LogFormatterParams) StatusCodeColor() string {
+	code := p.StatusCode
+
+	switch {
+	case code >= http.StatusContinue && code < http.StatusOK:
+		return white
+	case code >= http.StatusOK && code < http.StatusMultipleChoices:
+		return green
+	case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
+		return white
+	case code >= http.StatusBadRequest && code < http.StatusInternalServerError:
+		return yellow
+	default:
+		return red
+	}
+}
+
+// MethodColor is the ANSI color for appropriately logging http method to a terminal.
+func (p *LogFormatterParams) MethodColor() string {
+	method := p.Method
+
+	switch method {
+	case http.MethodGet:
+		return blue
+	case http.MethodPost:
+		return cyan
+	case http.MethodPut:
+		return yellow
+	case http.MethodDelete:
+		return red
+	case http.MethodPatch:
+		return green
+	case http.MethodHead:
+		return magenta
+	case http.MethodOptions:
+		return white
+	default:
+		return reset
+	}
+}
+
+// ResetColor resets all escape attributes.
+func (p *LogFormatterParams) ResetColor() string {
+	return reset
+}
+
+func (ls *LogServer) Formatter(param LogFormatterParams) string {
+	var statusColor, methodColor, resetColor string
+	if ls.isTerm {
+		statusColor = param.StatusCodeColor()
+		methodColor = param.MethodColor()
+		resetColor = param.ResetColor()
+	}
+
+	if param.Latency > time.Minute {
+		param.Latency = param.Latency.Truncate(time.Second)
+	}
+	return fmt.Sprintf("[Huan-Proxy] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v",
+		param.TimeStamp.Format("2006/01/02 - 15:04:05"),
+		statusColor, param.StatusCode, resetColor,
+		param.Latency,
+		param.RemoteAddr,
+		methodColor, param.Method, resetColor,
+		param.Path,
+	)
+}
+
+func (ls *LogServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
+	// Start timer
+	serverErr := false // 若为true则为server error(http.StatusInternalServerError)
+	start := time.Now()
+	path := r.URL.Path
+	raw := r.URL.RawQuery
+
+	writer := responsewriter.NewResponseWriter(w)
+
+	// Process request
+	next(writer, r)
+
+	err := writer.WriteToResponse()
+	if err != nil && !errors.Is(err, responsewriter.ErrHasWriter) {
+		serverErr = true
+		writer.ServerError()
+		// 请求发生服务器故障,日志服务继续
+	}
+
+	param := LogFormatterParams{
+		Request: r,
+		isTerm:  ls.isTerm,
+		Keys:    make(map[string]any),
+	}
+
+	// Stop timer
+	param.TimeStamp = time.Now()
+	param.Latency = param.TimeStamp.Sub(start)
+
+	param.RemoteAddr = r.RemoteAddr
+	param.Method = r.Method
+	if serverErr {
+		param.StatusCode = http.StatusInternalServerError
+		param.BodySize = 0
+	} else {
+		param.StatusCode = writer.Status()
+		param.BodySize = writer.Size()
+	}
+
+	if raw != "" {
+		path = path + "?" + raw
+	}
+
+	param.Path = path
+
+	if ls.logWriter != nil {
+		ls.logWriter(ls.Formatter(param))
+	}
+}

+ 99 - 0
src/server/request/proxyrequest/request.go

@@ -0,0 +1,99 @@
+package proxyrequest
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"net/http"
+	"net/url"
+)
+
+type ProxyRequest struct {
+	req *http.Request
+
+	Host       string
+	Proto      string
+	IsTLS      bool
+	Method     string
+	RemoteAddr string
+	URL        *url.URL
+	Header     http.Header
+
+	_host       string
+	_proto      string
+	_method     string
+	_remoteAddr string
+	_url        *url.URL
+	_header     http.Header
+
+	written bool
+}
+
+func NewRequest(req *http.Request) *ProxyRequest {
+	return &ProxyRequest{
+		req: req,
+
+		Host:       req.Host,
+		Proto:      req.Proto,
+		IsTLS:      req.TLS != nil,
+		Method:     req.Method,
+		RemoteAddr: req.RemoteAddr,
+		Header:     req.Header.Clone(),
+		URL:        utils.URLClone(req.URL),
+
+		_host:       req.Host,
+		_proto:      req.Proto,
+		_method:     req.Method,
+		_remoteAddr: req.RemoteAddr,
+		_url:        req.URL,
+		_header:     req.Header,
+
+		written: false,
+	}
+}
+
+func (r *ProxyRequest) ResetHttpRequest() error {
+	r.req.Host = r._host
+	r.req.Proto = r._proto
+	r.req.Method = r._method
+	r.req.RemoteAddr = r._remoteAddr
+	r.req.Header = r._header
+	r.req.URL = r._url
+	return nil
+}
+
+func (r *ProxyRequest) Reset() error {
+	err := r.ResetHttpRequest()
+	if err != nil {
+		return err
+	}
+
+	r.Host = r.req.Host
+	r.Proto = r.req.Proto
+	r.Method = r.req.Method
+	r.RemoteAddr = r.req.RemoteAddr
+	r.Header = r.req.Header.Clone()
+	r.URL = utils.URLClone(r.req.URL)
+	r.written = false
+	return nil
+}
+
+func (r *ProxyRequest) WriteToHttpRRequest() (req *http.Request, err error) {
+	if r.written {
+		return r.req, nil
+	}
+
+	defer func() {
+		if err != nil {
+			_ = r.ResetHttpRequest() // 复原所有操作
+			r.written = false
+		}
+	}()
+
+	r.req.Host = r.Host
+	r.req.Proto = r.Proto
+	r.req.Method = r.Method
+	r.req.RemoteAddr = r.RemoteAddr
+	r.req.URL = utils.URLClone(r.URL)
+	r.req.Header = r.Header.Clone()
+	r.written = true
+	return r.req, nil
+}

+ 62 - 0
src/server/request/readonlyrequest/request.go

@@ -0,0 +1,62 @@
+package readonlyrequest
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"net/http"
+	"net/url"
+)
+
+type ReadOnlyRequest struct {
+	req    *http.Request
+	url    *url.URL
+	header http.Header
+}
+
+func NewReadOnlyRequest(req *http.Request) *ReadOnlyRequest {
+	return &ReadOnlyRequest{
+		req:    req,
+		url:    utils.URLClone(req.URL),
+		header: req.Header.Clone(),
+	}
+}
+
+func (r *ReadOnlyRequest) Host() string {
+	return r.req.Host
+}
+
+func (r *ReadOnlyRequest) Method() string {
+	return r.req.Method
+}
+
+func (r *ReadOnlyRequest) RemoteAddr() string {
+	return r.req.Host
+}
+
+func (r *ReadOnlyRequest) Proto() string {
+	return r.req.Proto
+}
+
+func (r *ReadOnlyRequest) MustProto() string {
+	proto := r.req.Proto
+	if proto == "" {
+		if r.IsTLS() {
+			return "https"
+		} else {
+			return "http"
+		}
+	} else {
+		return proto
+	}
+}
+
+func (r *ReadOnlyRequest) URL() *url.URL {
+	return utils.URLClone(r.req.URL)
+}
+
+func (r *ReadOnlyRequest) Header() http.Header {
+	return r.req.Header.Clone()
+}
+
+func (r *ReadOnlyRequest) IsTLS() bool {
+	return r.req.TLS != nil
+}

+ 154 - 0
src/server/responsewriter/writer.go

@@ -0,0 +1,154 @@
+package responsewriter
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"net/http"
+)
+
+var ErrHasWriter = fmt.Errorf("ResponseWriter has been written")
+
+type ResponseWriter struct {
+	writer http.ResponseWriter
+
+	status int
+	buffer bytes.Buffer
+	header http.Header
+	size   int64
+
+	writtenStatus bool
+	writtenBody   bool
+	writtenHeader bool
+	written       bool
+}
+
+func NewResponseWriter(w http.ResponseWriter) *ResponseWriter {
+	if writer, ok := w.(*ResponseWriter); ok {
+		return writer
+	}
+
+	return &ResponseWriter{
+		writer: w,
+
+		status: 0,
+		header: w.Header().Clone(),
+
+		written: false,
+	}
+}
+
+func (r *ResponseWriter) Size() int64 {
+	return r.size
+}
+
+func (r *ResponseWriter) Status() int {
+	return r.status
+}
+
+func (r *ResponseWriter) Write(p []byte) (int, error) {
+	if r.written || r.writtenBody {
+		return 0, ErrHasWriter
+	}
+
+	return r.buffer.Write(p)
+}
+
+func (r *ResponseWriter) WriteHeader(statusCode int) {
+	if r.written {
+		return
+	}
+
+	r.status = statusCode
+	fmt.Printf("Set Status is: %d\n", r.status)
+}
+
+func (r *ResponseWriter) ServerError() {
+	if r.written || r.writtenStatus {
+		return
+	}
+
+	r.status = http.StatusInternalServerError
+	r.writer.WriteHeader(r.status)
+	r.writtenStatus = true
+	r.written = true
+
+	fmt.Printf("Server Error Status is: %d\n", r.status)
+}
+
+func (r *ResponseWriter) Header() http.Header {
+	if r.written {
+		return nil
+	}
+
+	return r.header
+}
+
+func (r *ResponseWriter) Reset() error {
+	if r.written {
+		return ErrHasWriter
+	}
+
+	r.status = 0
+	r.buffer.Reset()
+	r.header = r.writer.Header().Clone()
+	r.written = false
+
+	return nil
+}
+
+func (r *ResponseWriter) WriteToResponse() error {
+	if r.written {
+		return ErrHasWriter
+	}
+
+	// status 抓鬼太吗最先写入
+	if !r.writtenStatus {
+		r.writer.WriteHeader(r.status)
+		r.writtenStatus = true
+		fmt.Printf("Write Status is: %d\n", r.status)
+	}
+
+	if !r.writtenHeader {
+		writerHeader := r.writer.Header()
+		for n, h := range r.header {
+			nh := make([]string, 0, len(h))
+			copy(nh, h)
+			writerHeader[n] = nh
+		}
+
+		delHeader := make([]string, 0, 10)
+		for n, _ := range writerHeader {
+			if _, ok := r.header[n]; !ok {
+				delHeader = append(delHeader, n)
+			}
+		}
+
+		for _, n := range delHeader {
+			delete(writerHeader, n)
+		}
+		r.writtenHeader = false
+	}
+
+	if !r.writtenBody {
+		_, err := r.writer.Write(r.buffer.Bytes())
+		if err != nil {
+			return err
+		}
+		r.size = int64(r.buffer.Len())
+		r.buffer.Reset() // 清理
+		r.writtenBody = true
+	}
+
+	r.written = true
+	return nil
+}
+
+func (r *ResponseWriter) MustWriteToResponse() {
+	err := r.WriteToResponse()
+	if err == nil || errors.Is(err, ErrHasWriter) {
+		return
+	}
+
+	r.ServerError()
+}

+ 17 - 0
src/utils/cidr.go

@@ -0,0 +1,17 @@
+package utils
+
+import (
+	"net"
+	"strings"
+)
+
+func IsValidIPv4CIDR(cidr string) bool {
+	_, _, err := net.ParseCIDR(cidr)
+	return err == nil && !strings.Contains(cidr, ":")
+}
+
+// IsValidIPv6CIDR checks if the given string is a valid IPv6 CIDR notation.
+func IsValidIPv6CIDR(cidr string) bool {
+	_, _, err := net.ParseCIDR(cidr)
+	return err == nil && strings.Contains(cidr, ":")
+}

+ 53 - 0
src/utils/cmd.go

@@ -0,0 +1,53 @@
+package utils
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+)
+
+var _args0 = ""
+
+func init() {
+	var err error
+	if len(os.Args) > 0 {
+		_args0, err = os.Executable()
+		if err != nil {
+			_args0 = os.Args[0]
+		}
+	}
+
+	if _args0 == "" {
+		panic("args was empty")
+	}
+}
+
+func GetArgs0() string {
+	return _args0
+}
+
+func GetArgs0Name() string {
+	return filepath.Base(_args0)
+}
+
+func SayHellof(format string, args ...interface{}) {
+	var msg string
+	if len(format) == 0 && len(args) == 0 {
+		msg = fmt.Sprintf("%s: %s", GetArgs0Name(), "Normal startup, thank you.")
+	} else {
+		str := fmt.Sprintf(format, args...)
+		msg = fmt.Sprintf("%s: %s", GetArgs0Name(), str)
+	}
+	fmt.Println(FormatTextToWidth(msg, NormalConsoleWidth))
+}
+
+func SayGoodByef(format string, args ...interface{}) {
+	var msg string
+	if len(format) == 0 && len(args) == 0 {
+		msg = fmt.Sprintf("%s: %s", GetArgs0Name(), "Normal shutdown, thank you.")
+	} else {
+		str := fmt.Sprintf(format, args...)
+		msg = fmt.Sprintf("%s: %s", GetArgs0Name(), str)
+	}
+	fmt.Println(FormatTextToWidth(msg, NormalConsoleWidth))
+}

+ 45 - 0
src/utils/exit.go

@@ -0,0 +1,45 @@
+package utils
+
+import (
+	"os"
+)
+
+func ExitByError(err error, code ...int) int {
+	if err == nil {
+		return ExitByErrorMsg("")
+	} else {
+		return ExitByErrorMsg(err.Error(), code...)
+	}
+}
+
+func ExitByErrorMsg(msg string, code ...int) int {
+	if len(msg) == 0 {
+		msg = "exit: unknown error"
+	}
+
+	return ErrorExit(msg, code...)
+}
+
+func ErrorExit(msg string, code ...int) int {
+	if len(msg) == 0 {
+		SayGoodByef("%s", "Encountered an error, abnormal offline/shutdown.")
+	} else {
+		SayGoodByef("Encountered an error, abnormal offline/shutdown: %s\n", msg)
+	}
+
+	if len(code) == 1 && code[0] != 0 {
+		return Exit(code[0])
+	} else {
+		return Exit(1)
+	}
+}
+
+func Exit(code ...int) int {
+	if len(code) == 1 {
+		os.Exit(code[0])
+		return code[0]
+	} else {
+		os.Exit(0)
+		return 0
+	}
+}

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov