InPanel 1.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. inpanel/__init__.py +29 -0
  2. inpanel/acme.py +289 -0
  3. inpanel/aliyuncs.py +269 -0
  4. inpanel/app.py +514 -0
  5. inpanel/apt.py +223 -0
  6. inpanel/base.py +200 -0
  7. inpanel/config.py +139 -0
  8. inpanel/data/config.ini +29 -0
  9. inpanel/data/history +5 -0
  10. inpanel/data/runlogs.ini +12 -0
  11. inpanel/data/update_info.ini +4 -0
  12. inpanel/dnf.py +225 -0
  13. inpanel/lib/__init__.py +9 -0
  14. inpanel/lib/async_process.py +98 -0
  15. inpanel/lib/filelock.py +82 -0
  16. inpanel/lib/magic.py +233 -0
  17. inpanel/lib/pxssh.py +310 -0
  18. inpanel/lib/pyDes.py +867 -0
  19. inpanel/mod/__init__.py +0 -0
  20. inpanel/mod/apt.py +316 -0
  21. inpanel/mod/certificate.py +268 -0
  22. inpanel/mod/config.py +372 -0
  23. inpanel/mod/cron.py +315 -0
  24. inpanel/mod/disk.py +322 -0
  25. inpanel/mod/dnf.py +239 -0
  26. inpanel/mod/file.py +722 -0
  27. inpanel/mod/firewall.py +199 -0
  28. inpanel/mod/firewall_base.py +137 -0
  29. inpanel/mod/firewall_firewalld.py +170 -0
  30. inpanel/mod/firewall_iptables.py +176 -0
  31. inpanel/mod/firewall_macos.py +213 -0
  32. inpanel/mod/firewall_ufw.py +128 -0
  33. inpanel/mod/ftp.py +231 -0
  34. inpanel/mod/httpd.py +1205 -0
  35. inpanel/mod/lighttpd.py +26 -0
  36. inpanel/mod/login.py +98 -0
  37. inpanel/mod/mysql.py +577 -0
  38. inpanel/mod/named.py +26 -0
  39. inpanel/mod/nginx.py +2327 -0
  40. inpanel/mod/package.py +33 -0
  41. inpanel/mod/package_apt.py +62 -0
  42. inpanel/mod/package_base.py +77 -0
  43. inpanel/mod/package_dnf.py +62 -0
  44. inpanel/mod/package_map.py +366 -0
  45. inpanel/mod/package_yum.py +64 -0
  46. inpanel/mod/php.py +289 -0
  47. inpanel/mod/process.py +291 -0
  48. inpanel/mod/proftpd.py +26 -0
  49. inpanel/mod/pureftpd.py +26 -0
  50. inpanel/mod/query.py +108 -0
  51. inpanel/mod/server.py +1319 -0
  52. inpanel/mod/service.py +262 -0
  53. inpanel/mod/setting.py +228 -0
  54. inpanel/mod/shell.py +78 -0
  55. inpanel/mod/ssh.py +280 -0
  56. inpanel/mod/system.py +220 -0
  57. inpanel/mod/task.py +1337 -0
  58. inpanel/mod/ufw.py +14 -0
  59. inpanel/mod/user.py +306 -0
  60. inpanel/mod/vsftpd.py +67 -0
  61. inpanel/mod/yum.py +243 -0
  62. inpanel/plugins/__init__.py +10 -0
  63. inpanel/plugins/__pycache__/__init__.cpython-313.pyc +0 -0
  64. inpanel/plugins/acme/static/index.html +10 -0
  65. inpanel/plugins/acme/static/index.js +13 -0
  66. inpanel/plugins/shadowsocks-libev/__pycache__/info.cpython-313.pyc +0 -0
  67. inpanel/plugins/shadowsocks-libev/info.py +73 -0
  68. inpanel/plugins/shadowsocks-libev/static/index.html +70 -0
  69. inpanel/plugins/shadowsocks-libev/static/index.js +18 -0
  70. inpanel/public/css/global.css +138 -0
  71. inpanel/public/favicon.ico +0 -0
  72. inpanel/public/images/alipay.jpg +0 -0
  73. inpanel/public/images/loading.gif +0 -0
  74. inpanel/public/images/weixin.jpg +0 -0
  75. inpanel/public/index.html +94 -0
  76. inpanel/public/js/controllers/controllers.js +566 -0
  77. inpanel/public/js/controllers/database.js +590 -0
  78. inpanel/public/js/controllers/ecs.js +624 -0
  79. inpanel/public/js/controllers/file.js +940 -0
  80. inpanel/public/js/controllers/plugins.js +105 -0
  81. inpanel/public/js/controllers/service.js +1460 -0
  82. inpanel/public/js/controllers/site.js +1241 -0
  83. inpanel/public/js/controllers/utils.js +2319 -0
  84. inpanel/public/js/core.js +221 -0
  85. inpanel/public/js/directives.js +784 -0
  86. inpanel/public/js/filters.js +395 -0
  87. inpanel/public/js/services.js +257 -0
  88. inpanel/public/lib/angular/1.0.2/angular-resource.min.js +10 -0
  89. inpanel/public/lib/angular/1.0.2/angular.min.js +158 -0
  90. inpanel/public/lib/bootstrap/css/bootstrap-theme.css +587 -0
  91. inpanel/public/lib/bootstrap/css/bootstrap-theme.css.map +1 -0
  92. inpanel/public/lib/bootstrap/css/bootstrap-theme.min.css +6 -0
  93. inpanel/public/lib/bootstrap/css/bootstrap-theme.min.css.map +1 -0
  94. inpanel/public/lib/bootstrap/css/bootstrap.css +6757 -0
  95. inpanel/public/lib/bootstrap/css/bootstrap.css.map +1 -0
  96. inpanel/public/lib/bootstrap/css/bootstrap.min.css +6 -0
  97. inpanel/public/lib/bootstrap/css/bootstrap.min.css.map +1 -0
  98. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
  99. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
  100. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
  101. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
  102. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
  103. inpanel/public/lib/bootstrap/img/glyphicons-halflings-white.png +0 -0
  104. inpanel/public/lib/bootstrap/img/glyphicons-halflings.png +0 -0
  105. inpanel/public/lib/bootstrap/js/bootstrap.js +2377 -0
  106. inpanel/public/lib/bootstrap/js/bootstrap.min.js +7 -0
  107. inpanel/public/lib/bootstrap/js/npm.js +13 -0
  108. inpanel/public/lib/codemirror/codemirror-compressed.js +1 -0
  109. inpanel/public/lib/codemirror/codemirror.css +173 -0
  110. inpanel/public/lib/jquery/jquery-1.12.4.min.js +5 -0
  111. inpanel/public/lib/md5/md5.js +379 -0
  112. inpanel/public/lib/md5/md5.min.js +1 -0
  113. inpanel/public/partials/database/index.html +172 -0
  114. inpanel/public/partials/database/mysql/dbedit.html +462 -0
  115. inpanel/public/partials/database/mysql/dbnew.html +348 -0
  116. inpanel/public/partials/database/mysql/useredit.html +310 -0
  117. inpanel/public/partials/database/mysql/usernew.html +97 -0
  118. inpanel/public/partials/directives/message.html +20 -0
  119. inpanel/public/partials/directives/navbar.html +25 -0
  120. inpanel/public/partials/directives/selector.html +57 -0
  121. inpanel/public/partials/directives/srvbase.html +72 -0
  122. inpanel/public/partials/directives/srvext.html +34 -0
  123. inpanel/public/partials/directives/srvfile.html +23 -0
  124. inpanel/public/partials/directives/srvinstall.html +34 -0
  125. inpanel/public/partials/directives/srvlog.html +23 -0
  126. inpanel/public/partials/directives/srvminiop.html +28 -0
  127. inpanel/public/partials/directives/srvuninstall.html +38 -0
  128. inpanel/public/partials/directives/srvupdate.html +30 -0
  129. inpanel/public/partials/ecs/account.html +156 -0
  130. inpanel/public/partials/ecs/ecs.html +198 -0
  131. inpanel/public/partials/ecs/index.html +40 -0
  132. inpanel/public/partials/ecs/setings.html +459 -0
  133. inpanel/public/partials/file/file.html +501 -0
  134. inpanel/public/partials/file/trash.html +95 -0
  135. inpanel/public/partials/ftp.html +36 -0
  136. inpanel/public/partials/log.html +83 -0
  137. inpanel/public/partials/login.html +79 -0
  138. inpanel/public/partials/logout.html +22 -0
  139. inpanel/public/partials/main.html +534 -0
  140. inpanel/public/partials/plugins/index.html +55 -0
  141. inpanel/public/partials/plugins/plugins.html +13 -0
  142. inpanel/public/partials/secure.html +13 -0
  143. inpanel/public/partials/service/apache.html +147 -0
  144. inpanel/public/partials/service/cron.html +71 -0
  145. inpanel/public/partials/service/index.html +293 -0
  146. inpanel/public/partials/service/iptables.html +40 -0
  147. inpanel/public/partials/service/lighttpd.html +63 -0
  148. inpanel/public/partials/service/mariadb.html +113 -0
  149. inpanel/public/partials/service/memcache.html +36 -0
  150. inpanel/public/partials/service/minio.html +44 -0
  151. inpanel/public/partials/service/mongodb.html +44 -0
  152. inpanel/public/partials/service/mysql.html +113 -0
  153. inpanel/public/partials/service/named.html +61 -0
  154. inpanel/public/partials/service/nginx.html +296 -0
  155. inpanel/public/partials/service/ntp.html +40 -0
  156. inpanel/public/partials/service/php.html +250 -0
  157. inpanel/public/partials/service/proftpd.html +59 -0
  158. inpanel/public/partials/service/pureftpd.html +59 -0
  159. inpanel/public/partials/service/redis.html +40 -0
  160. inpanel/public/partials/service/samba.html +58 -0
  161. inpanel/public/partials/service/sendmail.html +40 -0
  162. inpanel/public/partials/service/ssh.html +186 -0
  163. inpanel/public/partials/service/tomcat.html +65 -0
  164. inpanel/public/partials/service/vsftpd.html +195 -0
  165. inpanel/public/partials/setting.html +299 -0
  166. inpanel/public/partials/site/apache/site.html +277 -0
  167. inpanel/public/partials/site/index.html +344 -0
  168. inpanel/public/partials/site/nginx/site.html +558 -0
  169. inpanel/public/partials/sorry.html +19 -0
  170. inpanel/public/partials/storage/autofm.html +103 -0
  171. inpanel/public/partials/storage/backup.html +42 -0
  172. inpanel/public/partials/storage/index.html +360 -0
  173. inpanel/public/partials/storage/migrate.html +93 -0
  174. inpanel/public/partials/storage/movedata.html +98 -0
  175. inpanel/public/partials/storage/remote.html +77 -0
  176. inpanel/public/partials/utils/cron.html +486 -0
  177. inpanel/public/partials/utils/firewall.html +266 -0
  178. inpanel/public/partials/utils/index.html +193 -0
  179. inpanel/public/partials/utils/network.html +118 -0
  180. inpanel/public/partials/utils/process.html +239 -0
  181. inpanel/public/partials/utils/repository.html +373 -0
  182. inpanel/public/partials/utils/shell.html +28 -0
  183. inpanel/public/partials/utils/ssl.html +319 -0
  184. inpanel/public/partials/utils/time.html +109 -0
  185. inpanel/public/partials/utils/user.html +359 -0
  186. inpanel/public/robots.txt +2 -0
  187. inpanel/remote.py +109 -0
  188. inpanel/templates/file/preview.html +34 -0
  189. inpanel/templates/index.html +93 -0
  190. inpanel/utils.py +282 -0
  191. inpanel/web.py +1811 -0
  192. inpanel/yum.py +756 -0
  193. inpanel-1.2.1.dist-info/METADATA +399 -0
  194. inpanel-1.2.1.dist-info/RECORD +198 -0
  195. inpanel-1.2.1.dist-info/WHEEL +5 -0
  196. inpanel-1.2.1.dist-info/entry_points.txt +2 -0
  197. inpanel-1.2.1.dist-info/licenses/LICENSE +30 -0
  198. inpanel-1.2.1.dist-info/top_level.txt +1 -0
inpanel/mod/nginx.py ADDED
@@ -0,0 +1,2327 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (c) 2017-2026 Jackson Dou
4
+ # All rights reserved.
5
+ #
6
+ # InPanel is distributed under the terms of the (new) BSD License.
7
+ # The full license can be found in 'LICENSE'.
8
+
9
+ '''Module for Nginx Management'''
10
+
11
+ import os
12
+ import pprint
13
+ import re
14
+ from glob import glob
15
+ from pathlib import Path
16
+
17
+ import tornado
18
+
19
+ from ..base import COMMENTFLAG, DEBUG, GENBY, config_path
20
+ from .config import Config
21
+ from ..utils import is_valid_domain, is_valid_ip, is_valid_ipv4, is_valid_ipv6, is_url, version_get
22
+
23
+ # d stand for directive, and c stand for context
24
+ DIRECTIVES = {
25
+ # REF: http://wiki.nginx.org/CoreModule
26
+ 'daemon': ['_'],
27
+ 'env': ['_'],
28
+ 'debug_points': ['_'],
29
+ 'error_log': ['_', 'http', 'server', 'location'],
30
+ 'include': ['_', 'http', 'server', 'location'],
31
+ 'lock_file': ['_'],
32
+ 'master_process': ['_'],
33
+ 'pcre_jit': ['_'],
34
+ 'pid': ['_'],
35
+ # 'ssl_engine': ['_'],
36
+ 'timer_resolution': ['_'],
37
+ 'user': ['_'],
38
+ 'worker_cpu_affinity': ['_'],
39
+ 'worker_priority': ['_'],
40
+ 'worker_processes': ['_'],
41
+ 'worker_rlimit_core': ['_'],
42
+ 'worker_rlimit_nofile': ['_'],
43
+ 'worker_rlimit_sigpending': ['_'],
44
+ 'working_directory': ['_'],
45
+
46
+ # REF: http://wiki.nginx.org/EventsModule
47
+ 'events': ['_'],
48
+ 'accept_mutex': ['events'],
49
+ 'accept_mutex_delay': ['events'],
50
+ 'debug_connection': ['events'],
51
+ 'devpoll_changes': ['events'],
52
+ 'devpoll_events': ['events'],
53
+ 'kqueue_changes': ['events'],
54
+ 'kqueue_events': ['events'],
55
+ 'epoll_events': ['events'],
56
+ 'multi_accept': ['events'],
57
+ 'rtsig_signo': ['events'],
58
+ 'rtsig_overflow_events': ['events'],
59
+ 'rtsig_overflow_test': ['events'],
60
+ 'rtsig_overflow_threshold': ['events'],
61
+ 'use': ['events'],
62
+ 'worker_connections': ['events'],
63
+
64
+ # REF: http://wiki.nginx.org/HttpCoreModule
65
+ 'http': ['_'],
66
+ 'aio': ['http', 'server', 'location'],
67
+ 'alias': ['location'],
68
+ 'chunked_transfer_encoding': ['http', 'server', 'location'],
69
+ 'client_body_in_file_only': ['http', 'server', 'location'],
70
+ 'client_body_in_single_buffer': ['http', 'server', 'location'],
71
+ 'client_body_buffer_size': ['http', 'server', 'location'],
72
+ 'client_body_temp_path': ['http', 'server', 'location'],
73
+ 'client_body_timeout': ['http', 'server', 'location'],
74
+ 'client_header_buffer_size': ['http', 'server'],
75
+ 'client_header_timeout': ['http', 'server'],
76
+ 'client_max_body_size': ['http', 'server', 'location'],
77
+ 'connection_pool_size': ['http', 'server'],
78
+ 'default_type': ['http', 'server', 'location'],
79
+ 'directio': ['http', 'server', 'location'],
80
+ 'directio_alignment': ['http', 'server', 'location'],
81
+ 'disable_symlinks': ['http', 'server', 'location'],
82
+ 'error_page': ['http', 'server', 'location', 'if in location', 'if'], #+if
83
+ 'if_modified_since': ['http', 'server', 'location'],
84
+ 'ignore_invalid_headers': ['http', 'server'],
85
+ 'internal': ['location'],
86
+ 'keepalive_disable': ['http', 'server', 'location'],
87
+ 'keepalive_timeout': ['http', 'server', 'location'],
88
+ 'keepalive_requests': ['http', 'server', 'location'],
89
+ 'large_client_header_buffers': ['http', 'server'],
90
+ 'limit_except': ['location'],
91
+ 'limit_rate': ['http', 'server', 'location', 'if in location', 'if'], #+if
92
+ 'limit_rate_after': ['http', 'server', 'location', 'if in location', 'if'], #+if
93
+ 'lingering_close': ['http', 'server', 'location'],
94
+ 'lingering_time': ['http', 'server', 'location'],
95
+ 'lingering_timeout': ['http', 'server', 'location'],
96
+ 'listen': ['server'],
97
+ 'location': ['server', 'location'],
98
+ 'log_not_found': ['http', 'server', 'location'],
99
+ 'log_subrequest': ['http', 'server', 'location'],
100
+ 'max_ranges': ['http', 'server', 'location'],
101
+ 'merge_slashes': ['http', 'server'],
102
+ 'msie_padding': ['http', 'server', 'location'],
103
+ 'msie_refresh': ['http', 'server', 'location'],
104
+ 'open_file_cache': ['http', 'server', 'location'],
105
+ 'open_file_cache_errors': ['http', 'server', 'location'],
106
+ 'open_file_cache_min_uses': ['http', 'server', 'location'],
107
+ 'open_file_cache_valid': ['http', 'server', 'location'],
108
+ 'optimize_server_names': ['http', 'server'],
109
+ 'port_in_redirect': ['http', 'server', 'location'],
110
+ 'post_action': ['http', 'server', 'location'],
111
+ 'postpone_output': ['http', 'server', 'location'],
112
+ 'read_ahead': ['http', 'server', 'location'],
113
+ 'recursive_error_pages': ['http', 'server', 'location'],
114
+ 'request_pool_size': ['http', 'server'],
115
+ 'reset_timedout_connection': ['http', 'server', 'location'],
116
+ 'resolver': ['http', 'server', 'location'],
117
+ 'resolver_timeout': ['http', 'server', 'location'],
118
+ 'root': ['http', 'server', 'location', 'if in location', 'if'], #+if
119
+ 'satisfy': ['http', 'server', 'location'],
120
+ 'satisfy_any': ['http', 'server', 'location'],
121
+ 'send_lowat': ['http', 'server', 'location'],
122
+ 'send_timeout': ['http', 'server', 'location'],
123
+ 'sendfile': ['http', 'server', 'location', 'if in location', 'if'], #+if
124
+ 'sendfile_max_chunk': ['http', 'server', 'location'],
125
+ 'server': ['http', 'upstream'],
126
+ 'server_name': ['server'],
127
+ 'server_name_in_redirect': ['http', 'server', 'location'],
128
+ 'server_names_hash_max_size': ['http'],
129
+ 'server_names_hash_bucket_size':['http'],
130
+ 'server_tokens': ['http', 'server', 'location'],
131
+ 'tcp_nodelay': ['http', 'server', 'location'],
132
+ 'tcp_nopush': ['http', 'server', 'location'],
133
+ 'try_files': ['server', 'location'],
134
+ 'types': ['http', 'server', 'location'],
135
+ 'types_hash_bucket_size': ['http', 'server', 'location'],
136
+ 'types_hash_max_size': ['http', 'server', 'location'],
137
+ 'underscores_in_headers': ['http', 'server'],
138
+ 'variables_hash_bucket_size': ['http'],
139
+ 'variables_hash_max_size': ['http'],
140
+
141
+ # REF: http://wiki.nginx.org/HttpAccessModule
142
+ 'allow': ['http', 'server', 'location', 'limit_except'],
143
+ 'deny': ['http', 'server', 'location', 'limit_except'],
144
+
145
+ # REF: http://wiki.nginx.org/HttpAuthBasicModule
146
+ 'auth_basic': ['http', 'server', 'location', 'limit_except'],
147
+ 'auth_basic_user_file': ['http', 'server', 'location', 'limit_except'],
148
+
149
+ # REF: http://wiki.nginx.org/HttpAutoindexModule
150
+ 'autoindex': ['http', 'server', 'location'],
151
+ 'autoindex_exact_size': ['http', 'server', 'location'],
152
+ 'autoindex_localtime': ['http', 'server', 'location'],
153
+
154
+ # REF: http://wiki.nginx.org/HttpBrowserModule
155
+ 'ancient_browser': ['http', 'server', 'location'],
156
+ 'ancient_browser_value': ['http', 'server', 'location'],
157
+ 'modern_browser': ['http', 'server', 'location'],
158
+ 'modern_browser_value': ['http', 'server', 'location'],
159
+
160
+ # REF: http://wiki.nginx.org/HttpCharsetModule
161
+ 'charset': ['http', 'server', 'location', 'if in location', 'if'], #+if
162
+ 'charset_map': ['http'],
163
+ 'charset_types': ['http', 'server', 'location'],
164
+ 'override_charset': ['http', 'server', 'location', 'if in location', 'if'], #+if
165
+ 'source_charset': ['http', 'server', 'location', 'if in location', 'if'], #+if
166
+
167
+ # REF: http://wiki.nginx.org/HttpEmptyGifModule
168
+ 'empty_gif': ['location'],
169
+
170
+ # REF: http://wiki.nginx.org/HttpFastcgiModule
171
+ 'fastcgi_bind': ['http', 'server', 'location'],
172
+ 'fastcgi_buffer_size': ['http', 'server', 'location'],
173
+ 'fastcgi_buffers': ['http', 'server', 'location'],
174
+ 'fastcgi_busy_buffers_size': ['http', 'server', 'location'],
175
+ 'fastcgi_cache': ['http', 'server', 'location'],
176
+ 'fastcgi_cache_bypass': ['http', 'server', 'location'],
177
+ 'fastcgi_cache_key': ['http', 'server', 'location'],
178
+ 'fastcgi_cache_lock': ['http', 'server', 'location'],
179
+ 'fastcgi_cache_lock_timeout': ['http', 'server', 'location'],
180
+ 'fastcgi_cache_methods': ['http', 'server', 'location'],
181
+ 'fastcgi_cache_min_uses': ['http', 'server', 'location'],
182
+ 'fastcgi_cache_path': ['http'],
183
+ 'fastcgi_cache_use_stale': ['http', 'server', 'location'],
184
+ 'fastcgi_cache_valid': ['http', 'server', 'location'],
185
+ 'fastcgi_connect_timeout': ['http', 'server', 'location'],
186
+ 'fastcgi_hide_header': ['http', 'server', 'location'],
187
+ 'fastcgi_ignore_client_abort': ['http', 'server', 'location'],
188
+ 'fastcgi_ignore_headers': ['http', 'server', 'location'],
189
+ 'fastcgi_index': ['http', 'server', 'location'],
190
+ 'fastcgi_intercept_errors': ['http', 'server', 'location'],
191
+ 'fastcgi_keep_conn': ['http', 'server', 'location'],
192
+ 'fastcgi_max_temp_file_size': ['http', 'server', 'location'],
193
+ 'fastcgi_next_upstream': ['http', 'server', 'location'],
194
+ 'fastcgi_no_cache': ['http', 'server', 'location'],
195
+ 'fastcgi_param': ['http', 'server', 'location'],
196
+ 'fastcgi_pass': ['location', 'if in location', 'if'], #+if
197
+ 'fastcgi_pass_header': ['http', 'server', 'location'],
198
+ 'fastcgi_pass_request_body': ['http', 'server', 'location'],
199
+ 'fastcgi_pass_request_headers': ['http', 'server', 'location'],
200
+ 'fastcgi_read_timeout': ['http', 'server', 'location'],
201
+ 'fastcgi_redirect_errors': ['http', 'server', 'location'],
202
+ 'fastcgi_send_timeout': ['http', 'server', 'location'],
203
+ 'fastcgi_split_path_info': ['location'],
204
+ 'fastcgi_store': ['http', 'server', 'location'],
205
+ 'fastcgi_store_access': ['http', 'server', 'location'],
206
+ 'fastcgi_temp_file_write_size': ['http', 'server', 'location'],
207
+ 'fastcgi_temp_path': ['http', 'server', 'location'],
208
+
209
+ # REF: http://wiki.nginx.org/HttpGeoModule
210
+ 'geo': ['http'],
211
+
212
+ # REF: http://wiki.nginx.org/HttpGzipModule
213
+ 'gzip': ['http', 'server', 'location', 'if in location', 'if'], #+if
214
+ 'gzip_buffers': ['http', 'server', 'location'],
215
+ 'gzip_comp_level': ['http', 'server', 'location'],
216
+ 'gzip_disable': ['http', 'server', 'location'],
217
+ 'gzip_http_version': ['http', 'server', 'location'],
218
+ 'gzip_min_length': ['http', 'server', 'location'],
219
+ 'gzip_proxied': ['http', 'server', 'location'],
220
+ 'gzip_types': ['http', 'server', 'location'],
221
+ 'gzip_vary': ['http', 'server', 'location'],
222
+
223
+ # REF: http://wiki.nginx.org/HttpHeadersModule
224
+ 'add_header': ['http', 'server', 'location'],
225
+ 'expires': ['http', 'server', 'location'],
226
+
227
+ # REF: http://wiki.nginx.org/HttpIndexModule
228
+ 'index': ['http', 'server', 'location'],
229
+
230
+ # REF: http://wiki.nginx.org/HttpLimitReqModule
231
+ 'limit_req': ['http', 'server', 'location'],
232
+ 'limit_req_log_level': ['http', 'server', 'location'],
233
+ 'limit_req_zone': ['http'],
234
+
235
+ # Deprecated in 1.1.8
236
+ # REF: http://wiki.nginx.org/HttpLimitZoneModule
237
+ 'limit_zone': ['http'],
238
+ #'limit_conn': ['http', 'server', 'location'],
239
+ #'limit_conn_log_level': ['http', 'server', 'location'],
240
+
241
+ # REF: http://wiki.nginx.org/HttpLimitConnModule
242
+ 'limit_conn': ['http', 'server', 'location'],
243
+ 'limit_conn_zone': ['http'],
244
+ 'limit_conn_log_level': ['http', 'server', 'location'],
245
+
246
+ # REF: http://wiki.nginx.org/HttpLogModule
247
+ 'access_log': ['http', 'server', 'location', 'if in location', 'limit_except', 'if'], #+if
248
+ 'log_format': ['http'],
249
+ 'open_log_file_cache': ['http', 'server', 'location'],
250
+
251
+ # REF: http://wiki.nginx.org/HttpMapModule
252
+ 'map': ['http'],
253
+ 'map_hash_max_size': ['http'],
254
+ 'map_hash_bucket_size': ['http'],
255
+
256
+ # REF: http://wiki.nginx.org/HttpMemcachedModule
257
+ 'memcached_pass': ['location', 'if in location', 'if'], #+if
258
+ 'memcached_connect_timeout': ['http', 'server', 'location'],
259
+ 'memcached_read_timeout': ['http', 'server', 'location'],
260
+ 'memcached_send_timeout': ['http', 'server', 'location'],
261
+ 'memcached_buffer_size': ['http', 'server', 'location'],
262
+ 'memcached_next_upstream': ['http', 'server', 'location'],
263
+
264
+ # REF: http://wiki.nginx.org/HttpProxyModule
265
+ 'proxy_bind': ['http', 'server', 'location'],
266
+ 'proxy_buffer_size': ['http', 'server', 'location'],
267
+ 'proxy_buffering': ['http', 'server', 'location'],
268
+ 'proxy_buffers': ['http', 'server', 'location'],
269
+ 'proxy_busy_buffers_size': ['http', 'server', 'location'],
270
+ 'proxy_cache': ['http', 'server', 'location'],
271
+ 'proxy_cache_bypass': ['http', 'server', 'location'],
272
+ 'proxy_cache_key': ['http', 'server', 'location'],
273
+ 'proxy_cache_lock': ['http', 'server', 'location'],
274
+ 'proxy_cache_lock_timeout': ['http', 'server', 'location'],
275
+ 'proxy_cache_methods': ['http', 'server', 'location'],
276
+ 'proxy_cache_min_uses': ['http', 'server', 'location'],
277
+ 'proxy_cache_path': ['http'],
278
+ 'proxy_cache_use_stale': ['http', 'server', 'location'],
279
+ 'proxy_cache_valid': ['http', 'server', 'location'],
280
+ 'proxy_connect_timeout': ['http', 'server', 'location'],
281
+ 'proxy_cookie_domain': ['http', 'server', 'location'],
282
+ 'proxy_cookie_path': ['http', 'server', 'location'],
283
+ 'proxy_headers_hash_bucket_size':['http', 'server', 'location'],
284
+ 'proxy_headers_hash_max_size': ['http', 'server', 'location'],
285
+ 'proxy_hide_header': ['http', 'server', 'location'],
286
+ 'proxy_http_version': ['http', 'server', 'location'],
287
+ 'proxy_ignore_client_abort': ['http', 'server', 'location'],
288
+ 'proxy_ignore_headers': ['http', 'server', 'location'],
289
+ 'proxy_intercept_errors': ['http', 'server', 'location'],
290
+ 'proxy_max_temp_file_size': ['http', 'server', 'location'],
291
+ 'proxy_method': ['http', 'server', 'location'],
292
+ 'proxy_next_upstream': ['http', 'server', 'location'],
293
+ 'proxy_no_cache': ['http', 'server', 'location'],
294
+ 'proxy_pass': ['location', 'if in location', 'limit_except', 'if'], #+if
295
+ 'proxy_pass_header': ['http', 'server', 'location'],
296
+ 'proxy_pass_request_body': ['http', 'server', 'location'],
297
+ 'proxy_pass_request_headers': ['http', 'server', 'location'],
298
+ 'proxy_redirect': ['http', 'server', 'location'],
299
+ 'proxy_read_timeout': ['http', 'server', 'location'],
300
+ 'proxy_redirect_errors': ['http', 'server', 'location'],
301
+ 'proxy_send_lowat': ['http', 'server', 'location'],
302
+ 'proxy_send_timeout': ['http', 'server', 'location'],
303
+ 'proxy_set_body': ['http', 'server', 'location'],
304
+ 'proxy_set_header': ['http', 'server', 'location'],
305
+ 'proxy_ssl_session_reuse': ['http', 'server', 'location'],
306
+ 'proxy_store': ['http', 'server', 'location'],
307
+ 'proxy_store_access': ['http', 'server', 'location'],
308
+ 'proxy_temp_file_write_size': ['http', 'server', 'location'],
309
+ 'proxy_temp_path': ['http', 'server', 'location'],
310
+ 'proxy_upstream_fail_timeout': ['http', 'server', 'location'],
311
+ 'proxy_upstream_max_fails': ['http', 'server', 'location'],
312
+
313
+ # REF: http://wiki.nginx.org/HttpRefererModule
314
+ 'valid_referers': ['server', 'location'],
315
+
316
+ # REF: http://wiki.nginx.org/HttpRewriteModule
317
+ 'break': ['server', 'location', 'if'],
318
+ 'if': ['server', 'location'],
319
+ 'return': ['server', 'location', 'if'],
320
+ 'rewrite': ['server', 'location', 'if'],
321
+ 'rewrite_log': ['server', 'location', 'if'],
322
+ 'set': ['server', 'location', 'if'],
323
+ 'uninitialized_variable_warn': ['server', 'location', 'if'],
324
+
325
+ # REF: http://wiki.nginx.org/HttpScgiModule
326
+ 'scgi_bind': ['http', 'server', 'location'],
327
+ 'scgi_buffer_size': ['http', 'server', 'location'],
328
+ 'scgi_buffering': ['http', 'server', 'location'],
329
+ 'scgi_buffers': ['http', 'server', 'location'],
330
+ 'scgi_busy_buffers_size': ['http', 'server', 'location', 'if'],
331
+ 'scgi_cache': ['http', 'server', 'location'],
332
+ 'scgi_cache_bypass': ['http', 'server', 'location'],
333
+ 'scgi_cache_key': ['http', 'server', 'location'],
334
+ 'scgi_cache_methods': ['http', 'server', 'location'],
335
+ 'scgi_cache_min_uses': ['http', 'server', 'location'],
336
+ 'scgi_cache_path': ['http'],
337
+ 'scgi_cache_use_stale': ['http', 'server', 'location'],
338
+ 'scgi_cache_valid': ['http', 'server', 'location'],
339
+ 'scgi_connect_timeout': ['http', 'server', 'location'],
340
+ 'scgi_hide_header': ['http', 'server', 'location'],
341
+ 'scgi_ignore_client_abort': ['http', 'server', 'location'],
342
+ 'scgi_ignore_headers': ['http', 'server', 'location'],
343
+ 'scgi_intercept_errors': ['http', 'server', 'location'],
344
+ 'scgi_max_temp_file_size': ['http', 'server', 'location'], #?
345
+ 'scgi_next_upstream': ['http', 'server', 'location'],
346
+ 'scgi_no_cache': ['http', 'server', 'location'],
347
+ 'scgi_param': ['http', 'server', 'location'],
348
+ 'scgi_pass': ['location', 'if in location', 'if'], #+if
349
+ 'scgi_pass_header': ['http', 'server', 'location'],
350
+ 'scgi_pass_request_body': ['http', 'server', 'location'],
351
+ 'scgi_pass_request_headers': ['http', 'server', 'location'],
352
+ 'scgi_read_timeout': ['http', 'server', 'location'],
353
+ 'scgi_send_timeout': ['http', 'server', 'location'],
354
+ 'scgi_store': ['http', 'server', 'location'],
355
+ 'scgi_store_access': ['http', 'server', 'location'],
356
+ 'scgi_temp_file_write_size': ['http', 'server', 'location'],
357
+ 'scgi_temp_path': ['http', 'server', 'location'],
358
+
359
+ # REF: http://wiki.nginx.org/HttpSplitClientsModule
360
+ 'split_clients': ['http'],
361
+
362
+ # REF: http://wiki.nginx.org/HttpSsiModule
363
+ 'ssi': ['http', 'server', 'location', 'if in location', 'if'], #+if
364
+ 'ssi_silent_errors': ['http', 'server', 'location'],
365
+ 'ssi_types': ['http', 'server', 'location'],
366
+ 'ssi_value_length': ['http', 'server', 'location'],
367
+
368
+ # REF: http://wiki.nginx.org/HttpUpstreamModule
369
+ 'ip_hash': ['upstream'],
370
+ 'keepalive': ['upstream'],
371
+ 'least_conn': ['upstream'],
372
+ #'server': ['upstream'],
373
+ 'upstream': ['http'],
374
+
375
+ # REF: http://wiki.nginx.org/HttpUseridModule
376
+ 'userid': ['http', 'server', 'location'],
377
+ 'userid_domain': ['http', 'server', 'location'],
378
+ 'userid_expires': ['http', 'server', 'location'],
379
+ 'userid_name': ['http', 'server', 'location'],
380
+ 'userid_p3p': ['http', 'server', 'location'],
381
+ 'userid_path': ['http', 'server', 'location'],
382
+ 'userid_service': ['http', 'server', 'location'],
383
+
384
+ # REF: http://wiki.nginx.org/HttpUwsgiModule
385
+ 'uwsgi_bind': ['http', 'server', 'location'],
386
+ 'uwsgi_buffer_size': ['http', 'server', 'location'],
387
+ 'uwsgi_buffering': ['http', 'server', 'location'],
388
+ 'uwsgi_buffers': ['http', 'server', 'location'],
389
+ 'uwsgi_busy_buffers_size': ['http', 'server', 'location', 'if'],
390
+ 'uwsgi_cache': ['http', 'server', 'location'],
391
+ 'uwsgi_cache_bypass': ['http', 'server', 'location'],
392
+ 'uwsgi_cache_key': ['http', 'server', 'location'],
393
+ 'uwsgi_cache_lock': ['http', 'server', 'location'],
394
+ 'uwsgi_cache_lock_timeout': ['http', 'server', 'location'],
395
+ 'uwsgi_cache_methods': ['http', 'server', 'location'],
396
+ 'uwsgi_cache_min_uses': ['http', 'server', 'location'],
397
+ 'uwsgi_cache_path': ['http', 'server', 'location'],
398
+ 'uwsgi_cache_use_stale': ['http', 'server', 'location'],
399
+ 'uwsgi_cache_valid': ['http', 'server', 'location'],
400
+ 'uwsgi_connect_timeout': ['http', 'server', 'location'],
401
+ 'uwsgi_hide_header': ['http', 'server', 'location'],
402
+ 'uwsgi_ignore_client_abort': ['http', 'server', 'location'],
403
+ 'uwsgi_ignore_headers': ['http', 'server', 'location'],
404
+ 'uwsgi_intercept_errors': ['http', 'server', 'location'],
405
+ 'uwsgi_max_temp_file_size': ['http', 'server', 'location'], #?
406
+ 'uwsgi_modifier1': ['server', 'location'],
407
+ 'uwsgi_modifier2': ['server', 'location'],
408
+ 'uwsgi_next_upstream': ['http', 'server', 'location'],
409
+ 'uwsgi_no_cache': ['http', 'server', 'location'],
410
+ 'uwsgi_param': ['http', 'server', 'location'],
411
+ 'uwsgi_pass': ['location', 'if in location', 'if'], #+if
412
+ 'uwsgi_pass_header': ['http', 'server', 'location'],
413
+ 'uwsgi_pass_request_body': ['http', 'server', 'location'],
414
+ 'uwsgi_pass_request_headers': ['http', 'server', 'location'],
415
+ 'uwsgi_read_timeout': ['http', 'server', 'location'],
416
+ 'uwsgi_send_timeout': ['http', 'server', 'location'],
417
+ 'uwsgi_store': ['http', 'server', 'location'],
418
+ 'uwsgi_store_access': ['http', 'server', 'location'],
419
+ 'uwsgi_string': ['server', 'location'],
420
+ 'uwsgi_temp_file_write_size': ['http', 'server', 'location', 'if'],
421
+ 'uwsgi_temp_path': ['http', 'server', 'location'],
422
+
423
+ # REF: http://wiki.nginx.org/HttpSslModule
424
+ 'ssl': ['http', 'server'],
425
+ 'ssl_certificate': ['http', 'server'],
426
+ 'ssl_certificate_key': ['http', 'server'],
427
+ 'ssl_ciphers': ['http', 'server'],
428
+ 'ssl_client_certificate': ['http', 'server'],
429
+ 'ssl_crl': ['http', 'server'],
430
+ 'ssl_dhparam': ['http', 'server'],
431
+ 'ssl_prefer_server_ciphers': ['http', 'server'],
432
+ 'ssl_protocols': ['http', 'server'],
433
+ 'ssl_verify_client': ['http', 'server'],
434
+ 'ssl_verify_depth': ['http', 'server'],
435
+ 'ssl_session_cache': ['http', 'server'],
436
+ 'ssl_session_timeout': ['http', 'server'],
437
+ 'ssl_engine': ['http', 'server'],
438
+
439
+ # REF: http://wiki.nginx.org/X-accel
440
+ # no directive
441
+ }
442
+
443
+ # module name : can has param if act as a context
444
+ # if False then a module name with param would considered a directive
445
+ MODULES = {
446
+ 'events': False,
447
+ 'http': False,
448
+ 'server': False,
449
+ 'upstream': True,
450
+ 'location': True,
451
+ 'types': False,
452
+ 'if': True,
453
+ 'limit_except': True,
454
+ }
455
+
456
+ DEFAULTVALS = {
457
+ 'client_max_body_size': '1m',
458
+ 'keepalive_timeout': '75s',
459
+ }
460
+
461
+ # load nginx module config
462
+ nginx_config = Config(str(Path(config_path) / 'module/nginx.ini'))
463
+ # print('nginx_config', nginx_config.get_config())
464
+ base_config = nginx_config.get('base', 'config')
465
+ base_servers = nginx_config.get('base', 'servers')
466
+
467
+ if base_config and Path(base_config).exists():
468
+ NGINXCONF = base_config
469
+ else:
470
+ NGINXCONF = '/etc/nginx/nginx.conf'
471
+
472
+ if base_servers and Path(base_servers).exists():
473
+ SERVERCONF = base_servers
474
+ else:
475
+ SERVERCONF = '/etc/nginx/conf.d/'
476
+
477
+
478
+ def loadconfig(conf=None, getlineinfo=False):
479
+ """Load nginx config and return a dict.
480
+ """
481
+
482
+ if not conf:
483
+ conf = NGINXCONF
484
+ if not Path(conf).exists():
485
+ return False
486
+ return _loadconfig(conf, getlineinfo)
487
+
488
+
489
+ def _loadconfig(conf, getlineinfo, config=None, context_stack=None):
490
+ """Recursively load nginx config and return a dict.
491
+ """
492
+ if not config:
493
+ file_i = 0
494
+ context = '_'
495
+ config = {'_files': [conf], '_': [{}], '_isdirty': False}
496
+ context_stack = [context]
497
+ cconfig = config[context][-1]
498
+ else:
499
+ if getlineinfo:
500
+ if conf not in config['_files']:
501
+ file_i = len(config['_files'])
502
+ config['_files'].append(conf)
503
+ else:
504
+ file_i = config['_files'].index(conf)
505
+ cconfig = config
506
+ for c in context_stack: cconfig = cconfig[c][-1]
507
+ context = context_stack[-1]
508
+
509
+ line_buffer = []
510
+
511
+ with open(conf, encoding='utf-8') as f:
512
+ for line_i, line in enumerate(f):
513
+ line = line.strip()
514
+ if DEBUG:
515
+ print('----------' + line)
516
+
517
+ # deal with our speical comment string
518
+ line_disabled = False
519
+ if line.startswith(COMMENTFLAG):
520
+ while line.startswith(COMMENTFLAG): line = line[3:]
521
+ line = line.strip()
522
+ line_disabled = True
523
+
524
+ if not line or line.startswith('#'): continue
525
+
526
+ # deal with comment and detect inpanel flag in comment
527
+ fields = line.split('#', 1)
528
+ line = fields[0].strip()
529
+ gen_by_inpanel = False
530
+ if len(fields) > 1 and fields[1].strip() == GENBY:
531
+ gen_by_inpanel = True
532
+
533
+ # context up
534
+ if line == '}':
535
+ if getlineinfo:
536
+ cconfig['_range']['end'] = {'file': file_i, 'line': [line_i, 1]}
537
+ if DEBUG:
538
+ print(context_stack, '-', context_stack[-1])
539
+ context_stack.pop()
540
+ context = context_stack[-1]
541
+ if DEBUG:
542
+ print('=', context_stack)
543
+ cconfig = config
544
+ for c in context_stack: cconfig = cconfig[c][-1]
545
+ continue
546
+
547
+ # this line may not ending, combine it with next line
548
+ if line[-1] not in (';', '{', '}'):
549
+ line_buffer.append(line)
550
+ continue
551
+ elif len(line_buffer)>0:
552
+ line_buffer.append(line)
553
+ line = ''.join(line_buffer)
554
+ line_count = len(line_buffer)
555
+ line_buffer = []
556
+ else:
557
+ line_count = 1
558
+
559
+ # only support one directive at a line
560
+ ## one line may contain serveral directives
561
+ #parts = re.split('[;{}]', line)
562
+ #for part_i, part in enumerate(parts):
563
+ # if not part or part.startswith('#'): continue
564
+ # fields = part.split()
565
+ fields = line.split()
566
+ key = fields[0].strip(';') # some directive have no value like ip_hash
567
+ #value = ' '.join(fields[1:]).strip()
568
+ value = ' '.join(fields[1:]).strip(';')
569
+
570
+ if key in DIRECTIVES and context in DIRECTIVES[key]:
571
+ if (not key in MODULES # not in module name list
572
+ or key in MODULES # or in module name list
573
+ and not MODULES[key] # and this module can't has param
574
+ and value != '{'): # but actually it has
575
+ if getlineinfo:
576
+ v = {'file': file_i, 'line': [line_i-line_count+1, line_count], 'value': value}
577
+ else:
578
+ v = value
579
+ if key in cconfig:
580
+ cconfig[key].append(v)
581
+ else:
582
+ cconfig[key] = [v]
583
+ if key == 'include': # expand include files
584
+ includepath = value
585
+ if not includepath.startswith('/'):
586
+ includepath = str(Path(config['_files'][0]).parent / includepath)
587
+ confs = glob(includepath)
588
+ # order by domain name, excluding tld
589
+ # getdm = lambda x: x.split('/')[-1].split('.')[-3::-1]
590
+ # confs = sorted(confs, lambda x,y: cmp(getdm(x), getdm(y)))
591
+ for subconf in confs:
592
+ if Path(subconf).is_file():
593
+ if DEBUG:
594
+ print('\n**********', subconf, '\n')
595
+ _loadconfig(subconf, getlineinfo, config, context_stack)
596
+ else:
597
+ context = key
598
+ if DEBUG: print(context_stack, '+', context)
599
+ context_stack.append(context)
600
+ if DEBUG: print('=', context_stack)
601
+ if context in cconfig:
602
+ cconfig[context].append({})
603
+ else:
604
+ cconfig[context] = [{}]
605
+ cconfig = cconfig[context][-1]
606
+ value = value.strip('{').strip()
607
+ if getlineinfo:
608
+ cconfig['_param'] = {'file': file_i, 'line': [line_i-line_count+1, line_count], 'disabled': line_disabled, 'value': value}
609
+ # record the context range
610
+ cconfig['_range'] = {'begin': {'file': file_i, 'line': [line_i-line_count+1, line_count]}}
611
+ cconfig['_inpanel'] = gen_by_inpanel
612
+ else:
613
+ cconfig['_param'] = value
614
+ cconfig['_disabled'] = line_disabled
615
+ cconfig['_inpanel'] = gen_by_inpanel
616
+
617
+ return config
618
+
619
+ def _context_gethttp(config=None):
620
+ """Get http context config.
621
+ """
622
+ if not config or config['_isdirty']:
623
+ config = loadconfig(NGINXCONF, True)
624
+ return config['_'][0]['http'][0]
625
+
626
+ def _context_getservers(disabled=None, config=None, getlineinfo=True):
627
+ """Get server context configs.
628
+ """
629
+ if not config or config['_isdirty']:
630
+ config = loadconfig(NGINXCONF, getlineinfo)
631
+ http = config['_'][0]['http'][0]
632
+ if not 'server' in http:
633
+ return []
634
+ servers = http['server']
635
+ if disabled == None or not getlineinfo:
636
+ return servers
637
+ else:
638
+ return [server for server in servers
639
+ if server['_param']['disabled']==disabled]
640
+
641
+ def _context_getserver(ip, port, server_name, config=None, disabled=None, getlineinfo=True):
642
+ """Get a server context config by server name.
643
+
644
+ If disabled is set to None, all servers would be return.
645
+ If disabled is set to True, only disabled servers would be return.
646
+ If disabled is set to False, only normal servers would be return.
647
+ """
648
+ if not config or config['_isdirty']:
649
+ config = loadconfig(NGINXCONF, getlineinfo)
650
+ cnfservers = _context_getservers(disabled=disabled, config=config, getlineinfo=getlineinfo)
651
+ if not ip or ip in ('*', '0.0.0.0'): ip = ''
652
+ if is_valid_ipv6(ip) and not is_valid_ipv4(ip):
653
+ ip = '[' + ip + ']'
654
+ for s in cnfservers:
655
+ if getlineinfo:
656
+ server_names = ' '.join([v['value'] for v in s['server_name']]).split()
657
+ listens = [v['value'].split()[0] for v in s['listen']]
658
+ else:
659
+ server_names = ' '.join([v for v in s['server_name']]).split()
660
+ listens = [v.split()[0] for v in s['listen']]
661
+ find_listen = ip and ['%s:%s' % (ip, port)] or [port, '*:%s' % port, '0.0.0.0:%s' % port]
662
+ if server_name in server_names and any([i in listens for i in find_listen]):
663
+ return s
664
+ return False
665
+
666
+
667
+ def _context_getupstreams(server_name, config=None, disabled=None, getlineinfo=True):
668
+ """Get upstream list related to a server.
669
+ """
670
+ if not config or config['_isdirty']:
671
+ config = loadconfig(NGINXCONF, getlineinfo)
672
+ upstreams = http_get('upstream', config)
673
+ if not upstreams: return False
674
+ if getlineinfo:
675
+ upstreams = [upstream for upstream in upstreams
676
+ if upstream['_param']['value'].startswith('backend_of_%s_' % server_name)]
677
+ else:
678
+ upstreams = [upstream for upstream in upstreams
679
+ if upstream['_param'].startswith('backend_of_%s_' % server_name)]
680
+
681
+ if disabled == None or not getlineinfo:
682
+ return upstreams
683
+ else:
684
+ return [upstream for upstream in upstreams
685
+ if upstream['_param']['disabled']==disabled]
686
+
687
+ def _comment(filepath, start, end):
688
+ """Commend some lines in the file.
689
+ """
690
+ if not Path(filepath).is_file(): return False
691
+ data = []
692
+ with open(filepath, encoding='utf-8') as f:
693
+ for i, line in enumerate(f):
694
+ if i >= start and i <= end:
695
+ if not line.startswith(COMMENTFLAG):
696
+ data.append(COMMENTFLAG)
697
+ data.append(line)
698
+ with open(filepath, 'w', encoding='utf-8') as f:
699
+ f.write(''.join(data))
700
+ return True
701
+
702
+ def _uncomment(filepath, start, end):
703
+ """Uncommend some lines in the file.
704
+ """
705
+ if not Path(filepath).is_file():
706
+ return False
707
+ data = []
708
+ with open(filepath, encoding='utf-8') as f:
709
+ for i, line in enumerate(f):
710
+ if i >= start and i <= end:
711
+ while line.startswith(COMMENTFLAG):
712
+ line = line[3:]
713
+ data.append(line)
714
+ with open(filepath, 'w', encoding='utf-8') as f:
715
+ f.write(''.join(data))
716
+ return True
717
+
718
+ def _delete(filepath, start, end, delete_emptyfile=True):
719
+ """Delete some lines in the file.
720
+
721
+ If delete_emptyfile is set to True, then the empty file will
722
+ be deleted from file system.
723
+ """
724
+ if not Path(filepath).is_file(): return False
725
+ data = []
726
+ with open(filepath, encoding='utf-8') as f:
727
+ for i, line in enumerate(f):
728
+ if i >= start and i <= end:
729
+ continue
730
+ data.append(line)
731
+ with open(filepath, 'w', encoding='utf-8') as f:
732
+ f.write(''.join(data))
733
+ if delete_emptyfile:
734
+ if ''.join(data).strip() == '':
735
+ Path(filepath).unlink()
736
+ return True
737
+
738
+ def _getcontextrange(context, config):
739
+ """Return the range of the input context, including the file path.
740
+
741
+ Return format:
742
+ [filepath, line_start, line_end]
743
+ """
744
+ file_i = context['_range']['begin']['file']
745
+ filepath = config['_files'][file_i]
746
+ line_start = context['_range']['begin']['line'][0]
747
+ line_end = context['_range']['end']['line'][0]
748
+ return [filepath, line_start, line_end]
749
+
750
+ def _context_commentserver(ip, port, server_name, config=None):
751
+ """Comment a context using InPanel's special comment string '#v#'
752
+ """
753
+ if not config or config['_isdirty']:
754
+ config = loadconfig(NGINXCONF, True)
755
+ scontext = _context_getserver(ip, port, server_name, config=config)
756
+ if not scontext: return False
757
+ filepath, line_start, line_end = _getcontextrange(scontext, config)
758
+ return _comment(filepath, line_start, line_end)
759
+
760
+ def _context_uncommentserver(ip, port, server_name, config=None):
761
+ """Uncomment a InPanel's special-commented context.
762
+ """
763
+ if not config or config['_isdirty']:
764
+ config = loadconfig(NGINXCONF, True)
765
+ scontext = _context_getserver(ip, port, server_name, config=config)
766
+ if not scontext: return False
767
+ filepath, line_start, line_end = _getcontextrange(scontext, config)
768
+ return _uncomment(filepath, line_start, line_end)
769
+
770
+ def _context_deleteserver(ip, port, server_name, config=None, disabled=None):
771
+ """Delete a server context.
772
+ """
773
+ if not config or config['_isdirty']:
774
+ config = loadconfig(NGINXCONF, True)
775
+ scontext = _context_getserver(ip, port, server_name, config=config, disabled=disabled)
776
+ if not scontext: return False
777
+ filepath, line_start, line_end = _getcontextrange(scontext, config)
778
+ config['_isdirty'] = True
779
+ return _delete(filepath, line_start, line_end)
780
+
781
+ def _context_commentupstreams(server_name, config=None):
782
+ """Comment upstreams by server names.
783
+ """
784
+ if not config or config['_isdirty']:
785
+ config = loadconfig(NGINXCONF, True)
786
+ upstreams = _context_getupstreams(server_name, config=config)
787
+ if not upstreams: return True
788
+ for upstream in upstreams:
789
+ filepath, line_start, line_end = _getcontextrange(upstream, config)
790
+ if not _comment(filepath, line_start, line_end): return False
791
+ return True
792
+
793
+ def _context_uncommentupstreams(server_name, config=None):
794
+ """Uncomment upstreams by server names.
795
+ """
796
+ if not config or config['_isdirty']:
797
+ config = loadconfig(NGINXCONF, True)
798
+ upstreams = _context_getupstreams(server_name, config=config)
799
+ if not upstreams: return True
800
+ for upstream in upstreams:
801
+ filepath, line_start, line_end = _getcontextrange(upstream, config)
802
+ if not _uncomment(filepath, line_start, line_end): return False
803
+ return True
804
+
805
+ def _context_deleteupstreams(server_name, config=None, disabled=None):
806
+ """Delete upstreams by server name.
807
+ """
808
+ while True:
809
+ # we need to reload config after delete one upstream
810
+ if not config or config['_isdirty']:
811
+ config = loadconfig(NGINXCONF, True)
812
+ upstreams = _context_getupstreams(server_name, config=config, disabled=disabled)
813
+ if not upstreams: return True
814
+ for upstream in upstreams:
815
+ filepath, line_start, line_end = _getcontextrange(upstream, config)
816
+ config['_isdirty'] = True
817
+ if not _delete(filepath, line_start, line_end): return False
818
+ break # only delete the first one
819
+ return True
820
+
821
+ def _context_http_init_limit_conn(config=None, version=None):
822
+ """Init limit_conn_zone in http context.
823
+
824
+ We only recognize limit_conn_zone with name "addr".
825
+
826
+ Return True if initialized do.
827
+ Or False if nothing to do with it.
828
+ """
829
+ if not config or config['_isdirty']:
830
+ config = loadconfig(NGINXCONF, True)
831
+
832
+ if not version or version_get(version, '1.1.8'):
833
+ conn_zones = http_get('limit_conn_zone')
834
+ if not conn_zones:
835
+ http_set('limit_conn_zone', '$binary_remote_addr zone=addr:10m', config)
836
+ return True
837
+ else:
838
+ addr_conn_zone_found = False
839
+ for conn_zone in conn_zones:
840
+ zonename = conn_zone.split('zone=', 1)[-1].split(':', 1)[0]
841
+ if zonename == 'addr':
842
+ addr_conn_zone_found = True
843
+ break
844
+ if not addr_conn_zone_found:
845
+ conn_zones.append('$binary_remote_addr zone=addr:10m')
846
+ http_set('limit_conn_zone', conn_zones, config)
847
+ return True
848
+ else:
849
+ return False
850
+ else:
851
+ conn_zones = http_get('limit_zone')
852
+ if not conn_zones:
853
+ http_set('limit_zone', 'addr $binary_remote_addr 10m', config)
854
+ return True
855
+ else:
856
+ addr_conn_zone_found = False
857
+ for conn_zone in conn_zones:
858
+ zonename = conn_zone.split()[0]
859
+ if zonename == 'addr':
860
+ addr_conn_zone_found = True
861
+ break
862
+ if not addr_conn_zone_found:
863
+ conn_zones.append('addr $binary_remote_addr 10m')
864
+ http_set('limit_zone', conn_zones, config)
865
+ return True
866
+ else:
867
+ return False
868
+
869
+
870
+ def _context_server_clear_default_server(ip, port, config=None):
871
+ """Clear the default_server flag at specified ip:port.
872
+ """
873
+ if not config or config['_isdirty']:
874
+ config = loadconfig(NGINXCONF, True)
875
+ cnfservers = _context_getservers(config=config)
876
+ found = False
877
+ for cnfserver in cnfservers:
878
+ listens = cnfserver['listen']
879
+ for listen in listens:
880
+ if 'default_server' in listen['value']:
881
+ _replace([(config['_files'][listen['file']], listen['line'][0], listen['line'][1])],
882
+ ['listen %s;' % listen['value'].replace('default_server', '').strip()])
883
+ found = True
884
+ break
885
+ if found: break
886
+ return found
887
+
888
+ def _replace(positions, lines):
889
+ """Replace the lines specified in list positions to new lines.
890
+
891
+ Structure of positions:
892
+ [
893
+ (filepath, line_start, line_count),
894
+ ...
895
+ ]
896
+ Parameter positions can not be empty.
897
+
898
+ * If the new lines is empty, old lines with be deleted.
899
+ * If the new lines is less than the old lines, the rest old lines
900
+ will also be deleted.
901
+ * If the new lines is more than the old lines, then new value will
902
+ append after the last line of the old lines.
903
+ """
904
+ # merge line positions by filepath
905
+ # struct: {'/path/to/file': [3, 4, 10, ...], ...}
906
+ files = {}
907
+ for pos in positions:
908
+ filepath, line_start, line_count = pos
909
+ if not filepath in files:
910
+ files[filepath] = []
911
+ for i in range(line_count):
912
+ files[filepath].append(line_start+i)
913
+ # replace line by line
914
+ for filepath, line_nums in files.items():
915
+ flines = []
916
+ with open(filepath, encoding='utf-8') as f:
917
+ for i, fline in enumerate(f):
918
+ if i in line_nums:
919
+ if len(lines) > 0:
920
+ # replace with a new line
921
+ line = lines[0]
922
+ lines = lines[1:]
923
+ # detect the space at the start of the old line
924
+ # this aim to keep the indent of the line
925
+ space = ''
926
+ for c in fline:
927
+ if c not in (' ', '\t'):
928
+ break
929
+ space += c
930
+ flines.append(''.join([space, line, '\n']))
931
+ else:
932
+ # no more new line, delete the old line
933
+ continue
934
+ else:
935
+ if i > line_nums[-1] and len(lines) > 0:
936
+ # exceed the last old line, insert the rest new lines here
937
+ # detect the indent of the last line
938
+ space = ''
939
+ if len(flines)>0: # last line exists
940
+ for c in flines[-1]:
941
+ if c not in (' ', '\t'):
942
+ break
943
+ space += c
944
+ for line in lines:
945
+ flines.append(''.join([space, line, '\n']))
946
+ lines = []
947
+ flines.append(fline)
948
+ # write back to file
949
+ with open(filepath, 'w', encoding='utf-8') as f:
950
+ f.write(''.join(flines))
951
+
952
+ def _insert(filepath, line_start, lines):
953
+ """Insert the lines to the specified position.
954
+ """
955
+ flines = []
956
+ with open(filepath, encoding='utf-8') as f:
957
+ for i, fline in enumerate(f):
958
+ if i == line_start:
959
+ # detect the indent of the last not empty line
960
+ space = ''
961
+ flines_len = len(flines)
962
+ if flines_len>0: # last line exists
963
+ line_i = -1
964
+ while flines[line_i].strip() == '' and -line_i <= flines_len:
965
+ line_i -= 1
966
+ for c in flines[line_i]:
967
+ if c not in (' ', '\t'):
968
+ break
969
+ space += c
970
+ if flines[line_i].strip().endswith('{'):
971
+ space += ' '
972
+ for line in lines:
973
+ flines.append(''.join([space, line, '\n']))
974
+ flines.append(fline)
975
+ # write back to file
976
+ with open(filepath, 'w', encoding='utf-8') as f:
977
+ f.write(''.join(flines))
978
+
979
+ def _detect_engines(context):
980
+ """Detect engines in context.
981
+ """
982
+ engine_flags = {
983
+ 'root' : 'static',
984
+ 'alias' : 'static_alias',
985
+ 'fastcgi_pass': 'fastcgi',
986
+ 'scgi_pass': 'scgi',
987
+ 'uwsgi_pass': 'uwsgi',
988
+ 'proxy_pass': 'proxy',
989
+ 'rewrite': 'rewrite',
990
+ 'return': 'return',
991
+ }
992
+ contexts = ['location', 'if', 'limit_except']
993
+
994
+ engines = []
995
+ for k, v in context.items():
996
+ if k in engine_flags:
997
+ if k == 'rewrite':
998
+ engines.extend([x and 'redirect' or 'rewrite' for x in _isredirect(v)])
999
+ else:
1000
+ engines.append(engine_flags[k])
1001
+ elif k in contexts:
1002
+ for ctx in context[k]:
1003
+ engines.extend(_detect_engines(ctx))
1004
+
1005
+ # return a uniq result
1006
+ engines = list(set(engines))
1007
+ if 'static_alias' in engines:
1008
+ if len(engines) == 1:
1009
+ engines[0] = 'static'
1010
+ else:
1011
+ engines = filter(lambda a: a != 'static_alias', engines)
1012
+ return engines
1013
+
1014
+ def _isredirect(values):
1015
+ """Check if rewrite values is redirect.
1016
+ """
1017
+ return [v.split().pop() in ('redirect', 'permanent') for v in values]
1018
+
1019
+ def getservers(config=None):
1020
+ """Get servers from nginx configuration files.
1021
+
1022
+ Struct of server dict:
1023
+ {
1024
+ 'server_names': [
1025
+ 'test.com',
1026
+ 'www.test.com'
1027
+ ],
1028
+ 'listens': [
1029
+ {'ip': '192.168.1.2', 'port': '80', 'default_server': true},
1030
+ {'ip': '*', 'port': '88', 'default_server': false}
1031
+ ],
1032
+ 'engines': [
1033
+ 'static',
1034
+ 'rewrite',
1035
+ 'redirect',
1036
+ 'fastcgi',
1037
+ 'scgi',
1038
+ 'uwsgi',
1039
+ 'proxy',
1040
+ ],
1041
+ }
1042
+ """
1043
+ if not config or config['_isdirty']:
1044
+ config = loadconfig(NGINXCONF) # do not need to load lineinfo
1045
+ cnfservers = _context_getservers(config=config)
1046
+ servers = []
1047
+ for s in cnfservers:
1048
+ server = {}
1049
+ server['server_names'] = ' '.join(s['server_name']).split()
1050
+ # parse server and port, and check if is default server
1051
+ server['listens'] = []
1052
+ for listen in s['listen']:
1053
+ port_ip = listen.split()[0]
1054
+ port = port_ip.split(':')[-1]
1055
+ ip = port_ip[0:-(len(port) + 1)]
1056
+ ip = ip.lstrip('[').rstrip(']') # for IPv6
1057
+ server['listens'].append({
1058
+ 'ip': ip or '*',
1059
+ 'port': port,
1060
+ 'default_server': 'default_server' in listen
1061
+ })
1062
+ engines = _detect_engines(s)
1063
+ engine_orders = ['static', 'fastcgi', 'scgi', 'uwsgi', 'redirect', 'proxy', 'rewrite', 'return']
1064
+ engines = [(engine_orders.index(engine), engine) for engine in engines]
1065
+ engines.sort()
1066
+ server['engines'] = list(zip(*engines))[1] if engines else []
1067
+ # check the status of this server
1068
+ server['status'] = 'off' if s['_disabled'] else 'on'
1069
+ servers.append(server)
1070
+ return servers
1071
+
1072
+ def http_get(directive, config=None):
1073
+ """Get directive values in http context.
1074
+ """
1075
+ if not config or config['_isdirty']:
1076
+ config = loadconfig(NGINXCONF)
1077
+ hcontext = _context_gethttp(config)
1078
+ if directive in hcontext:
1079
+ return hcontext[directive]
1080
+ elif directive in DEFAULTVALS:
1081
+ return DEFAULTVALS[directive]
1082
+ else:
1083
+ return None
1084
+
1085
+ def http_getfirst(directive, config=None):
1086
+ """Get the first value of the directive in http context.
1087
+ """
1088
+ values = http_get(directive, config)
1089
+ if values: return values[0]
1090
+ return None
1091
+
1092
+ def http_set(directive, values, config=None):
1093
+ """Set a directive in http context.
1094
+
1095
+ If directive exists, the value will be replace in place.
1096
+ If directive not exists, new directive will be created at the beginning of http context.
1097
+
1098
+ Parameter values can be a list or a string.
1099
+ If values is set to empty list or None or empty string, then the directive will be deleted.
1100
+ """
1101
+ if not config or config['_isdirty']:
1102
+ config = loadconfig(NGINXCONF, True)
1103
+ hcontext = _context_gethttp(config)
1104
+
1105
+ if not values:
1106
+ values = []
1107
+ elif isinstance(values, str):
1108
+ values = [values]
1109
+ values = ['%s %s;' % (directive, v) for v in values]
1110
+
1111
+ if directive in hcontext:
1112
+ # update or delete value
1113
+ dvalues = hcontext[directive]
1114
+ lines = [(config['_files'][dvalue['file']], dvalue['line'][0], dvalue['line'][1]) for dvalue in dvalues]
1115
+ _replace(lines, values)
1116
+ else:
1117
+ # add directive to the beginning of http context
1118
+ # some directive like proxy_cache_path should be declare before use the resource,
1119
+ # so we should insert it at the beginning
1120
+ begin = hcontext['_range']['begin']
1121
+ _insert(config['_files'][begin['file']], begin['line'][0]+begin['line'][1], values)
1122
+
1123
+ config['_isdirty'] = True
1124
+
1125
+ def disableserver(ip, port, server_name):
1126
+ """Disable a server.
1127
+ """
1128
+ _context_commentupstreams(server_name)
1129
+ return _context_commentserver(ip, port, server_name)
1130
+
1131
+ def enableserver(ip, port, server_name):
1132
+ """Enable a server.
1133
+ """
1134
+ _context_uncommentupstreams(server_name)
1135
+ return _context_uncommentserver(ip, port, server_name)
1136
+
1137
+ def deleteserver(ip, port, server_name, disabled=None):
1138
+ """Delete a server.
1139
+
1140
+ If disabled is not None, it will only delete those servers which is disabled or enabled.
1141
+ """
1142
+ _context_deleteupstreams(server_name, disabled=disabled)
1143
+ return _context_deleteserver(ip, port, server_name, disabled=disabled)
1144
+
1145
+ def servername_exists(ip, port, server_name, config=None):
1146
+ """Check if the server_name at given ip:port is already exists.
1147
+ """
1148
+ return _context_getserver(ip, port, server_name, config=config) and True or False
1149
+
1150
+ def getserver(ip, port, server_name, config=None):
1151
+ """Get server setting from nginx config files.
1152
+ """
1153
+ if not config or config['_isdirty']:
1154
+ config = loadconfig(NGINXCONF, False)
1155
+ scontext = _context_getserver(ip, port, server_name, config=config, getlineinfo=False)
1156
+ if not scontext: return False
1157
+
1158
+ server = {}
1159
+ server['_inpanel'] = scontext['_inpanel']
1160
+ server['server_names'] = []
1161
+ if 'server_name' in scontext:
1162
+ for name in scontext['server_name']:
1163
+ server['server_names'].extend(name.split())
1164
+
1165
+ server['listens'] = []
1166
+ if 'listen' in scontext:
1167
+ for listen in scontext['listen']:
1168
+ port_ip = listen.split()[0]
1169
+ port = port_ip.split(':')[-1]
1170
+ ip = port_ip[0:-(len(port) + 1)]
1171
+ ip = ip.lstrip('[').rstrip(']') # for IPv6
1172
+ server['listens'].append({
1173
+ 'ip': ip or '',
1174
+ 'port': port,
1175
+ 'ssl': 'ssl' in listen,
1176
+ 'default_server': 'default_server' in listen
1177
+ })
1178
+
1179
+ if 'charset' in scontext: server['charset'] = scontext['charset'][-1]
1180
+ if 'index' in scontext: server['index'] = scontext['index'][-1]
1181
+ if 'limit_rate' in scontext: server['limit_rate'] = scontext['limit_rate'][-1].replace('k','')
1182
+ if 'limit_conn' in scontext: server['limit_conn'] = scontext['limit_conn'][-1].split()[-1]
1183
+ if 'ssl' in scontext and scontext['ssl'][-1] == 'on': # deal with old config file
1184
+ for listen in server['listens']: listen['ssl'] = True
1185
+ if 'ssl_certificate' in scontext: server['ssl_crt'] = scontext['ssl_certificate'][-1]
1186
+ if 'ssl_certificate_key' in scontext: server['ssl_key'] = scontext['ssl_certificate_key'][-1]
1187
+ if 'rewrite' in scontext:
1188
+ server['rewrite_rules'] = ['rewrite %s' % rule for rule in scontext['rewrite']]
1189
+
1190
+ server['locations'] = []
1191
+ if 'location' in scontext:
1192
+ locs = scontext['location']
1193
+ for loc in locs:
1194
+ location = {}
1195
+ location['urlpath'] = loc['_param']
1196
+ if 'root' in loc:
1197
+ location['root'] = loc['root'][-1]
1198
+ elif 'root' in scontext: # deal with old config file
1199
+ location['root'] = scontext['root'][-1]
1200
+ if 'alias' in loc:
1201
+ location['root'] = loc['alias'][-1]
1202
+ if 'autoindex' in loc:
1203
+ location['autoindex'] = loc['autoindex'][-1] == 'on'
1204
+
1205
+ # detect redirect and rewrite
1206
+ # we treat redirect and rewrite conflict
1207
+ rewrites = []
1208
+ if 'rewrite' in loc:
1209
+ rewrites = loc['rewrite']
1210
+ if 'if' in loc:
1211
+ for ifstm in loc['if']:
1212
+ if re.sub(r'\s', '', ifstm['_param']) == '(!-e$request_filename)':
1213
+ location['rewrite_detect_file'] = True
1214
+ if 'rewrite' in ifstm:
1215
+ rewrites.extend(ifstm['rewrite'])
1216
+
1217
+ for rule in rewrites:
1218
+ rwinfo = rule.split()
1219
+ if rwinfo[-1] in ('permanent', 'redirect'):
1220
+ if rwinfo[1].endswith('$request_uri?') or rwinfo[1].endswith('$request_uri_alias?'):
1221
+ redirect_option = 'keep'
1222
+ redirect_url = rwinfo[1].replace('$request_uri?', '').replace('$request_uri_alias?', '')
1223
+ else:
1224
+ redirect_option = 'ignore'
1225
+ redirect_url = rwinfo[1]
1226
+ redirect_type = rwinfo[-1] == 'permanent' and '301' or '302'
1227
+ location['redirect_url'] = redirect_url
1228
+ location['redirect_type'] = redirect_type
1229
+ location['redirect_option'] = redirect_option
1230
+ else:
1231
+ if not 'rewrite_rules' in location:
1232
+ location['rewrite_rules'] = []
1233
+ location['rewrite_rules'].append('rewrite %s' % rule)
1234
+
1235
+ # detect fastcgi
1236
+ if 'fastcgi_pass' in loc:
1237
+ location['fastcgi_pass'] = loc['fastcgi_pass'][-1]
1238
+ elif 'location' in loc:
1239
+ for l in loc['location']:
1240
+ if 'fastcgi_pass' in l:
1241
+ location['fastcgi_pass'] = l['fastcgi_pass'][-1]
1242
+ break # only recoginze the first fastcgi setting
1243
+
1244
+ # detect proxy
1245
+ if 'proxy_pass' in loc:
1246
+ backend = loc['proxy_pass'][-1]
1247
+ if backend.startswith('http'): location['proxy_protocol'] = 'http'
1248
+ if backend.startswith('https'): location['proxy_protocol'] = 'https'
1249
+ if 'proxy_protocol' in location:
1250
+ if 'proxy_set_header' in loc:
1251
+ headers = loc['proxy_set_header']
1252
+ for header in headers:
1253
+ hinfo = re.split(r'\s+', header, 1)
1254
+ if len(hinfo) == 1: continue
1255
+ if hinfo[0] == 'X-Real-IP':
1256
+ if hinfo[1] == '$remote_addr':
1257
+ location['proxy_realip'] = True
1258
+ elif hinfo[0] == 'Host':
1259
+ location['proxy_host'] = hinfo[1]
1260
+ # detect upstream
1261
+ upstream_name = 'backend_of_%s_%s' % (server_name, location['urlpath'].replace('/', '_'))
1262
+ if backend == '%s://%s' % (location['proxy_protocol'], upstream_name):
1263
+ upstreams = http_get('upstream', config)
1264
+ for upstream in upstreams:
1265
+ if upstream['_param'] == upstream_name:
1266
+ if 'ip_hash' in upstream: location['proxy_balance'] = 'ip_hash'
1267
+ if 'least_conn' in upstream: location['proxy_balance'] = 'least_conn'
1268
+ if not 'proxy_balance' in location: location['proxy_balance'] = 'weight'
1269
+ if 'keepalive' in upstream: location['proxy_keepalive'] = upstream['keepalive'][-1]
1270
+ if 'server' in upstream:
1271
+ location['proxy_backends'] = []
1272
+ for s in upstream['server']:
1273
+ sinfo = s.split()
1274
+ backend = {}
1275
+ backend['server'] = sinfo[0]
1276
+ for t in sinfo[1:]:
1277
+ if t.startswith('weight='):
1278
+ backend['weight'] = t.replace('weight=', '')
1279
+ elif t.startswith('max_fails='):
1280
+ backend['max_fails'] = t.replace('max_fails=', '')
1281
+ elif t.startswith('fail_timeout='):
1282
+ backend['fail_timeout'] = t.replace('fail_timeout=', '').replace('s','')
1283
+ location['proxy_backends'].append(backend)
1284
+ break # found the right upstream
1285
+ # detect proxy cache
1286
+ if 'proxy_cache' in loc:
1287
+ cachename = loc['proxy_cache'][-1]
1288
+ if cachename.lower() != 'off':
1289
+ location['proxy_cache'] = cachename
1290
+ if 'proxy_cache_min_uses' in loc:
1291
+ location['proxy_cache_min_uses'] = loc['proxy_cache_min_uses'][-1]
1292
+ if 'proxy_cache_methods' in loc and 'POST' in loc['proxy_cache_methods'][-1].upper().split():
1293
+ location['proxy_cache_methods'] = 'POST'
1294
+ if 'proxy_cache_key' in loc:
1295
+ location['proxy_cache_key'] = loc['proxy_cache_key'][-1]
1296
+ if 'proxy_cache_use_stale' in loc:
1297
+ location['proxy_cache_use_stale'] = loc['proxy_cache_use_stale'][-1].split()
1298
+ if 'proxy_cache_valid' in loc:
1299
+ location['proxy_cache_valid'] = []
1300
+ for valid in loc['proxy_cache_valid']:
1301
+ fields = valid.split()
1302
+ for i in range(0, len(fields)-1):
1303
+ location['proxy_cache_valid'].append({'code': fields[i], 'time': fields[-1]})
1304
+ if 'proxy_cache_lock' in loc:
1305
+ location['proxy_cache_lock'] = loc['proxy_cache_lock'][-1].lower() == 'on'
1306
+ if 'proxy_cache_lock_timeout' in loc:
1307
+ location['proxy_cache_lock_timeout'] = loc['proxy_cache_lock_timeout'][-1].replace('s', '')
1308
+
1309
+ # detect error code
1310
+ if 'return' in loc:
1311
+ location['error_code'] = loc['return'][-1]
1312
+
1313
+ server['locations'].append(location)
1314
+
1315
+ return server
1316
+
1317
+ def addserver(server_names, listens, charset=None, index=None, locations=None,
1318
+ limit_rate=None, limit_conn=None, ssl_crt=None, ssl_key=None,
1319
+ rewrite_rules=None, conflict_check=True, version=None):
1320
+ """Add a new server.
1321
+
1322
+ We create new config file for each server under /etc/nginx/conf.d/.
1323
+ Config file name depends on the first of server_names.
1324
+
1325
+ Parameter examples:
1326
+ * server_names: ['example.com', 'www.example.com']
1327
+ * listens:
1328
+ [
1329
+ {
1330
+ 'ip': '',
1331
+ 'port': '80',
1332
+ 'ssl': True,
1333
+ 'default_server': False
1334
+ }
1335
+ ]
1336
+ * charset: 'utf-8'
1337
+ * index: 'index.html index.htm index.php'
1338
+ * locations:
1339
+ [
1340
+ {
1341
+ 'urlpath': '/',
1342
+ 'root': '/var/www/example',
1343
+ 'fastcgi_pass': '127.0.0.1:9000',
1344
+ 'rewrite_detect_file': True,
1345
+ 'rewrite_rules': [
1346
+ 'rewrite ^/$ /index.php last',
1347
+ 'rewrite ^/(.*)$ /index.php/$1 last',
1348
+ ],
1349
+ },
1350
+ {
1351
+ 'urlpath': '/download',
1352
+ 'root': '/var/www/example/download',
1353
+ 'autoindex': True,
1354
+ },
1355
+ {
1356
+ 'urlpath': '/redirect',
1357
+ 'redirect_url': 'http://test.com/',
1358
+ 'redirect_type': '301',
1359
+ 'redirect_option': 'keep',
1360
+ },
1361
+ {
1362
+ 'urlpath': '/proxy',
1363
+ 'proxy_balance': 'weight',
1364
+ 'proxy_protocol': 'http',
1365
+ 'proxy_host': 'www.baidu.com',
1366
+ 'proxy_charset': 'utf-8',
1367
+ 'proxy_realip': True,
1368
+ 'proxy_keepalive': 10,
1369
+ 'proxy_backends': [
1370
+ {'server':'www.baidu.com'}
1371
+ ],
1372
+ 'proxy_cache': 'proxycache',
1373
+ 'proxy_cache_min_uses': '5',
1374
+ 'proxy_cache_methods': 'POST',
1375
+ 'proxy_cache_key': '$scheme$proxy_host$request_uri',
1376
+ 'proxy_cache_valid': [
1377
+ {
1378
+ 'code': '200',
1379
+ 'time': '1m',
1380
+ }
1381
+ ],
1382
+ 'proxy_cache_use_stale': [
1383
+ 'error', 'timeout', 'invalid_header', 'updating',
1384
+ 'http_500', 'http_502', 'http_503', 'http_504', 'http_404'
1385
+ ],
1386
+ 'proxy_cache_lock': True,
1387
+ 'proxy_cache_lock_timeout': '5',
1388
+ },
1389
+ {
1390
+ 'urlpath': '/error',
1391
+ 'error_code': '502',
1392
+ }
1393
+ ]
1394
+ * limit_rate: '100'
1395
+ * limit_conn: '10'
1396
+ * ssl_crt: '/etc/nginx/ssl/server.crt'
1397
+ * ssl_key: '/etc/nginx/ssl/server.key'
1398
+ * rewrite_rules:
1399
+ [
1400
+ 'rewrite ^ https://www.example.com$document_uri permanent'
1401
+ ]
1402
+ """
1403
+ # check if any of listen - server_name pair already exists
1404
+ if conflict_check:
1405
+ for server_name in server_names:
1406
+ for listen in listens:
1407
+ ip = 'ip' in listen and listen['ip'] or ''
1408
+ if servername_exists(ip, listen['port'], server_name):
1409
+ return False
1410
+
1411
+ # start generate the config string
1412
+ servercfg = ['server { # %s' % GENBY]
1413
+
1414
+ if limit_rate: servercfg.append(' limit_rate %sk;' % limit_rate)
1415
+ if limit_conn:
1416
+ _context_http_init_limit_conn(version=version)
1417
+ servercfg.append(' limit_conn addr %s;' % limit_conn)
1418
+
1419
+ for listen in listens:
1420
+ flag_ds = 'default_server' in listen and listen['default_server'] and ' default_server' or ''
1421
+ flag_ssl = 'ssl' in listen and listen['ssl'] and ' ssl' or ''
1422
+ ip = 'ip' in listen and listen['ip'] or ''
1423
+ if is_valid_ipv6(ip) and not is_valid_ipv4(ip):
1424
+ ip = '[' + ip + ']'
1425
+ port = listen['port']
1426
+ if ip:
1427
+ servercfg.append(' listen %s:%s%s%s;' % (ip, port, flag_ds, flag_ssl))
1428
+ else:
1429
+ servercfg.append(' listen %s%s%s;' % (port, flag_ds, flag_ssl))
1430
+ # if set to default server, we should clear the default_server flag
1431
+ if flag_ds: _context_server_clear_default_server(ip, port)
1432
+
1433
+ servercfg.append(' server_name %s;' % (' '.join(server_names)))
1434
+
1435
+ if charset: servercfg.append(' charset %s;' % charset)
1436
+ servercfg.append('')
1437
+
1438
+ if ssl_crt and ssl_key:
1439
+ if not Path(ssl_crt).is_file() or not Path(ssl_key).is_file(): return False
1440
+ servercfg.append(' ssl_certificate %s;' % ssl_crt)
1441
+ servercfg.append(' ssl_certificate_key %s;' % ssl_key)
1442
+ servercfg.append(' ssl_session_timeout 5m;')
1443
+ servercfg.append(' ssl_protocols SSLv2 SSLv3 TLSv1;')
1444
+ servercfg.append(' ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;')
1445
+ servercfg.append(' ssl_prefer_server_ciphers on;')
1446
+ servercfg.append('')
1447
+
1448
+ if index:
1449
+ servercfg.append(' index %s;' % index)
1450
+ servercfg.append('')
1451
+
1452
+ upstreams = {}
1453
+ if locations:
1454
+ urlpaths = [] # use to detect if urlpath duplicates
1455
+ for location in locations:
1456
+ urlpath = location['urlpath'].strip()
1457
+ if urlpath in urlpaths: return False
1458
+ urlpaths.append(urlpath)
1459
+
1460
+ servercfg.append(' location %s {' % urlpath)
1461
+ if 'root' in location:
1462
+ if urlpath.startswith('~'): # deal with old config
1463
+ servercfg.append(' root %s;' % location['root'])
1464
+ else:
1465
+ if urlpath == '/':
1466
+ servercfg.append(' root %s;' % location['root'])
1467
+ else:
1468
+ servercfg.append(' alias %s;' % location['root'])
1469
+
1470
+ if 'autoindex' in location:
1471
+ servercfg.append(' autoindex %s;' % (location['autoindex'] and 'on' or 'off'))
1472
+
1473
+ if 'fastcgi_pass' in location:
1474
+ if urlpath.startswith('~'): # deal with old config
1475
+ servercfg.append(' fastcgi_index index.php;')
1476
+ servercfg.append(r' fastcgi_split_path_info ^(.+\.php)(/?.+)$;')
1477
+ servercfg.append(' fastcgi_param PATH_INFO $fastcgi_path_info;')
1478
+ servercfg.append(' fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;')
1479
+ servercfg.append(' include fastcgi_params;')
1480
+ servercfg.append(' fastcgi_pass %s;' % location['fastcgi_pass'])
1481
+ else:
1482
+ if urlpath == '/':
1483
+ servercfg.append(r' location ~ ^/.+\.php {')
1484
+ servercfg.append(' fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;')
1485
+ else:
1486
+ servercfg.append(r' location ~ ^%s(/.+\.php) {' % urlpath)
1487
+ servercfg.append(' root %s;' % location['root'])
1488
+ servercfg.append(' set $fastcgi_script_alias $1;')
1489
+ servercfg.append(' fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_alias;')
1490
+
1491
+ servercfg.append(' fastcgi_index index.php;')
1492
+ servercfg.append(r' fastcgi_split_path_info ^(.+\.php)(/?.+)$;')
1493
+ servercfg.append(' fastcgi_param PATH_INFO $fastcgi_path_info;')
1494
+ servercfg.append(' fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;')
1495
+ servercfg.append(' include fastcgi_params;')
1496
+ servercfg.append(' fastcgi_pass %s;' % location['fastcgi_pass'])
1497
+ servercfg.append(' }')
1498
+
1499
+ if 'rewrite_rules' in location:
1500
+ rules = location['rewrite_rules']
1501
+ if 'rewrite_detect_file' in location and location['rewrite_detect_file']:
1502
+ servercfg.append(' if (!-e $request_filename)')
1503
+ servercfg.append(' {')
1504
+ for rule in rules:
1505
+ servercfg.append(' %s;' % rule)
1506
+ servercfg.append(' }')
1507
+ else:
1508
+ for rule in rules:
1509
+ servercfg.append(' %s;' % rule)
1510
+
1511
+ if 'redirect_url' in location:
1512
+ redirect_url = location['redirect_url']
1513
+ if redirect_url:
1514
+ redirect_type = 'redirect_type' in location and location['redirect_type'] or '302'
1515
+ redirect_option = 'redirect_option' in location and location['redirect_option'] or 'ignore'
1516
+ if urlpath == '/' or redirect_option == 'ignore':
1517
+ servercfg.append(' rewrite ^ %s%s %s;' % (redirect_url,
1518
+ redirect_option=='keep' and '$request_uri?' or '',
1519
+ redirect_type=='301' and 'permanent' or 'redirect'))
1520
+ else:
1521
+ servercfg.append(' if ($request_uri ~ ^%s(/.+)$) {' % urlpath)
1522
+ servercfg.append(' set $request_uri_alias $1;')
1523
+ servercfg.append(' rewrite ^ %s$request_uri_alias? %s;' % (redirect_url,
1524
+ redirect_type=='301' and 'permanent' or 'redirect'))
1525
+ servercfg.append(' }')
1526
+
1527
+ if 'proxy_backends' in location and location['proxy_backends']:
1528
+ if urlpath != '/':
1529
+ servercfg.append(' rewrite %s(.*) / break;' % urlpath)
1530
+ if 'proxy_charset' in location and location['proxy_charset']:
1531
+ servercfg.append(' charset %s;' % location['proxy_charset'])
1532
+ if 'proxy_host' in location and location['proxy_host']:
1533
+ servercfg.append(' proxy_set_header Host %s;' % location['proxy_host'])
1534
+ if 'proxy_realip' in location and location['proxy_realip']:
1535
+ servercfg.append(' proxy_set_header X-Real-IP $remote_addr;')
1536
+ proxy_protocol = 'proxy_protocol' in location and location['proxy_protocol'] or 'http'
1537
+ upstream_name = 'backend_of_%s_%s' % (server_names[0], urlpath.replace('/', '_'))
1538
+ servercfg.append(' proxy_pass %s://%s;' % (proxy_protocol, upstream_name))
1539
+ proxy_balance = 'proxy_balance' in location and location['proxy_balance'] or 'ip_hash'
1540
+ upstreams[upstream_name] = {
1541
+ 'balance': proxy_balance,
1542
+ 'keepalive': 'proxy_keepalive' in location and location['proxy_keepalive'] or None,
1543
+ 'backends': location['proxy_backends']
1544
+ }
1545
+
1546
+ if 'proxy_cache' in location and location['proxy_cache']:
1547
+ servercfg.append(' proxy_cache %s;' % location['proxy_cache'])
1548
+ if 'proxy_cache_min_uses' in location:
1549
+ servercfg.append(' proxy_cache_min_uses %s;' % location['proxy_cache_min_uses'])
1550
+ if 'proxy_cache_methods' in location:
1551
+ servercfg.append(' proxy_cache_methods %s;' % location['proxy_cache_methods'])
1552
+ if 'proxy_cache_key' in location:
1553
+ servercfg.append(' proxy_cache_key %s;' % location['proxy_cache_key'])
1554
+ if 'proxy_cache_valid' in location:
1555
+ for v in location['proxy_cache_valid']:
1556
+ servercfg.append(' proxy_cache_valid %s %s;' % (v['code'], v['time']))
1557
+ if 'proxy_cache_use_stale' in location:
1558
+ servercfg.append(' proxy_cache_use_stale %s;' % (' '.join(location['proxy_cache_use_stale'])))
1559
+ if 'proxy_cache_lock' in location and location['proxy_cache_lock']:
1560
+ servercfg.append(' proxy_cache_lock on;')
1561
+ if 'proxy_cache_lock_timeout' in location:
1562
+ servercfg.append(' proxy_cache_lock_timeout %ss;' % location['proxy_cache_lock_timeout'])
1563
+
1564
+ if 'error_code' in location: servercfg.append(' return %s;' % location['error_code'])
1565
+
1566
+ servercfg.append(' }')
1567
+ servercfg.append('')
1568
+
1569
+ if rewrite_rules:
1570
+ for rewrite_rule in rewrite_rules:
1571
+ if rewrite_rule: servercfg.append(' %s;' % rewrite_rule.strip())
1572
+
1573
+ # end of server context
1574
+ servercfg.append('}')
1575
+
1576
+ for upstream_name, upstream in upstreams.items():
1577
+ balance = upstream['balance']
1578
+ backends = upstream['backends']
1579
+
1580
+ servercfg.append('upstream %s { # %s' % (upstream_name, GENBY))
1581
+
1582
+ if len(backends) > 1: # have balance options
1583
+ if balance == 'ip_hash': servercfg.append(' ip_hash;')
1584
+ if balance == 'least_conn': servercfg.append(' least_conn;')
1585
+ if upstream['keepalive']: servercfg.append(' keepalive %s;' % upstream['keepalive'])
1586
+ for backend in backends:
1587
+ weight = fail_timeout = max_fails = ''
1588
+ if balance == 'weight' and 'weight' in backend and backend['weight']:
1589
+ weight = ' weight=%s' % backend['weight']
1590
+ if 'fail_timeout' in backend and backend['fail_timeout']:
1591
+ fail_timeout = ' fail_timeout=%ss' % backend['fail_timeout']
1592
+ if 'max_fails' in backend and backend['max_fails']:
1593
+ max_fails = ' max_fails=%s' % backend['max_fails']
1594
+ servercfg.append(' server %s%s%s%s;' % (backend['server'],
1595
+ weight, fail_timeout, max_fails))
1596
+ else:
1597
+ servercfg.append(' server %s;' % backends[0]['server'])
1598
+
1599
+ servercfg.append('}')
1600
+
1601
+ #print '\n'.join(servercfg)
1602
+ configfile = str(Path(SERVERCONF) / ('%s.conf' % server_names[0]))
1603
+ configfile_exists = Path(configfile).exists()
1604
+
1605
+ # check if need to add a new line at the end of the file to
1606
+ # avoid first line go to the same former } line
1607
+ if configfile_exists:
1608
+ with open(configfile, encoding='utf-8') as f:
1609
+ f.seek(-1, 2)
1610
+ if f.read(1) != '\n':
1611
+ servercfg.insert(0, '')
1612
+ with open(configfile, configfile_exists and 'a' or 'w', encoding='utf-8') as f:
1613
+ f.write('\n'.join(servercfg))
1614
+ return True
1615
+
1616
+ def updateserver(old_ip, old_port, old_server_name,
1617
+ server_names, listens, charset=None, index=None, locations=None,
1618
+ limit_rate=None, limit_conn=None, ssl_crt=None, ssl_key=None,
1619
+ rewrite_rules=None, version=None):
1620
+ """Update an existing server.
1621
+
1622
+ If the old config is not in the right place, we would automatically delete it and
1623
+ create the new config to coresponding config file under /etc/nginx/conf.d/.
1624
+ """
1625
+ # compare the old context and the new context
1626
+ # to check if the ip:port/server_name change and conflict status
1627
+ config = loadconfig(NGINXCONF, True)
1628
+ oldscontext = _context_getserver(old_ip, old_port, old_server_name, config)
1629
+ if not oldscontext:
1630
+ return False
1631
+ for server_name in server_names:
1632
+ for listen in listens:
1633
+ ip = 'ip' in listen and listen['ip'] or ''
1634
+ scontext = _context_getserver(ip, listen['port'], server_name)
1635
+ # server context found, but not equals to the old
1636
+ # this means conflict occur
1637
+ if scontext and scontext != oldscontext:
1638
+ return False
1639
+
1640
+ # init limit_conn_zone in http, this may cause http context change
1641
+ # reload config if change occur
1642
+ if limit_conn:
1643
+ if _context_http_init_limit_conn(version=version):
1644
+ config = loadconfig(NGINXCONF, True)
1645
+
1646
+ # disable the old server and relative upstreams
1647
+ if not _context_commentserver(old_ip, old_port, old_server_name, config=config):
1648
+ return False
1649
+ if not _context_commentupstreams(old_server_name, config=config):
1650
+ return False
1651
+
1652
+ # add the new server
1653
+ if not addserver(server_names, listens, charset=charset, index=index, locations=locations,
1654
+ limit_rate=limit_rate, limit_conn=limit_conn, ssl_crt=ssl_crt, ssl_key=ssl_key,
1655
+ rewrite_rules=rewrite_rules, conflict_check=False, version=version): return False
1656
+
1657
+ # only delete the disabled server
1658
+ _context_deleteupstreams(old_server_name, disabled=True)
1659
+ _context_deleteserver(old_ip, old_port, old_server_name, disabled=True)
1660
+
1661
+ return True
1662
+
1663
+
1664
+ if __name__ == '__main__':
1665
+ import pprint
1666
+ pp = pprint.PrettyPrinter(indent=4)
1667
+
1668
+ #_insert('/etc/nginx/nginx.conf', 31, ['gzip off'])
1669
+ #_replace([('/etc/nginx/nginx.conf', 31, 1)], ['gzip on', 'gzip off'])
1670
+ #http_set('gzip', 'off')
1671
+ #http_set('limit_conn_zone', '$binary_remote_addr zone=addr:10m')
1672
+ #print http_get('limit_conn_zone')
1673
+ #print http_getfirst('limit_conn_zone')
1674
+
1675
+ #pp.pprint(loadconfig(NGINXCONF, True))
1676
+ #pp.pprint(getservers())
1677
+ #pp.pprint(_context_getserver('0.0.0.0', '80', 'test.local'))
1678
+ #pp.pprint(disableserver('0.0.0.0', '80', 'test.local'))
1679
+ #pp.pprint(enableserver('0.0.0.0', '80', 'test.local'))
1680
+ #pp.pprint(deleteserver('0.0.0.0', '80', 'youmeiyoua.com'))
1681
+
1682
+ #print servername_exists('0.0.0.0', '80', 'www.paomi.local')
1683
+
1684
+ #print _context_server_clear_default_server('0.0.0.0', '8000')
1685
+
1686
+ #pp.pprint(deleteserver('0.0.0.0', '8000', 'test.db.local'))
1687
+ #pp.pprint(_context_getupstreams('test.db.local', disabled=True))
1688
+ #pp.pprint(_context_deleteupstreams('test.db.local'))
1689
+ #pp.pprint(_context_uncommentupstreams('test.db.local'))
1690
+ #pp.pprint(_context_getupstreams('test.db.local'))
1691
+ #pp.pprint(getserver('*', '80', 'www.baidu.com'))
1692
+ #print _context_getserver('*', '80', '192.168.0.13')
1693
+
1694
+ if 0:
1695
+ a =addserver(
1696
+ ['example.com', 'test.db.local'],
1697
+ [{'port': '8000'}, {'port': '88'}],
1698
+ charset='utf-8',
1699
+ index='index.html index.htm index.php',
1700
+ locations=None,
1701
+ limit_rate='100k',
1702
+ limit_conn='10',
1703
+ rewrite_rules=['rewrite ^ https://www.example.com$document_uri permanent'])
1704
+ print(a)
1705
+ if 0:
1706
+ b = updateserver(
1707
+ '', '8000', 'test.db.local',
1708
+ ['test.db.local'],
1709
+ [{'port': '8000', 'default_server': True}],
1710
+ charset='utf-8',
1711
+ index='index.html index.htm index.php',
1712
+ locations=[
1713
+ {
1714
+ 'urlpath': '/',
1715
+ 'root': '/var/www/example',
1716
+ 'fastcgi_pass': '127.0.0.1:9000',
1717
+ }, {
1718
+ 'urlpath': '/phpinfo',
1719
+ 'root': '/var/www/phpinfo',
1720
+ 'fastcgi_pass': '127.0.0.1:9000',
1721
+ }, {
1722
+ 'urlpath': '/redirect',
1723
+ 'redirect_url': 'http://www.baidu.com',
1724
+ 'redirect_type': '301',
1725
+ 'redirect_option': 'keep',
1726
+ }, {
1727
+ 'urlpath': '/proxy',
1728
+ 'proxy_balance': 'weight',
1729
+ 'proxy_protocol': 'http',
1730
+ 'proxy_host': 'www.baidu.com',
1731
+ 'proxy_realip': True,
1732
+ 'proxy_backends': [
1733
+ {'server':'www.citydog.me'},
1734
+ {'server':'www.baidu.com'},
1735
+ ],
1736
+ },{
1737
+ 'urlpath': '/error',
1738
+ 'error_code': '502',
1739
+ }
1740
+ ],
1741
+ limit_rate='100',
1742
+ limit_conn='10')
1743
+ print(b)
1744
+
1745
+
1746
+ def web_handler(context):
1747
+ action = context.get_argument('action', '')
1748
+
1749
+ if action == 'getservers':
1750
+ sites = getservers()
1751
+ context.write({'code': 0, 'msg': '', 'data': sites})
1752
+
1753
+ elif action in ('enableserver', 'disableserver', 'deleteserver'):
1754
+ ip = context.get_argument('ip', '')
1755
+ port = context.get_argument('port', '')
1756
+ name = context.get_argument('server_name', '')
1757
+ handler = getattr(sys.modules[__name__], action)
1758
+ opstr = {
1759
+ 'enableserver': '启用',
1760
+ 'disableserver': '停用',
1761
+ 'deleteserver': '删除',
1762
+ }
1763
+ if handler(ip, port, name):
1764
+ context.write({'code': 0, 'msg': '站点 %s:%s %s成功!' % (name, port, opstr[action])})
1765
+ else:
1766
+ context.write({'code': -1, 'msg': '站点 %s:%s %s失败!' % (name, port, opstr[action])})
1767
+
1768
+ elif action == 'gethttpsettings':
1769
+ items = context.get_argument('items', '')
1770
+ items = items.split(',')
1771
+
1772
+ if 'limit_conn_zone' in items:
1773
+ items.append('limit_zone')
1774
+
1775
+ data = {}
1776
+ config = loadconfig()
1777
+ for item in items:
1778
+ if item.endswith('[]'):
1779
+ item = item[:-2]
1780
+ returnlist = True
1781
+ values = http_get(item, config)
1782
+ else:
1783
+ returnlist = False
1784
+ values = [http_getfirst(item, config)]
1785
+
1786
+ if values:
1787
+ if item == 'gzip':
1788
+ values = [v == 'on' for v in values if v]
1789
+ elif item == 'limit_rate':
1790
+ values = [v.replace('k', '') for v in values if v]
1791
+ elif item == 'limit_conn':
1792
+ values = [v.split()[-1] for v in values if v]
1793
+ elif item == 'limit_conn_zone':
1794
+ values = [v.split(':')[-1].replace('m', '') for v in values if v]
1795
+ elif item == 'limit_zone':
1796
+ values = [v.split()[-1].replace('m', '') for v in values if v]
1797
+ elif item == 'client_max_body_size':
1798
+ values = [v.replace('m', '') for v in values if v]
1799
+ elif item == 'keepalive_timeout':
1800
+ values = [v.replace('s', '') for v in values if v]
1801
+ elif item == 'allow':
1802
+ values = [v for v in values if v and v != 'all']
1803
+ elif item == 'deny':
1804
+ values = [v for v in values if v and v != 'all']
1805
+ elif item == 'proxy_cache_path':
1806
+ result = []
1807
+ for v in values:
1808
+ info = {}
1809
+ fields = v.split()
1810
+ info['path'] = fields[0]
1811
+ for field in fields[1:]:
1812
+ key, value = field.split('=', 1)
1813
+ if key == 'levels':
1814
+ levels = value.split(':')
1815
+ info['path_level_1'] = levels[0]
1816
+ if len(levels) > 1:
1817
+ info['path_level_2'] = levels[1]
1818
+ if len(levels) > 2:
1819
+ info['path_level_3'] = levels[2]
1820
+ elif key == 'keys_zone':
1821
+ t = value.split(':')
1822
+ info['name'] = t[0]
1823
+ if len(t) > 1:
1824
+ info['mem'] = t[1].replace('m', '')
1825
+ elif key == 'inactive':
1826
+ info['inactive'] = value[:-1]
1827
+ info['inactive_unit'] = value[-1]
1828
+ elif key == 'max_size':
1829
+ info['max_size'] = value[:-1]
1830
+ info['max_size_unit'] = value[-1]
1831
+ result.append(info)
1832
+ values = result
1833
+
1834
+ if item == 'limit_zone':
1835
+ item = 'limit_conn_zone'
1836
+
1837
+ if returnlist:
1838
+ data[item] = values
1839
+ else:
1840
+ data[item] = values and values[0] or ''
1841
+ context.write({'code': 0, 'msg': '', 'data': data})
1842
+
1843
+ elif action == 'sethttpsettings':
1844
+ version = context.get_argument('version', '')
1845
+ gzip = context.get_argument('gzip', '')
1846
+ limit_rate = context.get_argument('limit_rate', '')
1847
+ limit_conn = context.get_argument('limit_conn', '')
1848
+ limit_conn_zone = context.get_argument('limit_conn_zone', '')
1849
+ client_max_body_size = context.get_argument('client_max_body_size', '')
1850
+ keepalive_timeout = context.get_argument('keepalive_timeout', '')
1851
+ allow = context.get_argument('allow', '')
1852
+ deny = context.get_argument('deny', '')
1853
+ access_status = context.get_argument('access_status', '')
1854
+
1855
+ setting = {}
1856
+ setting['gzip'] = gzip == 'on' and 'on' or 'off'
1857
+ if not limit_rate.isdigit():
1858
+ limit_rate = ''
1859
+ setting['limit_rate'] = limit_rate and '%sk' % limit_rate or ''
1860
+ if not limit_conn.isdigit():
1861
+ limit_conn = ''
1862
+ setting['limit_conn'] = limit_conn and 'addr %s' % limit_conn or ''
1863
+ if not limit_conn_zone.isdigit():
1864
+ limit_conn_zone = '10'
1865
+ if not version or version_get(version, '1.1.8'):
1866
+ setting['limit_conn_zone'] = '$binary_remote_addr zone=addr:%sm' % limit_conn_zone
1867
+ setting['limit_zone'] = ''
1868
+ else:
1869
+ setting['limit_zone'] = 'addr $binary_remote_addr %sm' % limit_conn_zone
1870
+ setting['limit_conn_zone'] = ''
1871
+ if not client_max_body_size.isdigit():
1872
+ client_max_body_size = '1'
1873
+ setting['client_max_body_size'] = '%sm' % client_max_body_size
1874
+ if not keepalive_timeout.isdigit():
1875
+ keepalive_timeout = ''
1876
+ setting['keepalive_timeout'] = keepalive_timeout and '%ss' % keepalive_timeout or ''
1877
+ if access_status == 'white':
1878
+ setting['allow'] = [a.strip() for a in allow.split() if a.strip()]
1879
+ setting['deny'] = 'all'
1880
+ elif access_status == 'black':
1881
+ setting['deny'] = [a.strip() for a in deny.split() if a.strip()]
1882
+ setting['allow'] = ''
1883
+ else:
1884
+ setting['allow'] = setting['deny'] = ''
1885
+
1886
+ directives = ('gzip', 'limit_rate', 'limit_conn', 'limit_conn_zone', 'limit_zone',
1887
+ 'client_max_body_size', 'keepalive_timeout', 'allow', 'deny')
1888
+ for directive in directives:
1889
+ if directive not in setting:
1890
+ continue
1891
+ value = setting[directive]
1892
+ if isinstance(value, str):
1893
+ value = _u(value)
1894
+ elif isinstance(value, list):
1895
+ for i, v in enumerate(value):
1896
+ value[i] = v
1897
+ http_set(directive, value)
1898
+
1899
+ context.write({'code': 0, 'msg': '设置保存成功!'})
1900
+
1901
+ elif action == 'setproxycachesettings':
1902
+ proxy_caches = tornado.escape.json_decode(context.get_argument('proxy_caches', ''))
1903
+
1904
+ values = []
1905
+ for cache in proxy_caches:
1906
+ fields = []
1907
+ if 'path' in cache and cache['path']:
1908
+ if not Path(cache['path']).exists() and 'autocreate' in cache and cache['autocreate']:
1909
+ try:
1910
+ Path(cache['path']).mkdir(parents=True, exist_ok=True)
1911
+ except:
1912
+ context.write({'code': -1, 'msg': '缓存目录 %s 创建失败!' % cache['path']})
1913
+ return
1914
+ else:
1915
+ context.write({'code': -1, 'msg': '请选择缓存目录!'})
1916
+ return
1917
+ fields.append(cache['path'])
1918
+ if not 'path_level_1' in cache or not cache['path_level_1'].isdigit() or \
1919
+ not 'path_level_2' in cache or not cache['path_level_2'].isdigit() or \
1920
+ not 'path_level_3' in cache or not cache['path_level_3'].isdigit():
1921
+ context.write({'code': -1, 'msg': '缓存目录名长度必须是数字!'})
1922
+ return
1923
+ if int(cache['path_level_1']) + int(cache['path_level_2']) + int(cache['path_level_3']) > 32:
1924
+ context.write({'code': -1, 'msg': '缓存目录名长度总和不能超过32位!'})
1925
+ return
1926
+ levels = [cache['path_level_1']]
1927
+ if int(cache['path_level_2']) > 0:
1928
+ levels.append(cache['path_level_2'])
1929
+ if int(cache['path_level_3']) > 0:
1930
+ levels.append(cache['path_level_3'])
1931
+ fields.append('levels=%s' % (':'.join(levels)))
1932
+
1933
+ if not 'name' in cache or cache['name'].strip() == '':
1934
+ context.write({'code': -1, 'msg': '缓存区名称不能为空!'})
1935
+ return
1936
+ if not 'mem' in cache or not cache['mem'].isdigit():
1937
+ context.write({'code': -1, 'msg': '缓存计数内存大小必须是数字!'})
1938
+ return
1939
+ fields.append('keys_zone=%s:%sm' % (cache['name'].strip(), cache['mem']))
1940
+
1941
+ if not 'inactive' in cache or not cache['inactive'].isdigit():
1942
+ context.write({'code': -1, 'msg': '缓存过期时间必须是数字!'})
1943
+ return
1944
+ if not 'inactive_unit' in cache or not cache['inactive_unit'] in ('s', 'm', 'h', 'd'):
1945
+ context.write({'code': -1, 'msg': '缓存过期时间单位错误!'})
1946
+ return
1947
+ fields.append('inactive=%s%s' % (cache['inactive'], cache['inactive_unit']))
1948
+
1949
+ if not 'max_size' in cache or not cache['max_size'].isdigit():
1950
+ context.write({'code': -1, 'msg': '缓存大小限制值必须是数字!'})
1951
+ return
1952
+ if not 'max_size_unit' in cache or not cache['max_size_unit'] in ('m', 'g'):
1953
+ context.write({'code': -1, 'msg': '缓存大小限制单位错误!'})
1954
+ return
1955
+ fields.append('max_size=%s%s' % (cache['max_size'], cache['max_size_unit']))
1956
+
1957
+ values.append(' '.join(fields))
1958
+
1959
+ http_set('proxy_cache_path', values)
1960
+ context.write({'code': 0, 'msg': '设置保存成功!'})
1961
+
1962
+ elif action == 'getserver':
1963
+ ip = context.get_argument('ip', '')
1964
+ port = context.get_argument('port', '')
1965
+ server_name = context.get_argument('server_name', '')
1966
+ serverinfo = getserver(ip, port, server_name)
1967
+ if serverinfo:
1968
+ context.write({'code': 0, 'msg': '站点信息读取成功!', 'data': serverinfo})
1969
+ else:
1970
+ context.write({'code': -1, 'msg': '站点不存在!'})
1971
+
1972
+ elif action in ('addserver', 'updateserver'):
1973
+ if action == 'updateserver':
1974
+ old_server_ip = context.get_argument('ip', '')
1975
+ old_server_port = context.get_argument('port', '')
1976
+ old_server_name = context.get_argument('server_name', '')
1977
+
1978
+ version = context.get_argument('version', '')
1979
+ setting = tornado.escape.json_decode(context.get_argument('setting', ''))
1980
+
1981
+ server_names = None
1982
+ if 'server_names' in setting:
1983
+ server_names = [s['name'].strip().lower() for s in setting['server_names'] if s['name'].strip()]
1984
+ for server_name in server_names:
1985
+ if server_name != '_' and not is_valid_domain(server_name):
1986
+ server_names = None
1987
+ break
1988
+ if not server_names:
1989
+ context.write({'code': -1, 'msg': '请输入有效的站点域名!'})
1990
+ return
1991
+
1992
+ listens = None
1993
+ if 'listens' in setting:
1994
+ listens = setting['listens']
1995
+ ipportpairs = []
1996
+ for listen in listens:
1997
+ if 'ip' in listen:
1998
+ if listen['ip'] not in ('', '*', '0.0.0.0') and not is_valid_ip(listen['ip']):
1999
+ listens = None
2000
+ break
2001
+ if not 'port' in listen:
2002
+ listens = None
2003
+ break
2004
+ elif not listen['port'].isdigit():
2005
+ listens = None
2006
+ break
2007
+ else:
2008
+ port = int(listen['port'])
2009
+ if port <= 0 or port > 65535:
2010
+ listens = None
2011
+ break
2012
+ ipport = '%s:%s' % (listen['ip'], listen['port'])
2013
+ if ipport in ipportpairs:
2014
+ context.write({'code': -1, 'msg': '监听的IP:端口重复!'})
2015
+ return
2016
+ if listen['ip'] in ('', '*', '0.0.0.0'):
2017
+ ipportpairs.append(ipport)
2018
+ if not listens:
2019
+ context.write({'code': -1, 'msg': '请输入有效的监听地址!'})
2020
+ return
2021
+
2022
+ charset = None
2023
+ charsets = ('', 'utf-8', 'gb2312', 'gbk', 'gb18030',
2024
+ 'big5', 'euc-jp', 'euc-kr', 'iso-8859-2', 'shift_jis')
2025
+ if 'charset' in setting:
2026
+ charset = setting['charset']
2027
+ if charset not in charsets:
2028
+ context.write({'code': -1, 'msg': '请选择有效的字符编码!'})
2029
+ return
2030
+
2031
+ index = None
2032
+ if 'index' in setting:
2033
+ index = setting['index']
2034
+
2035
+ limit_rate = None
2036
+ if 'limit_rate' in setting:
2037
+ limit_rate = setting['limit_rate']
2038
+ if limit_rate and not limit_rate.isdigit():
2039
+ context.write({'code': -1, 'msg': '下载速度限制必须为数字!'})
2040
+ return
2041
+
2042
+ limit_conn = None
2043
+ if 'limit_conn' in setting:
2044
+ limit_conn = setting['limit_conn']
2045
+ if limit_conn and not limit_conn.isdigit():
2046
+ context.write({'code': -1, 'msg': '连接数限制必须为数字!'})
2047
+ return
2048
+
2049
+ ssl_crt = ssl_key = None
2050
+ if 'ssl_crt' in setting and 'ssl_key' in setting:
2051
+ if setting['ssl_crt'] or setting['ssl_key']:
2052
+ ssl_crt = setting['ssl_crt']
2053
+ ssl_key = setting['ssl_key']
2054
+ if not Path(ssl_crt).is_file() or not Path(ssl_key).is_file():
2055
+ context.write({'code': -1, 'msg': 'SSL证书或密钥不存在!'})
2056
+ return
2057
+
2058
+ rewrite_rules = None
2059
+ if 'rewrite_enable' in setting and setting['rewrite_enable']:
2060
+ if 'rewrite_rules' in setting:
2061
+ rules = setting['rewrite_rules'].split('\n')
2062
+ rewrite_rules = []
2063
+ for rule in rules:
2064
+ rule = rule.strip().strip(';')
2065
+ if rule == '':
2066
+ continue
2067
+ t = re.split(r'\s+', rule)
2068
+ if len(t) not in (3, 4) or \
2069
+ len(t) == 4 and (t[0] != 'rewrite' or t[-1] not in ('last', 'break', 'redirect', 'permanent')) or \
2070
+ len(t) == 3 and t[0] != 'rewrite':
2071
+ context.write({'code': -1, 'msg': 'Rewrite 规则 "%s" 格式有误!' % rule})
2072
+ return
2073
+ rewrite_rules.append(rule)
2074
+
2075
+ locations = []
2076
+ urlpaths = []
2077
+ if 'locations' in setting:
2078
+ locs = setting['locations']
2079
+ for loc in locs:
2080
+ if not 'urlpath' in loc:
2081
+ context.write({'code': -1, 'msg': '站点URL路径输入错误!'})
2082
+ return
2083
+ if not 'engine' in loc \
2084
+ or loc['engine'] not in ('static', 'fastcgi', 'redirect', 'proxy', 'error'):
2085
+ context.write({'code': -1, 'msg': '站点路径引擎选择存在错误!'})
2086
+ return
2087
+ if not loc['engine'] in loc:
2088
+ context.write({'code': -1, 'msg': '缺少站点路径配置!'})
2089
+ return
2090
+ location = {}
2091
+ location['urlpath'] = loc['urlpath']
2092
+ if loc['urlpath'] in urlpaths:
2093
+ context.write({'code': -1, 'msg': '重复的站点路径 %s!' % loc['urlpath']})
2094
+ return
2095
+ urlpaths.append(loc['urlpath'])
2096
+ locsetting = loc[loc['engine']]
2097
+ if loc['engine'] in ('static', 'fastcgi'):
2098
+ if not 'root' in locsetting:
2099
+ context.write({'code': -1, 'msg': '站点目录不能为空!'})
2100
+ return
2101
+ if not Path(locsetting['root']).exists():
2102
+ if 'autocreate' in locsetting and locsetting['autocreate']:
2103
+ try:
2104
+ Path(locsetting['root']).mkdir(parents=True, exist_ok=True)
2105
+ except:
2106
+ context.write({'code': -1, 'msg': '站点目录 %s 创建失败!' % locsetting['root']})
2107
+ return
2108
+ else:
2109
+ context.write({'code': -1, 'msg': '站点目录 %s 不存在!' % locsetting['root']})
2110
+ return
2111
+ location['root'] = locsetting['root']
2112
+ if 'charset' in locsetting and locsetting['charset'] in charsets:
2113
+ location['charset'] = locsetting['charset']
2114
+ if 'index' in locsetting:
2115
+ location['index'] = locsetting['index']
2116
+ if 'rewrite_enable' in locsetting and locsetting['rewrite_enable']:
2117
+ if 'rewrite_detect_file' in locsetting and locsetting['rewrite_detect_file']:
2118
+ location['rewrite_detect_file'] = True
2119
+ else:
2120
+ location['rewrite_detect_file'] = False
2121
+ location['rewrite_rules'] = []
2122
+ rwrules = locsetting['rewrite_rules'].split('\n')
2123
+ for rule in rwrules:
2124
+ rule = rule.strip().strip(';')
2125
+ if rule == '':
2126
+ continue
2127
+ t = re.split(r'\s+', rule)
2128
+ if len(t) not in (3, 4) or \
2129
+ len(t) == 4 and (t[0] != 'rewrite' or t[-1] not in ('last', 'break')) or \
2130
+ len(t) == 3 and t[0] != 'rewrite':
2131
+ context.write({'code': -1, 'msg': 'Rewrite 规则 "%s" 格式有误!' % rule})
2132
+ return
2133
+ location['rewrite_rules'].append(rule)
2134
+ if loc['engine'] == 'static':
2135
+ if 'autoindex' in locsetting and locsetting['autoindex']:
2136
+ location['autoindex'] = True
2137
+ elif loc['engine'] == 'fastcgi':
2138
+ if not 'fastcgi_pass' in locsetting or not locsetting['fastcgi_pass']:
2139
+ context.write({'code': -1, 'msg': '请输入FastCGI服务器地址!'})
2140
+ return
2141
+ fastcgi_pass = locsetting['fastcgi_pass']
2142
+ if not fastcgi_pass.startswith('unix:'):
2143
+ fields = fastcgi_pass.split(':', 1)
2144
+ if len(fields) > 1:
2145
+ server, port = fields
2146
+ else:
2147
+ server = fields[0]
2148
+ port = None
2149
+ if not is_valid_domain(server) or port and not port.isdigit():
2150
+ context.write({'code': -1, 'msg': 'FastCGI服务器地址 %s 输入有误!' % fastcgi_pass})
2151
+ return
2152
+ location['fastcgi_pass'] = fastcgi_pass
2153
+ elif loc['engine'] == 'redirect':
2154
+ if not 'url' in locsetting or not locsetting['url']:
2155
+ context.write({'code': -1, 'msg': '请输入要跳转到的 URL 地址!'})
2156
+ return
2157
+ if not is_url(locsetting['url']):
2158
+ context.write({'code': -1, 'msg': '跳转到的 URL 地址"%s"格式有误,请检查是否添加了 http:// 或 https:// 等!' % locsetting['url']})
2159
+ return
2160
+ location['redirect_url'] = locsetting['url']
2161
+ if 'type' in locsetting and locsetting['type'] in ('301', '302'):
2162
+ location['redirect_type'] = locsetting['type']
2163
+ if 'option' in locsetting and locsetting['option'] in ('keep', 'ignore'):
2164
+ location['redirect_option'] = locsetting['option']
2165
+ elif loc['engine'] == 'proxy':
2166
+ if not 'backends' in locsetting or not locsetting['backends']:
2167
+ context.write({'code': -1, 'msg': '反向代理后端不能为空!'})
2168
+ return
2169
+ if not 'protocol' in locsetting or not locsetting['protocol'] in ('http', 'https'):
2170
+ context.write({'code': -1, 'msg': '后端协议选择有误!'})
2171
+ return
2172
+ location['proxy_protocol'] = locsetting['protocol']
2173
+ if 'host' in locsetting and is_valid_domain(locsetting['host']):
2174
+ location['proxy_host'] = locsetting['host']
2175
+ if 'realip' in locsetting:
2176
+ location['proxy_realip'] = locsetting['realip'] and True or False
2177
+
2178
+ backends = [backend for backend in locsetting['backends']
2179
+ if 'server' in backend and backend['server'].strip()]
2180
+ if 'charset' in locsetting:
2181
+ if not locsetting['charset'] in charsets:
2182
+ context.write({'code': -1, 'msg': '请选择有效的字符编码!'})
2183
+ return
2184
+ if locsetting['charset']:
2185
+ location['proxy_charset'] = locsetting['charset']
2186
+ if len(backends) == 0:
2187
+ context.write({'code': -1, 'msg': '反向代理后端不能为空!'})
2188
+ return
2189
+ elif len(backends) > 1:
2190
+ if not 'balance' in locsetting or not locsetting['balance'] in ('weight', 'ip_hash', 'least_conn'):
2191
+ context.write({'code': -1, 'msg': '请设置负载均衡策略!'})
2192
+ return
2193
+ location['proxy_balance'] = locsetting['balance']
2194
+ if 'keepalive' in locsetting:
2195
+ if locsetting['keepalive'] and not locsetting['keepalive'].isdigit():
2196
+ context.write({'code': -1, 'msg': '后端保持连接数必须是数字!'})
2197
+ return
2198
+ if locsetting['keepalive']:
2199
+ location['proxy_keepalive'] = locsetting['keepalive']
2200
+
2201
+ location['proxy_backends'] = []
2202
+ for backend in backends:
2203
+ if not 'server' in backend:
2204
+ context.write({'code': -1, 'msg': '后端地址输入有误!'})
2205
+ return
2206
+ fields = backend['server'].split(':', 1)
2207
+ if len(fields) > 1:
2208
+ server, port = fields
2209
+ else:
2210
+ server = fields[0]
2211
+ port = None
2212
+ if not is_valid_domain(server) or port and not port.isdigit():
2213
+ context.write({'code': -1, 'msg': '后端地址 %s 输入有误!' % backend['server']})
2214
+ return
2215
+ proxy_backend = {'server': backend['server']}
2216
+ if len(backends) > 1:
2217
+ if location['proxy_balance'] in ('weight', 'ip_hash'):
2218
+ if location['proxy_balance'] == 'weight':
2219
+ if 'weight' in backend:
2220
+ if backend['weight'] and not backend['weight'].isdigit():
2221
+ context.write({'code': -1, 'msg': '后端权重值必须为数字!'})
2222
+ return
2223
+ if backend['weight']:
2224
+ proxy_backend['weight'] = backend['weight']
2225
+ if 'fail_timeout' in backend and 'max_fails' in backend:
2226
+ if backend['fail_timeout'] and not backend['fail_timeout'].isdigit():
2227
+ context.write({'code': -1, 'msg': '后端失效检测超时必须为数字!'})
2228
+ return
2229
+ if backend['max_fails'] and not backend['max_fails'].isdigit():
2230
+ context.write({'code': -1, 'msg': '后端失效检测次数必须为数字!'})
2231
+ return
2232
+ if backend['fail_timeout']:
2233
+ proxy_backend['fail_timeout'] = backend['fail_timeout']
2234
+ if backend['max_fails']:
2235
+ proxy_backend['max_fails'] = backend['max_fails']
2236
+ location['proxy_backends'].append(proxy_backend)
2237
+
2238
+ if 'proxy_cache_enable' in locsetting and locsetting['proxy_cache_enable']:
2239
+ if not 'proxy_cache' in locsetting or locsetting['proxy_cache'] == '':
2240
+ context.write({'code': -1, 'msg': '请选择缓存区域!'})
2241
+ return
2242
+ location['proxy_cache'] = locsetting['proxy_cache']
2243
+ if 'proxy_cache_min_uses' in locsetting and locsetting['proxy_cache_min_uses'] != '':
2244
+ if not locsetting['proxy_cache_min_uses'].isdigit():
2245
+ context.write({'code': -1, 'msg': '缓存条件的次数必须为数字!'})
2246
+ return
2247
+ location['proxy_cache_min_uses'] = locsetting['proxy_cache_min_uses']
2248
+ if 'proxy_cache_methods_post' in locsetting and locsetting['proxy_cache_methods_post']:
2249
+ location['proxy_cache_methods'] = 'POST'
2250
+ if 'proxy_cache_key' in locsetting:
2251
+ t = []
2252
+ ck = locsetting['proxy_cache_key']
2253
+ if 'schema' in ck and ck['schema']:
2254
+ t.append('$scheme')
2255
+ if 'host' in ck and ck['host']:
2256
+ t.append('$host')
2257
+ if 'proxy_host' in ck and ck['proxy_host']:
2258
+ t.append('$proxy_host')
2259
+ if 'uri' in ck and ck['uri']:
2260
+ t.append('$request_uri')
2261
+ if len(t) > 0:
2262
+ location['proxy_cache_key'] = ''.join(t)
2263
+ if 'proxy_cache_valid' in locsetting:
2264
+ t = []
2265
+ cvs = locsetting['proxy_cache_valid']
2266
+ for cv in cvs:
2267
+ if not 'code' in cv or not 'time' in cv or not 'time_unit' in cv:
2268
+ continue
2269
+ if cv['code'] not in ('200', '301', '302', '404', '500', '502', '503', '504', 'any'):
2270
+ context.write({'code': -1, 'msg': '缓存过期规则的状态码有误!'})
2271
+ return
2272
+ if not cv['time'].isdigit():
2273
+ context.write({'code': -1, 'msg': '缓存过期规则的过期时间必须为数字!'})
2274
+ return
2275
+ if not cv['time_unit'] in ('s', 'm', 'h', 'd'):
2276
+ context.write({'code': -1, 'msg': '缓存过期规则的过期时间单位有误!'})
2277
+ return
2278
+ t.append({'code': cv['code'], 'time': '%s%s' % (cv['time'], cv['time_unit'])})
2279
+ if len(t) > 0:
2280
+ location['proxy_cache_valid'] = t
2281
+ if 'proxy_cache_use_stale' in locsetting:
2282
+ t = []
2283
+ cus = locsetting['proxy_cache_use_stale']
2284
+ for k, v in cus.items():
2285
+ if not k in ('error', 'timeout', 'invalid_header', 'updating',
2286
+ 'http_500', 'http_502', 'http_503', 'http_504', 'http_404') or not v:
2287
+ continue
2288
+ t.append(k)
2289
+ if len(t) > 0:
2290
+ location['proxy_cache_use_stale'] = t
2291
+ if 'proxy_cache_lock' in locsetting and locsetting['proxy_cache_lock']:
2292
+ location['proxy_cache_lock'] = True
2293
+ if 'proxy_cache_lock_timeout' in locsetting:
2294
+ if not locsetting['proxy_cache_lock_timeout'].isdigit():
2295
+ context.write({'code': -1, 'msg': '缓存锁定时间必须为数字!'})
2296
+ return
2297
+ location['proxy_cache_lock_timeout'] = locsetting['proxy_cache_lock_timeout']
2298
+
2299
+ elif loc['engine'] == 'error':
2300
+ if not 'code' in locsetting or not locsetting['code']:
2301
+ context.write({'code': -1, 'msg': '请选择错误代码!'})
2302
+ return
2303
+ if locsetting['code'] not in ('401', '403', '404', '500', '502'):
2304
+ context.write({'code': -1, 'msg': '错误代码选择有误!'})
2305
+ return
2306
+ location['error_code'] = locsetting['code']
2307
+ locations.append(location)
2308
+
2309
+ if action == 'addserver':
2310
+ if not addserver(server_names, listens,
2311
+ charset=charset, index=index, locations=locations,
2312
+ limit_rate=limit_rate, limit_conn=limit_conn,
2313
+ ssl_crt=ssl_crt, ssl_key=ssl_key,
2314
+ rewrite_rules=rewrite_rules, version=version):
2315
+ context.write({'code': -1, 'msg': '新站点添加失败!请检查站点域名是否重复。'})
2316
+ else:
2317
+ context.write({'code': 0, 'msg': '新站点添加成功!'})
2318
+ else:
2319
+ if not updateserver(old_server_ip, old_server_port, old_server_name,
2320
+ server_names, listens,
2321
+ charset=charset, index=index, locations=locations,
2322
+ limit_rate=limit_rate, limit_conn=limit_conn,
2323
+ ssl_crt=ssl_crt, ssl_key=ssl_key,
2324
+ rewrite_rules=rewrite_rules, version=version):
2325
+ context.write({'code': -1, 'msg': '站点设置更新失败!请检查配置信息(如域名是否重复?)'})
2326
+ else:
2327
+ context.write({'code': 0, 'msg': '站点设置更新成功!'})