├── .gitattributes ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE.txt ├── install.py ├── nginx.conf ├── readme.md ├── readme_zh.md ├── test ├── base_case.py ├── requirements.txt ├── test.py └── testcase │ ├── __init__.py │ ├── a_1_start_without_config │ ├── __init__.py │ └── case.py │ ├── a_2_basic_behaviour │ ├── __init__.py │ ├── case.py │ └── config.json │ ├── a_3_matcher_host │ ├── __init__.py │ ├── case.py │ └── config.json │ ├── a_4_matcher_useragent │ ├── __init__.py │ ├── case.py │ └── config.json │ └── a_5_matcher_refer │ ├── __init__.py │ ├── case.py │ └── config.json └── verynginx ├── configs └── .gitignore ├── dashboard ├── css │ ├── bs_callout.css │ ├── pace.css │ ├── tables.css │ └── webInterface.css ├── index.html ├── index_zh.html └── js │ ├── config.js │ ├── dashboard.js │ ├── data_stat.js │ ├── matcher_editor.js │ ├── monitor.js │ ├── pace.min.js │ ├── tips.js │ ├── upstream_editor.js │ ├── util.js │ ├── verify.js │ └── vnform.js ├── lua_script ├── VeryNginxConfig.lua ├── module │ ├── backend_proxy.lua │ ├── backend_static.lua │ ├── browser_verify.lua │ ├── cookie.lua │ ├── dkjson.lua │ ├── encrypt_seed.lua │ ├── filter.lua │ ├── frequency_limit.lua │ ├── json.lua │ ├── redirect.lua │ ├── request_tester.lua │ ├── router.lua │ ├── scheme_lock.lua │ ├── status.lua │ ├── summary.lua │ ├── uri_rewrite.lua │ └── util.lua ├── on_access.lua ├── on_banlance.lua ├── on_init.lua ├── on_log.lua ├── on_rewrite.lua └── resty │ └── dns │ └── resolver.lua ├── nginx_conf ├── in_external.conf ├── in_http_block.conf └── in_server_block.conf └── support └── verify_javascript.html /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-vendored=true 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *.swn 4 | *.swm 5 | __pycache__ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | os: linux 5 | 6 | language: c 7 | 8 | compiler: 9 | - gcc 10 | 11 | addons: 12 | apt: 13 | packages: 14 | - python3 15 | - python-pip 16 | - python3-pip 17 | 18 | cache: 19 | apt: true 20 | directories: 21 | - $HOME/.cache/pip 22 | - /opt/verynginx 23 | 24 | before_install: 25 | - if [ ! -d /opt/verynginx/openresty ]; then python3 install.py install openresty ; fi 26 | - sudo rm -rf /opt/verynginx/verynginx 27 | - sudo adduser --system --no-create-home --group nginx 28 | 29 | install: 30 | - sudo python3 -m pip install -r test/requirements.txt 31 | - sudo python3 install.py install verynginx 32 | 33 | before_script: 34 | - if [ ! -f /opt/verynginx/openresty/nginx/logs/error.log ]; then touch /opt/verynginx/openresty/nginx/logs/error.log;sudo chown nginx:nginx /opt/verynginx/openresty/nginx/logs/error.log ; fi 35 | - sudo /opt/verynginx/openresty/nginx/sbin/nginx 36 | - sudo /opt/verynginx/openresty/nginx/sbin/nginx -s stop 37 | - echo -e "127.0.0.1 a.vntest.com\n127.0.0.1 b.vntest.com\n127.0.0.1 c.vntest.com" | sudo tee -a /etc/hosts 38 | 39 | script: 40 | - sudo python3 test/test.py 41 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | RUN yum clean all && yum update -y && yum install -y gcc pcre-devel openssl-devel wget perl make build-essential procps libreadline-dev libncurses5-dev libpcre3-dev libssl-dev 4 | 5 | RUN mkdir /code 6 | COPY ./ /code/ 7 | WORKDIR /code 8 | RUN groupadd -r nginx && useradd -r -g nginx nginx 9 | RUN python install.py install 10 | 11 | EXPOSE 80 12 | 13 | CMD ["/opt/verynginx/openresty/nginx/sbin/nginx", "-g", "daemon off; error_log /dev/stderr info;"] 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /install.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2016-04-04 23:48 4 | # @Author : Alexa (AlexaZhou@163.com) 5 | # @Link : https://github.com/alexazhou/VeryNginx 6 | # @Disc : install VeryNginx 7 | # @Disc : support python 2.x and 3.x 8 | 9 | import os 10 | import sys 11 | import getopt 12 | import filecmp 13 | 14 | #最新版openresty 15 | 16 | openresty_pkg_url = 'https://openresty.org/download/openresty-1.15.8.1.tar.gz' 17 | openresty_pkg = 'openresty-1.15.8.1.tar.gz' 18 | 19 | work_path = os.getcwd() 20 | 21 | def install_openresty( ): 22 | #check if the old version of VeryNginx installed( use upcase directory ) 23 | if os.path.exists('/opt/VeryNginx/VeryNginx') == True: 24 | print("Seems that a old version of VeryNginx was installed in /opt/verynginx/...\nBefore install, please delete it and backup the configs if you need.") 25 | sys.exit(1) 26 | 27 | #makesure the dir is clean 28 | print('### makesure the work directory is clean') 29 | exec_sys_cmd('rm -rf ' + openresty_pkg.replace('.tar.gz','')) 30 | 31 | #download openresty 32 | down_flag = True 33 | if os.path.exists( './' + openresty_pkg ): 34 | ans = '' 35 | while ans not in ['y','n']: 36 | ans = common_input(' Found %s in current directory, use it?(y/n)'%openresty_pkg) 37 | if ans == 'y': 38 | down_flag = False 39 | 40 | if down_flag == True: 41 | print('### start download openresty package...') 42 | exec_sys_cmd('rm -rf ' + openresty_pkg) 43 | exec_sys_cmd( 'wget ' + openresty_pkg_url ) 44 | else: 45 | print('### use local openresty package...') 46 | 47 | print('### release the package ...') 48 | exec_sys_cmd( 'tar -xzf ' + openresty_pkg ) 49 | 50 | #configure && compile && install openresty 51 | print('### configure openresty ...') 52 | os.chdir( openresty_pkg.replace('.tar.gz','') ) 53 | exec_sys_cmd( './configure --prefix=/opt/verynginx/openresty --user=nginx --group=nginx --with-http_v2_module --with-http_sub_module --with-http_stub_status_module --with-luajit' ) 54 | 55 | print('### compile openresty ...') 56 | exec_sys_cmd( 'make' ) 57 | 58 | print('### install openresty ...') 59 | exec_sys_cmd( 'make install' ) 60 | 61 | # print('### make nginx into PATH ...') 62 | # May Not Work in CI 63 | # exec_sys_cmd( 'export PATH=/opt/verynginx/openresty/nginx/sbin:$PATH' ) 64 | 65 | # print('### add user and group nginx:nginx') 66 | # May Not Work in CI 67 | # exec_sys_cmd( 'sudo groupadd -f nginx && useradd -g nginx nginx' ) 68 | 69 | def install_verynginx(): 70 | 71 | #install VeryNginx file 72 | print('### copy VeryNginx files ...') 73 | os.chdir( work_path ) 74 | if os.path.exists('/opt/verynginx/') == False: 75 | exec_sys_cmd( 'mkdir -p /opt/verynginx' ) 76 | 77 | exec_sys_cmd( 'cp -r -f ./verynginx /opt/verynginx' ) 78 | 79 | #copy nginx config file to openresty 80 | if os.path.exists('/opt/verynginx/openresty') == True: 81 | if filecmp.cmp( '/opt/verynginx/openresty/nginx/conf/nginx.conf', '/opt/verynginx/openresty/nginx/conf/nginx.conf.default', False ) == True: 82 | print('cp nginx config file to openresty') 83 | exec_sys_cmd( 'cp -f ./nginx.conf /opt/verynginx/openresty/nginx/conf/' ) 84 | else: 85 | print( 'openresty not fount, so not copy nginx.conf' ) 86 | 87 | # set mask for the path which used for save configs 88 | # file too open; 89 | # exec_sys_cmd( 'chmod -R 777 /opt/verynginx/verynginx/configs' ) 90 | exec_sys_cmd( 'chmod -R 755 /opt/verynginx/verynginx/configs' ) 91 | 92 | 93 | def update_verynginx(): 94 | install_verynginx() 95 | 96 | 97 | def exec_sys_cmd(cmd, accept_failed = False): 98 | print( cmd ) 99 | ret = os.system( cmd ) 100 | if ret == 0: 101 | return ret 102 | else: 103 | if accept_failed == False: 104 | print('*** The installing stopped because something was wrong') 105 | exit(1) 106 | else: 107 | return False 108 | 109 | def common_input( s ): 110 | if sys.version_info[0] == 3: 111 | return input( s ) 112 | else: 113 | return raw_input( s ) 114 | 115 | def safe_pop(l): 116 | if len(l) == 0: 117 | return None 118 | else: 119 | return l.pop(0) 120 | 121 | def show_help_and_exit(): 122 | help_doc = 'usage: install.py <cmd> <args> ... \n\n\ 123 | install cmds and args:\n\ 124 | install\n\ 125 | all : install verynginx and openresty(default)\n\ 126 | openresty : install openresty\n\ 127 | verynginx : install verynginx\n\ 128 | update\n\ 129 | verynginx : update the installed verynginx\n\ 130 | ' 131 | print(help_doc) 132 | exit() 133 | 134 | 135 | if __name__ == '__main__': 136 | 137 | opts, args = getopt.getopt(sys.argv[1:], '', []) 138 | 139 | cmd = safe_pop(args) 140 | if cmd == 'install': 141 | cmd = safe_pop(args) 142 | if cmd == 'all' or cmd == None: 143 | install_openresty() 144 | install_verynginx() 145 | elif cmd == 'openresty': 146 | install_openresty() 147 | elif cmd == 'verynginx': 148 | install_verynginx() 149 | else: 150 | show_help_and_exit() 151 | elif cmd == 'update': 152 | cmd = safe_pop(args) 153 | if cmd == 'verynginx': 154 | update_verynginx() 155 | else: 156 | show_help_and_exit() 157 | else: 158 | show_help_and_exit() 159 | 160 | print('*** All work finished successfully, enjoy it~') 161 | 162 | 163 | else: 164 | print ('install.py had been imported as a module') 165 | print ('please add group and user nginx:nginx') 166 | print ('to use nginx, add it in PATH: \nexport PATH=/opt/verynginx/openresty/nginx/sbin:$PATH') 167 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | 2 | user nginx; 3 | worker_processes auto; 4 | 5 | #error_log logs/error.log; 6 | #error_log logs/error.log notice; 7 | #error_log logs/error.log info; 8 | 9 | #pid logs/nginx.pid; 10 | 11 | 12 | events { 13 | worker_connections 1024; 14 | } 15 | 16 | include /opt/verynginx/verynginx/nginx_conf/in_external.conf; 17 | 18 | http { 19 | include mime.types; 20 | default_type application/octet-stream; 21 | 22 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 23 | # '$status $body_bytes_sent "$http_referer" ' 24 | # '"$http_user_agent" "$http_x_forwarded_for"'; 25 | 26 | #access_log logs/access.log main; 27 | sendfile on; 28 | #tcp_nopush on; 29 | 30 | #keepalive_timeout 0; 31 | keepalive_timeout 65; 32 | client_body_buffer_size 128k; 33 | 34 | #gzip on; 35 | 36 | #this line shoud be include in every http block 37 | include /opt/verynginx/verynginx/nginx_conf/in_http_block.conf; 38 | 39 | server { 40 | listen 80; 41 | 42 | #this line shoud be include in every server block 43 | include /opt/verynginx/verynginx/nginx_conf/in_server_block.conf; 44 | 45 | location = / { 46 | root html; 47 | index index.html index.htm; 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # VeryNginx 2 | VeryNginx is a very powerful and friendly nginx . 3 | 4 | [中文文档](https://github.com/alexazhou/VeryNginx/blob/master/readme_zh.md) 5 | 6 | ### Notice 7 | After v0.2 , The entry uri of control panel was moved to `/verynginx/index.html` 8 | 9 | 10 | ## Description 11 | 12 | VeryNginx is based on `lua_nginx_module(openrestry)`. It implements advanced firewall(waf), access statistics and some other features. It strengthens the Nginx's functions, and provides a friendly Web interface. 13 | 14 | [VeryNginx online demo](http://alexazhou.xyz/vn/index.html) 15 | 16 | User / Password: **verynginx / verynginx** 17 | 18 | The full version of config guide can be found on: [VeryNginx Wiki](https://github.com/alexazhou/VeryNginx/wiki) . 19 | 20 | ### Nginx run status analyzing 21 | 22 | * Request per second 23 | * Response time 24 | * Net Traffic 25 | * Tcp connectinn num 26 | 27 |  28 | 29 | 30 | ### Custom Action 31 | 32 | VeryNginx supports custom actions, which can do a lot of things. 33 | 34 | Custom action consists of two parts, `Matcher` and `Action` . `Matcher` is used to test whether a request meets the rule, `Action` is the logic you want to run. 35 | 36 | >The advantage of this design is that the `Matcher` includes all select rule, and can be reused, making use of rules to describe a very complex logic possible. 37 | 38 | #### Matcher 39 | 40 | A `Matcher` is used to select a part of all requests, a `Matcher` may contain one or more condition. The following conditions are currently supported: 41 | 42 | * Client IP 43 | * Host 44 | * UserAgent 45 | * URI 46 | * Referer 47 | * Request Args 48 | 49 | When a request does not conflict with any of the conditions of the Matcher, the request will be selected by the `Matcher` 50 | 51 | #### Action 52 | 53 | Every `Action` refers to a `Matcher` , and will run on the requests selected by the `Matcher` 54 | 55 | Now we have these `Action`s 56 | 57 | * **Scheme** Lock lock the scheme to http/https 58 | * **Redirect** Redirect request 59 | * **URI Rewrite** Do internal rewrite on the request 60 | * **Browser Verify** Use set-cookies and javascript to verify the client is a browser,and block traffic of the robot. This action may block the spider of search engine, so please enable it when under attack only. 61 | * **Frequency Limit** Limit max request time in a specified time period 62 | * **Filter** Block some request, can do the WAF 63 | 64 | Matcher can select requests by multiple conditions, so with Filter Action, we got a powerful waf. The waf can filter requests with complex rules and return special status code when it block a request. 65 | 66 | VeryNginx presets some simple filter rules, which can prevent simple SQL injection, Git and SVN file disclosure, directory traversal attacks and common scanning tool. 67 | 68 |  69 | 70 |  71 | 72 | #### Backend 73 | 74 | Every `Backend` refers to a `Matcher`,and will handle the requests selected by the `Matcher` 75 | 76 | Now we have these `Backend` 77 | 78 | * **Proxy Pass** Proxy the request to other server 79 | * **Static File** Use local file to handle the request file 80 | 81 | ### Request statistics 82 | 83 | VeryNginx can record the request of URI, include these data of every URI: 84 | 85 | * All Request Count 86 | * Request count of every status code 87 | * Total Bytes 88 | * Avg Bytes 89 | * Total response time 90 | * Avg reqponse time 91 | 92 | 93 |  94 | 95 | 96 | ## Installation 97 | 98 | ### Install Nginx / OpenResty 99 | 100 | VeryNginx is based on OpenResty, so you need to install it first. But don't warry, VeryNginx gives a script to do it automatically. 101 | 102 | 103 | ``` 104 | python install.py install 105 | ``` 106 | 107 | Just run this command, openresty and verynginx will be installed automatically. 108 | 109 | #### Want using custom nginx? 110 | 111 | VeryNginx can install openresty automatically so that you **needn't install nginx(openresty) manually**. 112 | 113 | But if you want use a nginx compiled by you self, that also ok. You can see that for some help 114 | 115 | [Use-own-nginx](https://github.com/alexazhou/VeryNginx/wiki/Use-own-nginx) 116 | 117 | ### Usage 118 | 119 | #### Edit nginx configuration file 120 | 121 | The configuration file of VeryNginx is `/opt/verynginx/openresty/nginx/conf/nginx.conf`, that's a demo. It just can let verynginx run so that you can see the dashboard of verynginx. If you want do something really useful, you need edit that file and add your own nginx configuration into it. 122 | 123 | > 124 | This configuration file add three `include` command to embeded verynginx into original nginx( openresty ) 125 | 126 | > 127 | * include /opt/verynginx/verynginx/nginx_conf/in_external.conf; 128 | * include /opt/verynginx/verynginx/nginx_conf/in_http_block.conf; 129 | * include /opt/verynginx/verynginx/nginx_conf/in_server_block.conf; 130 | 131 | >These `include` command were placed outside a block, block http internal configuration, server configuration block inside, Remenber keep these three line when modifying. If you add a new Server configuration block or http configuration block, also need add suitable `include` line into it. 132 | 133 | ### Start / Stop / Restart Service 134 | 135 | ``` 136 | #Start Service 137 | /opt/verynginx/openresty/nginx/sbin/nginx 138 | 139 | #Stop Service 140 | /opt/verynginx/openresty/nginx/sbin/nginx -s stop 141 | 142 | #Restart Service 143 | /opt/verynginx/openresty/nginx/sbin/nginx -s reload 144 | 145 | ``` 146 | 147 | ### Configure VeryNginx on dashboard 148 | 149 | After the service begin running, you can see server status and do config on dashboard. 150 | 151 | The address of dashboard is `http://{{your_machine_address}}/verynginx/index.html`. 152 | 153 | Default user and password is `verynginx` / `verynginx`. You should be able to work through all the options now. 154 | 155 | The full version of config guide can be found in [VeryNginx Wiki](https://github.com/alexazhou/VeryNginx/) . 156 | 157 | ### Trouble Shooting 158 | 159 | If you have any problems during **installation** / **configuration** / **use** , you can refer the Trouble Shooting document. 160 | 161 | [Trouble Shooting](https://github.com/alexazhou/VeryNginx/wiki/Trouble-Shooting) 162 | 163 | #### Tips 164 | 165 | * New configs will be effective immediately upon saving. It's not necessary to restart or reload nginx. 166 | 167 | * When you save config, VeryNginx will write all configs to `/opt/verynginx/verynginx/configs/config.json`. 168 | 169 | * If the chat in status page is stuck, you can click the gear icon in the upper right corner to turn off animation 170 | 171 | * If you lock yourself out of VeryNginx by doing something stupid, you can always delete `config.json` to revert VeryNginx to its default. 172 | 173 | ### Update VeryNginx / OpenResty 174 | 175 | Over time, VeryNginx own will evolve, and can also support newer version of OpenResty. New version of VeryNginx might support some new features or fix some old bugs. If you want to update locally installed VeryNginx, you just need pull the latest code from github to local, and run the following commands: 176 | 177 | ``` 178 | #Update VeryNginx 179 | python install.py update verynginx 180 | 181 | #Update OpenResty 182 | python install.py update openresty 183 | 184 | ``` 185 | 186 | install.py will keep the old config.json and nginx.conf during update. So that you **will not lost configuration** after update. 187 | 188 | ### Build VeryNginx docker Image 189 | 190 | After cloning code to your local filesystem, you can run the following commands: 191 | 192 | ``` 193 | docker build -t verynginx . 194 | docker run verynginx 195 | ``` 196 | 197 | Then you can navigate to your browser `http://{{your_docker_machine_address}}/verynginx/index.html` 198 | 199 | Optionally you can run `docker run -p xxxx:80 verynginx` to map your container port 80 to your host's xxxx port 200 | 201 | 202 | ## Donate 203 | 204 | If you like VeryNginx, you can donate to support my development VeryNginx. With your support, I will be able to make VeryNginx better 😎. 205 | 206 | ### PayPal 207 | 208 | [Support VeryNginx via PayPal](https://www.paypal.me/alexazhou) 209 | 210 | ### We Chat 211 | 212 | Scan the QRcode to support VeryNginx. 213 | 214 | <img title="WeChat QRcode" src="http://ww4.sinaimg.cn/mw690/3fcd0ed3jw1f6kecm1e3nj20f00emq59.jpg" width="200"> 215 | 216 | ## Thanks 217 | 218 | [VeryNginx thanks for the help](https://github.com/alexazhou/VeryNginx/wiki/Thanks) 219 | 220 | 221 | ### Enjoy~ 222 | 223 | [^openresty]: [OpenResty](https://github.com/openresty/openresty) Openresty is a enhanced nginx,bundle standard nginx core and lots of 3rd-party nginx module. 224 | 225 | -------------------------------------------------------------------------------- /readme_zh.md: -------------------------------------------------------------------------------- 1 | # VeryNginx 2 | VeryNginx 是一个功能强大而对人类友好的 Nginx 扩展程序. 3 | 4 | ### 提示 5 | `v0.2` 版本之后,控制台入口被移动到了 `/verynginx/index.html` 6 | 7 | ## 介绍 8 | 9 | VeryNginx 基于 `lua_nginx_module(openrestry)` 开发,实现了高级的防火墙、访问统计和其他的一些功能。 集成在 Nginx 中运行,扩展了 Nginx 本身的功能,并提供了友好的 Web 交互界面。 10 | 11 | [VeryNginx在线实例](http://alexazhou.xyz/vn/index.html) 12 | 13 | 用户名 / 密码: **verynginx / verynginx** 14 | 15 | 详细配置说明见:[VeryNginx Github WiKi](https://github.com/alexazhou/VeryNginx/wiki/目录) 16 | 17 | ### Nginx 运行状态分析 18 | 19 | * 每秒请求数 20 | * 响应时间 21 | * 网络流量 22 | * 网络连接数 23 | 24 |  25 | 26 | 27 | ### 自定义行为 28 | 29 | VeryNginx 包含强大的自定义功能,可以做很多事情 30 | 31 | 自定义行为包含两部分, Matcher 和 Action 。 Matcher 用来对请求进行匹配, Action 为要执行的动作 32 | 33 | 这样的优势在于把所有的前置判断整合在Matcher里一起来实现了,使复杂(组合)规则的实现变成了可能 34 | 35 | #### Matcher 36 | 37 | 一个 Matcher 用来判断一个 Http 请求是否符合指定的条件, 一个 Matcher 可以包含一个或者多个约束条件,目前支持以下几种约束: 38 | 39 | * Client IP 40 | * Host 41 | * UserAgent 42 | * URI 43 | * Referer 44 | * Request Args 45 | 46 | 当一个请求没有违反 Matcher 中包含的全部条件时,即命中了这个 Matcher 47 | 48 | #### Action 49 | 50 | 每个 Action 会引用一个 Matcher ,当 Matcher 命中时, Action 会被执行 51 | 52 | 目前已经实现了以下 Action 53 | 54 | * **Scheme Lock** 将访问协议锁定为 Https 或者 Http 55 | * **Redirect** 对请求进行重定向 56 | * **URI Rewrite** 对请求的 URI 进行内部重写 57 | * **Browser Verify** 通过set-cookies 和 js 验证客户端是否为浏览器,并拦截非浏览器流量。本功能可能会阻拦搜索引擎爬虫,建议仅在被攻击时开启,或者针对搜索引擎编写特别的规则。 58 | * **Frequency Limit** 访问频率限制 59 | * **Filter(waf)** 过滤器 60 | 61 | 因为 Matcher 可以对请求进行细致的匹配,所以结合 Filter Action,就可以实现一个高级的WAF,可以利用Matcher中所有的条件来对请求进行过滤,并返回指定状态码 62 | 63 | VeryNginx 预置了常用的过滤规则,可以在一定程度上阻止常见的 SQL 注入、Git 及 SVN 文件泄露、目录遍历攻击,并拦截常见的扫描工具。 64 | 65 |  66 | 67 |  68 | 69 | #### Backend 70 | 71 | 每个 Backend 会引用一个 Matcher ,当 Matcher 命中时, 请求会通过 Backend 进行处理 72 | 73 | 目前已经实现了以下 Backend 74 | 75 | * **Proxy Pass** 将请求反向代理到其它服务器 76 | * **Static File** 使用本地文件处理请求 77 | 78 | ### 访问统计 79 | 80 | VeryNginx 可以统计网站每个URI的访问情况,包括每个URI的: 81 | 82 | * 总请求次数 83 | * 各状态码次数 84 | * 返回总字节数 85 | * 每请求平均字节数 86 | * 总响应时间 87 | * 平均响应时间 88 | 89 | 并且可以按各种规则排序进行分析。 90 | 91 |  92 | 93 | ## 安装和使用说明 94 | 95 | VeryNginx 基于 OpenResty[^openresty],所以安装 VeryNginx 需要先安装好 OpenResty。不过并不用担心安装过程中可能的麻烦,VeryNginx 自身提供了脚本来进行安装工作。 96 | 97 | ### 安装 VeryNginx 98 | 99 | 克隆 VeryNginx 仓库到本地, 然后进入仓库目录,执行以下命令 100 | 101 | ``` 102 | python install.py install 103 | ``` 104 | 105 | 即可一键安装 VeryNginx 和 以及依赖的 OpenResty 106 | 107 | #### 想使用自己的 Nginx? 108 | 109 | VeryNginx 可以自动为你安装依赖的 OpenResty,通常情况下你**没有必要**再自己安装 OpenResty。 110 | 111 | 但如果你想要**使用自己编译的 Nginx( OpenResty )**,也是可以的。具体方法请阅读Wiki中的这篇说明:[Use own nginx](https://github.com/alexazhou/VeryNginx/wiki/Use-own-nginx) 112 | ### 使用 113 | 114 | #### 编辑 Nginx 配置文件 115 | 116 | VeryNginx 的配置文件位置为 **/opt/verynginx/openresty/nginx/conf/nginx.conf**,这是一个简单的示例文件,可以让你访问到 VeryNginx的控制面板。如果你想真正的用 VeryNginx 来做点什么,那你需要编辑这个文件,并将自己的 Nginx 配置加入到其中。 117 | 118 | >这个配置文件在普通的 Nginx 配置文件基础上添加了三条 Include 指令来实现功能,分别为 119 | > 120 | * include /opt/verynginx/verynginx/nginx_conf/in_external.conf; 121 | * include /opt/verynginx/verynginx/nginx_conf/in_http_block.conf; 122 | * include /opt/verynginx/verynginx/nginx_conf/in_server_block.conf; 123 | > 124 | 以上三条指令分别放在 http 配置块外部,http 配置块内部,server 配置块内部,在修改时请保留这三条。如果添加了新的 Server 配置块或 http 配置块,也需要在新的块内部加入对应的 include 行。 125 | 126 | ### 启动/停止/重启 服务 127 | 128 | 完成安装工作以后,可以通过以下命令来运行 VeryNginx 129 | 130 | ``` 131 | #启动服务 132 | /opt/verynginx/openresty/nginx/sbin/nginx 133 | 134 | #停止服务 135 | /opt/verynginx/openresty/nginx/sbin/nginx -s stop 136 | 137 | #重启服务 138 | /opt/verynginx/openresty/nginx/sbin/nginx -s reload 139 | ``` 140 | 141 | 142 | ### 通过web面板对 VeryNginx 进行配置 143 | 144 | VeryNginx 启动后,可以通过浏览器访问管理面板来查看状态以及进行配置。 145 | 146 | 管理面板地址为 `http://{{your_machine_address}}/verynginx/index.html`。 147 | 148 | 默认用户名和密码是 `verynginx` / `verynginx`。 149 | 150 | 登录之后就可以查看状态,并对配置进行修改了。修改配置后,点击保存才会生效. 151 | 152 | ### 故障排除 153 | 154 | 如果你在 **安装** / **配置** / **使用** 的过程中遇到任何问题, 你可以参考故障排除文档来解决. 155 | 156 | [故障排除](https://github.com/alexazhou/VeryNginx/wiki/Trouble-Shooting) 157 | 158 | 159 | #### 详细的配置说明 160 | 161 | VeryNginx 按照易于使用的思想进行设计,如果你有一定的基础,或是对 Nginx 较了解,那么你应该可以直接在界面上使用。 162 | 163 | 当然 VeryNginx 也提供了详细的文档供你查阅。 164 | [VeryNginx Wiki](https://github.com/alexazhou/VeryNginx/wiki/目录) 165 | 166 | #### 提示 167 | 168 | * 通过 VeryNginx 控制面板保存新配置之后,会立刻生效,并不需要 restart/reload Nginx。 169 | 170 | * VeryNginx 把配置保存在 `/opt/verynginx/verynginx/configs/config.json` 里面。 171 | 172 | * 状态页面图表默认带有动画效果,如果有卡顿,可以点右上角齿轮图标关掉动画效果 173 | 174 | * 如果因为配错了什么选项,导致无法登录,可以手动删除 `config.json` 来清空配置,或者手动编辑这个文件来修复。 175 | 176 | ### 更新 VeryNginx / OpenResty 177 | 178 | 随着时间的发展,VeryNginx 本身的代码会演进,也可以会支持更新版本的 OpenResty ,更新的代码可能会支持一些新的功能,或是修复了一些旧的bug。如果要更新本地已经安装的 VeryNginx ,你只需要先 pull github 上最新的代码到本地,然后通过以下命令来进行更新: 179 | 180 | 181 | ``` 182 | #更新 Verynginx 183 | python install.py update verynginx 184 | 185 | #更新 OpenResty 186 | python install.py update openresty 187 | 188 | ``` 189 | 190 | install.py脚本在升级过程中,将保留原有的 config.json 和 nginx.conf, 所以**更新的过程并不会丢失配置** 191 | 192 | ## 构建VeryNginx docker 镜像 193 | 194 | 在将代码clone到本地之后,你可以运行下面的命令: 195 | 196 | ``` 197 | cd VeryNginx 198 | docker build -t verynginx . 199 | docker run -d --name=verynginx -p 8080:80 verynginx 200 | ``` 201 | 然后用浏览器打开 `http://{{your_docker_machine_address}}/verynginx/index.html` 202 | 203 | 当然你也可以运行 `docker run -d --name=verynginx -p xxxx:80 verynginx` 来映射一下你的container的端口到你的宿主机,默认是80,你可以把xxxx改成你希望的在宿主机上的端口号 204 | 205 | ## 捐赠 206 | 207 | 如果你喜欢 VeryNginx,那么你可以通过捐赠来支持我开发 VeryNginx。有了你的支持,我将可以让 VeryNginx 变的更好😎 208 | 209 | ### PayPal 210 | 211 | [通过 PayPal 来支持 VeryNginx](https://www.paypal.me/alexazhou) 212 | 213 | ### 微信 214 | 215 | 扫描下方的二维码来支持 VeryNginx 216 | 217 | <img title="WeChat QRcode" src="http://ww4.sinaimg.cn/mw690/3fcd0ed3jw1f6kecm1e3nj20f00emq59.jpg" width="200"> 218 | 219 | 220 | ## 致谢 221 | 222 | [感谢大家对VeryNginx的帮助](https://github.com/alexazhou/VeryNginx/wiki/Thanks) 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /test/base_case.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import unittest 4 | 5 | 6 | 7 | class Base_Case(unittest.TestCase): 8 | def __init__(self, *args, **kwargs): 9 | super(Base_Case, self).__init__(*args, **kwargs) 10 | self.desc = "Base Case" 11 | self.ngx_bin = '/opt/verynginx/openresty/nginx/sbin/nginx' 12 | self.ngx_errlog = '/opt/verynginx/openresty/nginx/logs/error.log' 13 | 14 | self.ngx_conf_dir = None 15 | self.ngx_conf = None 16 | 17 | self.vn_conf_dir = self.ngx_conf_dir 18 | self.vn_conf = 'config.json' 19 | 20 | self.f_ngx_errlog = None 21 | 22 | def exec_sys_cmd(self, cmd): 23 | ret = os.system(cmd) 24 | assert ret == 0 25 | 26 | def cfg_str(self): 27 | if self.ngx_conf == None: 28 | return '' 29 | else: 30 | return ' -c %s'%self.ngx_conf 31 | 32 | def get_ngx_stderr(self): 33 | self.exec_sys_cmd(self.ngx_bin + self.cfg_str() + ' -s reopen')#refresh nginx log 34 | ret = self.f_ngx_errlog.read() 35 | assert len(self.f_ngx_errlog.read(1)) == 0 #make sure no more log 36 | return ret 37 | 38 | def check_ngx_stderr(self, log_str=None, ignore_flag=[]): 39 | if log_str == None: 40 | log_str = self.get_ngx_stderr() 41 | 42 | mark = [ '[lua]','stack traceback','coroutine','in function','aborted','runtime error' ] 43 | for item in ignore_flag: 44 | mark.remove(item) 45 | 46 | for item in mark: 47 | if item in log_str: 48 | print( 'Got unexpected flag "%s" in nginx error.log:%s'%(item,log_str)) 49 | assert False 50 | 51 | def setUp(self): 52 | print('Run: %s'%self.desc) 53 | #open nginx error.log 54 | self.f_ngx_errlog = open(self.ngx_errlog,'r') 55 | self.f_ngx_errlog.seek(0,os.SEEK_END) 56 | 57 | #prepare config.json for verynginx 58 | self.exec_sys_cmd('rm -rf /opt/verynginx/verynginx/configs/*') 59 | if self.vn_conf != None: 60 | self.exec_sys_cmd('cp %s/%s /opt/verynginx/verynginx/configs/config.json'%(self.vn_conf_dir, self.vn_conf)) 61 | 62 | #start nginx 63 | self.exec_sys_cmd(self.ngx_bin + self.cfg_str()) 64 | 65 | def tearDown(self): 66 | #close nginx log file 67 | self.f_ngx_errlog.close() 68 | #stop nginx 69 | self.exec_sys_cmd(self.ngx_bin + self.cfg_str() + ' -s stop') 70 | -------------------------------------------------------------------------------- /test/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | -------------------------------------------------------------------------------- /test/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2016-08-21 23:06 4 | # @Author : Alexa (AlexaZhou@163.com) 5 | # @Link : https://github.com/alexazhou/VeryNginx 6 | # @Disc : test VeryNginx 7 | # @Disc : support python 3.x 8 | 9 | import os 10 | import time 11 | import unittest 12 | 13 | 14 | def load_test_case(): 15 | ret = {} 16 | 17 | dir_filter = lambda x:os.path.exists( './testcase/' + x + '/case.py' ) 18 | case_list = list(filter( dir_filter, os.listdir('./testcase/') )) 19 | 20 | for case in case_list: 21 | tmp = __import__( 'testcase.%s.case'%case ) 22 | ret[case] = eval( 'tmp.%s.case'%case ) 23 | 24 | return ret 25 | 26 | 27 | if __name__ == "__main__": 28 | 29 | script_path = os.path.dirname( os.path.abspath(__file__) ) 30 | os.chdir( script_path ) 31 | 32 | all_case = load_test_case() 33 | major_suite = unittest.TestSuite() 34 | for k in all_case.keys(): 35 | suite = unittest.defaultTestLoader.loadTestsFromTestCase(all_case[k].Case) 36 | major_suite.addTests( suite ) 37 | 38 | runner = unittest.TextTestRunner() 39 | ret = runner.run(major_suite) 40 | assert ret.wasSuccessful() 41 | -------------------------------------------------------------------------------- /test/testcase/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexazhou/VeryNginx/b66ef2ee6a108279653316323bd8ac026dd7ccd8/test/testcase/__init__.py -------------------------------------------------------------------------------- /test/testcase/a_1_start_without_config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexazhou/VeryNginx/b66ef2ee6a108279653316323bd8ac026dd7ccd8/test/testcase/a_1_start_without_config/__init__.py -------------------------------------------------------------------------------- /test/testcase/a_1_start_without_config/case.py: -------------------------------------------------------------------------------- 1 | import base_case 2 | import requests 3 | import os 4 | 5 | class Case(base_case.Base_Case): 6 | def __init__(self, *args, **kwargs): 7 | super(Case, self).__init__(*args, **kwargs) 8 | self.desc = "test verynginx start without config.json" 9 | self.vn_conf = None 10 | 11 | def test_vn_start_without_conf(self): 12 | r = requests.get('http://127.0.0.1') 13 | assert r.status_code == 200 14 | assert r.headers.get('content-type') == 'text/html' 15 | f = open('/opt/verynginx/openresty/nginx/html/index.html', 'rb') 16 | index_content = f.read(1*1024*1024) 17 | f.close() 18 | assert index_content==r.content 19 | self.check_ngx_stderr() 20 | 21 | -------------------------------------------------------------------------------- /test/testcase/a_2_basic_behaviour/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexazhou/VeryNginx/b66ef2ee6a108279653316323bd8ac026dd7ccd8/test/testcase/a_2_basic_behaviour/__init__.py -------------------------------------------------------------------------------- /test/testcase/a_2_basic_behaviour/case.py: -------------------------------------------------------------------------------- 1 | import base_case 2 | import requests 3 | import os 4 | 5 | class Case(base_case.Base_Case): 6 | def __init__(self, *args, **kwargs): 7 | super(Case, self).__init__(*args, **kwargs) 8 | self.desc = "test basic behaviour with config.json" 9 | self.vn_conf_dir = os.path.dirname(os.path.abspath(__file__)) 10 | 11 | def test_vn_start_with_conf(self): 12 | #test index.html 13 | r = requests.get('http://127.0.0.1') 14 | assert r.status_code == 200 15 | assert r.headers.get('content-type') == 'text/html' 16 | f = open('/opt/verynginx/openresty/nginx/html/index.html', 'rb') 17 | index_content = f.read(1*1024*1024) 18 | f.close() 19 | assert index_content==r.content 20 | self.check_ngx_stderr() 21 | 22 | def test_404(self): 23 | #test notexist.html 24 | r = requests.get('http://127.0.0.1/notexist.html') 25 | assert r.status_code == 404 26 | assert r.headers.get('content-type') == 'text/html' 27 | assert b'404' in r.content 28 | self.check_ngx_stderr() 29 | 30 | def test_vn_index(self): 31 | r = requests.get('http://127.0.0.1/verynginx/index.html') 32 | assert r.status_code == 200 33 | f = open('/opt/verynginx/verynginx/dashboard/index.html', 'rb') 34 | index_content = f.read(1*1024*1024) 35 | f.close() 36 | assert index_content==r.content 37 | self.check_ngx_stderr() 38 | 39 | def test_vn_index_with_short_url(self): 40 | r = requests.get('http://127.0.0.1/vn/index.html') 41 | assert r.status_code == 200 42 | f = open('/opt/verynginx/verynginx/dashboard/index.html', 'rb') 43 | index_content = f.read(1*1024*1024) 44 | f.close() 45 | assert index_content==r.content 46 | self.check_ngx_stderr() 47 | 48 | -------------------------------------------------------------------------------- /test/testcase/a_2_basic_behaviour/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "redirect_rule":[{ 3 | "to_uri":"/verynginx/index.html", 4 | "matcher":"demo_other_verynginx_uri", 5 | "enable":true 6 | }], 7 | "filter_rule":[{ 8 | "enable":false, 9 | "action":"accept", 10 | "matcher":"localhost" 11 | },{ 12 | "enable":true, 13 | "action":"block", 14 | "code":"403", 15 | "matcher":"attack_sql_0" 16 | },{ 17 | "enable":true, 18 | "action":"block", 19 | "code":"403", 20 | "matcher":"attack_backup_0" 21 | },{ 22 | "enable":true, 23 | "action":"block", 24 | "code":"403", 25 | "matcher":"attack_scan_0" 26 | },{ 27 | "enable":true, 28 | "action":"block", 29 | "code":"403", 30 | "matcher":"attack_code_0" 31 | }], 32 | "config_version":"0.36", 33 | "static_file_rule":[], 34 | "proxy_pass_rule":[], 35 | "summary_collect_rule":[], 36 | "summary_group_persistent_enable":true, 37 | "admin":[{ 38 | "user":"verynginx", 39 | "password":"verynginx", 40 | "enable":true 41 | }], 42 | "dashboard_host":"", 43 | "response":{ 44 | "demo_response_html":{ 45 | "content_type":"text/html", 46 | "body":"This is a html demo response" 47 | }, 48 | "demo_response_json":{ 49 | "content_type":"application/json", 50 | "body":"{\"msg\":\"soms text\",\"status\":\"success\"}" 51 | } 52 | }, 53 | "scheme_lock_enable":false, 54 | "uri_rewrite_rule":[{ 55 | "to_uri":"/verynginx/$1", 56 | "matcher":"demo_verynginx_short_uri", 57 | "replace_re":"^/vn/(.*)", 58 | "enable":true 59 | }], 60 | "cookie_prefix":"verynginx", 61 | "filter_enable":true, 62 | "browser_verify_enable":true, 63 | "scheme_lock_rule":[{ 64 | "enable":false, 65 | "matcher":"verynginx", 66 | "scheme":"https" 67 | }], 68 | "redirect_enable":true, 69 | "backend_upstream":{ 70 | }, 71 | "frequency_limit_enable":true, 72 | "matcher":{ 73 | "demo_verynginx_short_uri":{ 74 | "URI":{ 75 | "operator":"≈", 76 | "value":"^/vn" 77 | } 78 | }, 79 | "all_request":{ 80 | }, 81 | "attack_sql_0":{ 82 | "Args":{ 83 | "name_operator":"*", 84 | "operator":"≈", 85 | "value":"select.*from" 86 | } 87 | }, 88 | "verynginx":{ 89 | "URI":{ 90 | "operator":"≈", 91 | "value":"^/verynginx/" 92 | } 93 | }, 94 | "attack_scan_0":{ 95 | "UserAgent":{ 96 | "operator":"≈", 97 | "value":"(nmap|w3af|netsparker|nikto|fimap|wget)" 98 | } 99 | }, 100 | "attack_code_0":{ 101 | "URI":{ 102 | "operator":"≈", 103 | "value":"\\.(git|svn|\\.)" 104 | } 105 | }, 106 | "localhost":{ 107 | "IP":{ 108 | "operator":"=", 109 | "value":"127.0.0.1" 110 | } 111 | }, 112 | "demo_other_verynginx_uri":{ 113 | "URI":{ 114 | "operator":"=", 115 | "value":"/redirect_to_verynginx" 116 | } 117 | }, 118 | "attack_backup_0":{ 119 | "URI":{ 120 | "operator":"≈", 121 | "value":"\\.(htaccess|bash_history|ssh|sql)quot; 122 | } 123 | } 124 | }, 125 | "static_file_enable":true, 126 | "summary_temporary_period":60, 127 | "summary_group_temporary_enable":true, 128 | "summary_with_host":false, 129 | "frequency_limit_rule":[], 130 | "uri_rewrite_enable":true, 131 | "base_uri":"/verynginx", 132 | "summary_request_enable":true, 133 | "proxy_pass_enable":true, 134 | "readonly":false, 135 | "browser_verify_rule":[] 136 | } -------------------------------------------------------------------------------- /test/testcase/a_3_matcher_host/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexazhou/VeryNginx/b66ef2ee6a108279653316323bd8ac026dd7ccd8/test/testcase/a_3_matcher_host/__init__.py -------------------------------------------------------------------------------- /test/testcase/a_3_matcher_host/case.py: -------------------------------------------------------------------------------- 1 | import base_case 2 | import requests 3 | import os 4 | 5 | class Case(base_case.Base_Case): 6 | def __init__(self, *args, **kwargs): 7 | super(Case, self).__init__(*args, **kwargs) 8 | self.desc = "test matcher with multiform condition of host" 9 | self.vn_conf_dir = os.path.dirname(os.path.abspath(__file__)) 10 | 11 | def test_host_equal(self): 12 | r = requests.get('http://a.vntest.com/testhostequal') 13 | assert r.status_code == 400 14 | assert r.headers.get('content-type') == 'text/html' 15 | assert 'hited' in r.text 16 | self.check_ngx_stderr() 17 | 18 | r = requests.get('http://b.vntest.com/testhostequal') 19 | assert r.status_code == 404 20 | assert r.headers.get('content-type') == 'text/html' 21 | assert 'hited' not in r.text 22 | self.check_ngx_stderr() 23 | 24 | r = requests.get('http://c.vntest.com/testhostequal') 25 | assert r.status_code == 404 26 | assert r.headers.get('content-type') == 'text/html' 27 | assert 'hited' not in r.text 28 | self.check_ngx_stderr() 29 | 30 | def test_host_not_equal(self): 31 | r = requests.get('http://b.vntest.com/testhostnotequal') 32 | assert r.status_code == 400 33 | assert r.headers.get('content-type') == 'text/html' 34 | assert 'hited' in r.text 35 | self.check_ngx_stderr() 36 | 37 | r = requests.get('http://c.vntest.com/testhostnotequal') 38 | assert r.status_code == 400 39 | assert r.headers.get('content-type') == 'text/html' 40 | assert 'hited' in r.text 41 | self.check_ngx_stderr() 42 | 43 | r = requests.get('http://a.vntest.com/testhostnotequal') 44 | assert r.status_code == 404 45 | assert r.headers.get('content-type') == 'text/html' 46 | assert 'hited' not in r.text 47 | self.check_ngx_stderr() 48 | 49 | def test_host_match(self): 50 | r = requests.get('http://a.vntest.com/testhostmatch') 51 | assert r.status_code == 400 52 | assert r.headers.get('content-type') == 'text/html' 53 | assert 'hited' in r.text 54 | self.check_ngx_stderr() 55 | 56 | r = requests.get('http://b.vntest.com/testhostmatch') 57 | assert r.status_code == 400 58 | assert r.headers.get('content-type') == 'text/html' 59 | assert 'hited' in r.text 60 | self.check_ngx_stderr() 61 | 62 | r = requests.get('http://127.0.0.1/testhostmatch') 63 | assert r.status_code == 404 64 | assert r.headers.get('content-type') == 'text/html' 65 | assert 'hited' not in r.text 66 | self.check_ngx_stderr() 67 | 68 | def test_host_not_match(self): 69 | r = requests.get('http://127.0.0.1/testhostnotmatch') 70 | assert r.status_code == 400 71 | assert r.headers.get('content-type') == 'text/html' 72 | assert 'hited' in r.text 73 | self.check_ngx_stderr() 74 | 75 | r = requests.get('http://a.vntest.com/testhostnotmatch') 76 | assert r.status_code == 404 77 | assert r.headers.get('content-type') == 'text/html' 78 | assert 'hited' not in r.text 79 | self.check_ngx_stderr() 80 | 81 | r = requests.get('http://b.vntest.com/testhostnotmatch') 82 | assert r.status_code == 404 83 | assert r.headers.get('content-type') == 'text/html' 84 | assert 'hited' not in r.text 85 | self.check_ngx_stderr() 86 | 87 | -------------------------------------------------------------------------------- /test/testcase/a_3_matcher_host/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "redirect_rule":[], 3 | "filter_rule":[{ 4 | "enable":true, 5 | "code":"400", 6 | "response":"hit", 7 | "matcher":"host_equal", 8 | "custom_response":true, 9 | "action":"block" 10 | },{ 11 | "enable":true, 12 | "code":"400", 13 | "response":"hit", 14 | "matcher":"host_match", 15 | "custom_response":true, 16 | "action":"block" 17 | },{ 18 | "enable":true, 19 | "code":"400", 20 | "response":"hit", 21 | "matcher":"host_not_equal", 22 | "custom_response":true, 23 | "action":"block" 24 | },{ 25 | "enable":true, 26 | "code":"400", 27 | "response":"hit", 28 | "matcher":"host_not_match", 29 | "custom_response":true, 30 | "action":"block" 31 | }], 32 | "config_version":"0.36", 33 | "static_file_rule":[], 34 | "proxy_pass_rule":[], 35 | "summary_collect_rule":[], 36 | "summary_group_persistent_enable":true, 37 | "admin":[{ 38 | "user":"verynginx", 39 | "password":"verynginx", 40 | "enable":true 41 | }], 42 | "dashboard_host":"", 43 | "response":{ 44 | "hit":{ 45 | "content_type":"text/html", 46 | "body":"hited" 47 | }, 48 | "didn'thit":{ 49 | "content_type":"text/html", 50 | "body":"didn't hited" 51 | } 52 | }, 53 | "scheme_lock_enable":true, 54 | "uri_rewrite_rule":[], 55 | "cookie_prefix":"verynginx", 56 | "filter_enable":true, 57 | "browser_verify_enable":true, 58 | "scheme_lock_rule":[], 59 | "redirect_enable":true, 60 | "backend_upstream":{ 61 | }, 62 | "frequency_limit_enable":true, 63 | "matcher":{ 64 | "host_match":{ 65 | "Host":{ 66 | "value":".*vntest.*", 67 | "operator":"≈" 68 | }, 69 | "URI":{ 70 | "value":"/testhostmatch", 71 | "operator":"=" 72 | } 73 | }, 74 | "host_not_equal":{ 75 | "URI":{ 76 | "value":"/testhostnotequal", 77 | "operator":"=" 78 | }, 79 | "Host":{ 80 | "value":"a.vntest.com", 81 | "operator":"!=" 82 | } 83 | }, 84 | "host_not_match":{ 85 | "Host":{ 86 | "value":".*vntest.*", 87 | "operator":"!≈" 88 | }, 89 | "URI":{ 90 | "value":"/testhostnotmatch", 91 | "operator":"≈" 92 | } 93 | }, 94 | "all_request":{ 95 | }, 96 | "host_equal":{ 97 | "Host":{ 98 | "value":"a.vntest.com", 99 | "operator":"=" 100 | }, 101 | "URI":{ 102 | "value":"/testhostequal", 103 | "operator":"=" 104 | } 105 | } 106 | }, 107 | "static_file_enable":true, 108 | "summary_temporary_period":60, 109 | "summary_group_temporary_enable":true, 110 | "summary_with_host":false, 111 | "frequency_limit_rule":[], 112 | "uri_rewrite_enable":true, 113 | "base_uri":"/verynginx", 114 | "summary_request_enable":true, 115 | "proxy_pass_enable":true, 116 | "readonly":false, 117 | "browser_verify_rule":[] 118 | } -------------------------------------------------------------------------------- /test/testcase/a_4_matcher_useragent/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexazhou/VeryNginx/b66ef2ee6a108279653316323bd8ac026dd7ccd8/test/testcase/a_4_matcher_useragent/__init__.py -------------------------------------------------------------------------------- /test/testcase/a_4_matcher_useragent/case.py: -------------------------------------------------------------------------------- 1 | import base_case 2 | import requests 3 | import os 4 | 5 | class Case(base_case.Base_Case): 6 | def __init__(self, *args, **kwargs): 7 | super(Case, self).__init__(*args, **kwargs) 8 | self.desc = "test matcher with multiform condition of UserAgent" 9 | self.vn_conf_dir = os.path.dirname(os.path.abspath(__file__)) 10 | 11 | def test_useragent_equal(self): 12 | r = requests.get('http://127.0.0.1/test_useragent_equal',headers={'User-Agent':'vntestflag'}) 13 | assert r.status_code == 400 14 | assert r.headers.get('content-type') == 'text/html' 15 | assert 'hited' in r.text 16 | self.check_ngx_stderr() 17 | 18 | r = requests.get('http://127.0.0.1/test_useragent_equal',headers={'User-Agent':'vntestflag1'}) 19 | assert r.status_code == 404 20 | assert r.headers.get('content-type') == 'text/html' 21 | assert 'hited' not in r.text 22 | self.check_ngx_stderr() 23 | 24 | r = requests.get('http://127.0.0.1/test_useragent_equal',headers={'User-Agent':'otherflag'}) 25 | assert r.status_code == 404 26 | assert r.headers.get('content-type') == 'text/html' 27 | assert 'hited' not in r.text 28 | self.check_ngx_stderr() 29 | 30 | r = requests.get('http://127.0.0.1/test_useragent_equal',headers={'User-Agent':''}) 31 | assert r.status_code == 404 32 | assert r.headers.get('content-type') == 'text/html' 33 | assert 'hited' not in r.text 34 | self.check_ngx_stderr() 35 | 36 | r = requests.get('http://127.0.0.1/test_useragent_equal',headers={'User-Agent':None}) 37 | assert r.status_code == 404 38 | assert r.headers.get('content-type') == 'text/html' 39 | assert 'hited' not in r.text 40 | self.check_ngx_stderr() 41 | 42 | def test_useragent_not_equal(self): 43 | r = requests.get('http://127.0.0.1/test_useragent_not_equal',headers={'User-Agent':'otherflag'}) 44 | assert r.status_code == 400 45 | assert r.headers.get('content-type') == 'text/html' 46 | assert 'hited' in r.text 47 | self.check_ngx_stderr() 48 | 49 | r = requests.get('http://127.0.0.1/test_useragent_not_equal',headers={'User-Agent':'vntestflagz'}) 50 | assert r.status_code == 400 51 | assert r.headers.get('content-type') == 'text/html' 52 | assert 'hited' in r.text 53 | self.check_ngx_stderr() 54 | 55 | r = requests.get('http://127.0.0.1/test_useragent_not_equal',headers={'User-Agent':''}) 56 | assert r.status_code == 400 57 | assert r.headers.get('content-type') == 'text/html' 58 | assert 'hited' in r.text 59 | self.check_ngx_stderr() 60 | 61 | r = requests.get('http://127.0.0.1/test_useragent_not_equal',headers={'User-Agent':None}) 62 | assert r.status_code == 400 63 | assert r.headers.get('content-type') == 'text/html' 64 | assert 'hited' in r.text 65 | self.check_ngx_stderr() 66 | 67 | r = requests.get('http://127.0.0.1/test_useragent_not_equal',headers={'User-Agent':'vntestflag'}) 68 | assert r.status_code == 404 69 | assert r.headers.get('content-type') == 'text/html' 70 | assert 'hited' not in r.text 71 | self.check_ngx_stderr() 72 | 73 | def test_useragent_match(self): 74 | r = requests.get('http://127.0.0.1/test_useragent_match',headers={'User-Agent':'aaatestbbb'}) 75 | assert r.status_code == 400 76 | assert r.headers.get('content-type') == 'text/html' 77 | assert 'hited' in r.text 78 | self.check_ngx_stderr() 79 | 80 | r = requests.get('http://127.0.0.1/test_useragent_match',headers={'User-Agent':'aaaotherbbb'}) 81 | assert r.status_code == 404 82 | assert r.headers.get('content-type') == 'text/html' 83 | assert 'hited' not in r.text 84 | self.check_ngx_stderr() 85 | 86 | r = requests.get('http://127.0.0.1/test_useragent_match',headers={'User-Agent':'aaazestbbb'}) 87 | assert r.status_code == 404 88 | assert r.headers.get('content-type') == 'text/html' 89 | assert 'hited' not in r.text 90 | self.check_ngx_stderr() 91 | 92 | r = requests.get('http://127.0.0.1/test_useragent_match',headers={'User-Agent':''}) 93 | assert r.status_code == 404 94 | assert r.headers.get('content-type') == 'text/html' 95 | assert 'hited' not in r.text 96 | self.check_ngx_stderr() 97 | 98 | r = requests.get('http://127.0.0.1/test_useragent_match',headers={'User-Agent':None}) 99 | assert r.status_code == 404 100 | assert r.headers.get('content-type') == 'text/html' 101 | assert 'hited' not in r.text 102 | self.check_ngx_stderr() 103 | 104 | def test_useragent_not_match(self): 105 | r = requests.get('http://127.0.0.1/test_useragent_not_match',headers={'User-Agent':'aaaotherbbb'}) 106 | assert r.status_code == 400 107 | assert r.headers.get('content-type') == 'text/html' 108 | assert 'hited' in r.text 109 | self.check_ngx_stderr() 110 | 111 | r = requests.get('http://127.0.0.1/test_useragent_not_match',headers={'User-Agent':'aaazestbbb'}) 112 | assert r.status_code == 400 113 | assert r.headers.get('content-type') == 'text/html' 114 | assert 'hited' in r.text 115 | self.check_ngx_stderr() 116 | 117 | r = requests.get('http://127.0.0.1/test_useragent_not_match',headers={'User-Agent':''}) 118 | assert r.status_code == 400 119 | assert r.headers.get('content-type') == 'text/html' 120 | assert 'hited' in r.text 121 | self.check_ngx_stderr() 122 | 123 | r = requests.get('http://127.0.0.1/test_useragent_not_match',headers={'User-Agent':None}) 124 | assert r.status_code == 400 125 | assert r.headers.get('content-type') == 'text/html' 126 | assert 'hited' in r.text 127 | self.check_ngx_stderr() 128 | 129 | r = requests.get('http://127.0.0.1/test_useragent_not_match',headers={'User-Agent':'aaatestbbb'}) 130 | assert r.status_code == 404 131 | assert r.headers.get('content-type') == 'text/html' 132 | assert 'hited' not in r.text 133 | self.check_ngx_stderr() 134 | 135 | def test_useragent_existed(self): 136 | r = requests.get('http://127.0.0.1/test_useragent_existed',headers={'User-Agent':'aaatestbbb'}) 137 | assert r.status_code == 400 138 | assert r.headers.get('content-type') == 'text/html' 139 | assert 'hited' in r.text 140 | self.check_ngx_stderr() 141 | 142 | r = requests.get('http://127.0.0.1/test_useragent_existed',headers={'User-Agent':'aaaotherbbb'}) 143 | assert r.status_code == 400 144 | assert r.headers.get('content-type') == 'text/html' 145 | assert 'hited' in r.text 146 | self.check_ngx_stderr() 147 | 148 | r = requests.get('http://127.0.0.1/test_useragent_existed',headers={'User-Agent':''}) 149 | assert r.status_code == 400 150 | assert r.headers.get('content-type') == 'text/html' 151 | assert 'hited' in r.text 152 | self.check_ngx_stderr() 153 | 154 | r = requests.get('http://127.0.0.1/test_useragent_existed',headers={'User-Agent':None}) 155 | assert r.status_code == 404 156 | assert r.headers.get('content-type') == 'text/html' 157 | assert 'hited' not in r.text 158 | self.check_ngx_stderr() 159 | 160 | def test_useragent_not_existed(self): 161 | r = requests.get('http://127.0.0.1/test_useragent_not_existed',headers={'User-Agent':None}) 162 | assert r.status_code == 400 163 | assert r.headers.get('content-type') == 'text/html' 164 | assert 'hited' in r.text 165 | self.check_ngx_stderr() 166 | 167 | r = requests.get('http://127.0.0.1/test_useragent_not_existed',headers={'User-Agent':'aaatestbbb'}) 168 | assert r.status_code == 404 169 | assert r.headers.get('content-type') == 'text/html' 170 | assert 'hited' not in r.text 171 | self.check_ngx_stderr() 172 | 173 | r = requests.get('http://127.0.0.1/test_useragent_not_existed',headers={'User-Agent':'aaaotherbbb'}) 174 | assert r.status_code == 404 175 | assert r.headers.get('content-type') == 'text/html' 176 | assert 'hited' not in r.text 177 | self.check_ngx_stderr() 178 | 179 | r = requests.get('http://127.0.0.1/test_useragent_not_existed',headers={'User-Agent':''}) 180 | assert r.status_code == 404 181 | assert r.headers.get('content-type') == 'text/html' 182 | assert 'hited' not in r.text 183 | self.check_ngx_stderr() 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /test/testcase/a_4_matcher_useragent/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "redirect_rule":[], 3 | "filter_rule":[{ 4 | "enable":true, 5 | "code":"400", 6 | "response":"hit", 7 | "matcher":"useragent_equal", 8 | "custom_response":true, 9 | "action":"block" 10 | },{ 11 | "enable":true, 12 | "code":"400", 13 | "response":"hit", 14 | "matcher":"useragent_not_equal", 15 | "custom_response":true, 16 | "action":"block" 17 | },{ 18 | "enable":true, 19 | "code":"400", 20 | "response":"hit", 21 | "matcher":"useragent_match", 22 | "custom_response":true, 23 | "action":"block" 24 | },{ 25 | "enable":true, 26 | "code":"400", 27 | "response":"hit", 28 | "matcher":"useragent_not_match", 29 | "custom_response":true, 30 | "action":"block" 31 | },{ 32 | "enable":true, 33 | "code":"400", 34 | "response":"hit", 35 | "matcher":"useragent_existed", 36 | "custom_response":true, 37 | "action":"block" 38 | },{ 39 | "enable":true, 40 | "code":"400", 41 | "response":"hit", 42 | "action":"block", 43 | "custom_response":true, 44 | "matcher":"useragent_not_existed" 45 | }], 46 | "config_version":"0.36", 47 | "static_file_rule":[], 48 | "proxy_pass_rule":[], 49 | "summary_collect_rule":[], 50 | "summary_group_persistent_enable":true, 51 | "admin":[{ 52 | "user":"verynginx", 53 | "password":"verynginx", 54 | "enable":true 55 | }], 56 | "dashboard_host":"", 57 | "response":{ 58 | "hit":{ 59 | "content_type":"text/html", 60 | "body":"hited" 61 | }, 62 | "didn'thit":{ 63 | "content_type":"text/html", 64 | "body":"didn't hited" 65 | } 66 | }, 67 | "scheme_lock_enable":true, 68 | "uri_rewrite_rule":[], 69 | "cookie_prefix":"verynginx", 70 | "filter_enable":true, 71 | "browser_verify_enable":true, 72 | "scheme_lock_rule":[], 73 | "redirect_enable":true, 74 | "backend_upstream":{ 75 | }, 76 | "frequency_limit_enable":true, 77 | "matcher":{ 78 | "useragent_equal":{ 79 | "URI":{ 80 | "value":"/test_useragent_equal", 81 | "operator":"=" 82 | }, 83 | "UserAgent":{ 84 | "value":"vntestflag", 85 | "operator":"=" 86 | } 87 | }, 88 | "useragent_not_match":{ 89 | "URI":{ 90 | "value":"/test_useragent_not_match", 91 | "operator":"=" 92 | }, 93 | "UserAgent":{ 94 | "value":".*test.*", 95 | "operator":"!≈" 96 | } 97 | }, 98 | "useragent_existed":{ 99 | "URI":{ 100 | "value":"/test_useragent_existed", 101 | "operator":"=" 102 | }, 103 | "UserAgent":{ 104 | "operator":"Exist" 105 | } 106 | }, 107 | "useragent_not_existed":{ 108 | "URI":{ 109 | "value":"/test_useragent_not_existed", 110 | "operator":"=" 111 | }, 112 | "UserAgent":{ 113 | "operator":"!Exist" 114 | } 115 | }, 116 | "all_request":{ 117 | }, 118 | "useragent_not_equal":{ 119 | "URI":{ 120 | "value":"/test_useragent_not_equal", 121 | "operator":"=" 122 | }, 123 | "UserAgent":{ 124 | "value":"vntestflag", 125 | "operator":"!=" 126 | } 127 | }, 128 | "useragent_match":{ 129 | "URI":{ 130 | "value":"/test_useragent_match", 131 | "operator":"=" 132 | }, 133 | "UserAgent":{ 134 | "value":".*test.*", 135 | "operator":"≈" 136 | } 137 | } 138 | }, 139 | "static_file_enable":true, 140 | "summary_temporary_period":60, 141 | "summary_group_temporary_enable":true, 142 | "summary_with_host":false, 143 | "frequency_limit_rule":[], 144 | "uri_rewrite_enable":true, 145 | "base_uri":"/verynginx", 146 | "summary_request_enable":true, 147 | "proxy_pass_enable":true, 148 | "readonly":false, 149 | "browser_verify_rule":[] 150 | } -------------------------------------------------------------------------------- /test/testcase/a_5_matcher_refer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexazhou/VeryNginx/b66ef2ee6a108279653316323bd8ac026dd7ccd8/test/testcase/a_5_matcher_refer/__init__.py -------------------------------------------------------------------------------- /test/testcase/a_5_matcher_refer/case.py: -------------------------------------------------------------------------------- 1 | import base_case 2 | import requests 3 | import os 4 | 5 | class Case(base_case.Base_Case): 6 | def __init__(self, *args, **kwargs): 7 | super(Case, self).__init__(*args, **kwargs) 8 | self.desc = "test matcher with multiform condition of referer" 9 | self.vn_conf_dir = os.path.dirname(os.path.abspath(__file__)) 10 | 11 | def test_referer_equal(self): 12 | r = requests.get('http://127.0.0.1/test_referer_equal',headers={'Referer':'vntestflag'}) 13 | assert r.status_code == 400 14 | assert r.headers.get('content-type') == 'text/html' 15 | assert 'hited' in r.text 16 | self.check_ngx_stderr() 17 | 18 | r = requests.get('http://127.0.0.1/test_referer_equal',headers={'Referer':'vntestflag1'}) 19 | assert r.status_code == 404 20 | assert r.headers.get('content-type') == 'text/html' 21 | assert 'hited' not in r.text 22 | self.check_ngx_stderr() 23 | 24 | r = requests.get('http://127.0.0.1/test_referer_equal',headers={'Referer':'otherflag'}) 25 | assert r.status_code == 404 26 | assert r.headers.get('content-type') == 'text/html' 27 | assert 'hited' not in r.text 28 | self.check_ngx_stderr() 29 | 30 | r = requests.get('http://127.0.0.1/test_referer_equal',headers={'Referer':''}) 31 | assert r.status_code == 404 32 | assert r.headers.get('content-type') == 'text/html' 33 | assert 'hited' not in r.text 34 | self.check_ngx_stderr() 35 | 36 | r = requests.get('http://127.0.0.1/test_referer_equal',headers={'Referer':None}) 37 | assert r.status_code == 404 38 | assert r.headers.get('content-type') == 'text/html' 39 | assert 'hited' not in r.text 40 | self.check_ngx_stderr() 41 | 42 | def test_referer_not_equal(self): 43 | r = requests.get('http://127.0.0.1/test_referer_not_equal',headers={'Referer':'otherflag'}) 44 | assert r.status_code == 400 45 | assert r.headers.get('content-type') == 'text/html' 46 | assert 'hited' in r.text 47 | self.check_ngx_stderr() 48 | 49 | r = requests.get('http://127.0.0.1/test_referer_not_equal',headers={'Referer':'vntestflagz'}) 50 | assert r.status_code == 400 51 | assert r.headers.get('content-type') == 'text/html' 52 | assert 'hited' in r.text 53 | self.check_ngx_stderr() 54 | 55 | r = requests.get('http://127.0.0.1/test_referer_not_equal',headers={'Referer':''}) 56 | assert r.status_code == 400 57 | assert r.headers.get('content-type') == 'text/html' 58 | assert 'hited' in r.text 59 | self.check_ngx_stderr() 60 | 61 | r = requests.get('http://127.0.0.1/test_referer_not_equal',headers={'Referer':None}) 62 | assert r.status_code == 400 63 | assert r.headers.get('content-type') == 'text/html' 64 | assert 'hited' in r.text 65 | self.check_ngx_stderr() 66 | 67 | r = requests.get('http://127.0.0.1/test_referer_not_equal',headers={'Referer':'vntestflag'}) 68 | assert r.status_code == 404 69 | assert r.headers.get('content-type') == 'text/html' 70 | assert 'hited' not in r.text 71 | self.check_ngx_stderr() 72 | 73 | def test_referer_match(self): 74 | r = requests.get('http://127.0.0.1/test_referer_match',headers={'Referer':'aaatestbbb'}) 75 | assert r.status_code == 400 76 | assert r.headers.get('content-type') == 'text/html' 77 | assert 'hited' in r.text 78 | self.check_ngx_stderr() 79 | 80 | r = requests.get('http://127.0.0.1/test_referer_match',headers={'Referer':'aaaotherbbb'}) 81 | assert r.status_code == 404 82 | assert r.headers.get('content-type') == 'text/html' 83 | assert 'hited' not in r.text 84 | self.check_ngx_stderr() 85 | 86 | r = requests.get('http://127.0.0.1/test_referer_match',headers={'Referer':'aaazestbbb'}) 87 | assert r.status_code == 404 88 | assert r.headers.get('content-type') == 'text/html' 89 | assert 'hited' not in r.text 90 | self.check_ngx_stderr() 91 | 92 | r = requests.get('http://127.0.0.1/test_referer_match',headers={'Referer':''}) 93 | assert r.status_code == 404 94 | assert r.headers.get('content-type') == 'text/html' 95 | assert 'hited' not in r.text 96 | self.check_ngx_stderr() 97 | 98 | r = requests.get('http://127.0.0.1/test_referer_match',headers={'Referer':None}) 99 | assert r.status_code == 404 100 | assert r.headers.get('content-type') == 'text/html' 101 | assert 'hited' not in r.text 102 | self.check_ngx_stderr() 103 | 104 | def test_referer_not_match(self): 105 | r = requests.get('http://127.0.0.1/test_referer_not_match',headers={'Referer':'aaaotherbbb'}) 106 | assert r.status_code == 400 107 | assert r.headers.get('content-type') == 'text/html' 108 | assert 'hited' in r.text 109 | self.check_ngx_stderr() 110 | 111 | r = requests.get('http://127.0.0.1/test_referer_not_match',headers={'Referer':'aaazestbbb'}) 112 | assert r.status_code == 400 113 | assert r.headers.get('content-type') == 'text/html' 114 | assert 'hited' in r.text 115 | self.check_ngx_stderr() 116 | 117 | r = requests.get('http://127.0.0.1/test_referer_not_match',headers={'Referer':''}) 118 | assert r.status_code == 400 119 | assert r.headers.get('content-type') == 'text/html' 120 | assert 'hited' in r.text 121 | self.check_ngx_stderr() 122 | 123 | r = requests.get('http://127.0.0.1/test_referer_not_match',headers={'Referer':None}) 124 | assert r.status_code == 400 125 | assert r.headers.get('content-type') == 'text/html' 126 | assert 'hited' in r.text 127 | self.check_ngx_stderr() 128 | 129 | r = requests.get('http://127.0.0.1/test_referer_not_match',headers={'Referer':'aaatestbbb'}) 130 | assert r.status_code == 404 131 | assert r.headers.get('content-type') == 'text/html' 132 | assert 'hited' not in r.text 133 | self.check_ngx_stderr() 134 | 135 | def test_referer_existed(self): 136 | r = requests.get('http://127.0.0.1/test_referer_existed',headers={'Referer':'aaatestbbb'}) 137 | assert r.status_code == 400 138 | assert r.headers.get('content-type') == 'text/html' 139 | assert 'hited' in r.text 140 | self.check_ngx_stderr() 141 | 142 | r = requests.get('http://127.0.0.1/test_referer_existed',headers={'Referer':'aaaotherbbb'}) 143 | assert r.status_code == 400 144 | assert r.headers.get('content-type') == 'text/html' 145 | assert 'hited' in r.text 146 | self.check_ngx_stderr() 147 | 148 | r = requests.get('http://127.0.0.1/test_referer_existed',headers={'Referer':''}) 149 | assert r.status_code == 400 150 | assert r.headers.get('content-type') == 'text/html' 151 | assert 'hited' in r.text 152 | self.check_ngx_stderr() 153 | 154 | r = requests.get('http://127.0.0.1/test_referer_existed',headers={'Referer':None}) 155 | assert r.status_code == 404 156 | assert r.headers.get('content-type') == 'text/html' 157 | assert 'hited' not in r.text 158 | self.check_ngx_stderr() 159 | 160 | def test_referer_not_existed(self): 161 | r = requests.get('http://127.0.0.1/test_referer_not_existed',headers={'Referer':None}) 162 | assert r.status_code == 400 163 | assert r.headers.get('content-type') == 'text/html' 164 | assert 'hited' in r.text 165 | self.check_ngx_stderr() 166 | 167 | r = requests.get('http://127.0.0.1/test_referer_not_existed',headers={'Referer':'aaatestbbb'}) 168 | assert r.status_code == 404 169 | assert r.headers.get('content-type') == 'text/html' 170 | assert 'hited' not in r.text 171 | self.check_ngx_stderr() 172 | 173 | r = requests.get('http://127.0.0.1/test_referer_not_existed',headers={'Referer':'aaaotherbbb'}) 174 | assert r.status_code == 404 175 | assert r.headers.get('content-type') == 'text/html' 176 | assert 'hited' not in r.text 177 | self.check_ngx_stderr() 178 | 179 | r = requests.get('http://127.0.0.1/test_referer_not_existed',headers={'Referer':''}) 180 | assert r.status_code == 404 181 | assert r.headers.get('content-type') == 'text/html' 182 | assert 'hited' not in r.text 183 | self.check_ngx_stderr() 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /test/testcase/a_5_matcher_refer/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "redirect_rule":[], 3 | "filter_rule":[{ 4 | "enable":true, 5 | "code":"400", 6 | "response":"hit", 7 | "matcher":"referer_equal", 8 | "custom_response":true, 9 | "action":"block" 10 | },{ 11 | "enable":true, 12 | "code":"400", 13 | "response":"hit", 14 | "matcher":"referer_not_equal", 15 | "custom_response":true, 16 | "action":"block" 17 | },{ 18 | "enable":true, 19 | "code":"400", 20 | "response":"hit", 21 | "matcher":"referer_match", 22 | "custom_response":true, 23 | "action":"block" 24 | },{ 25 | "enable":true, 26 | "code":"400", 27 | "response":"hit", 28 | "matcher":"referer_not_match", 29 | "custom_response":true, 30 | "action":"block" 31 | },{ 32 | "enable":true, 33 | "code":"400", 34 | "response":"hit", 35 | "matcher":"referer_existed", 36 | "custom_response":true, 37 | "action":"block" 38 | },{ 39 | "enable":true, 40 | "code":"400", 41 | "response":"hit", 42 | "matcher":"referer_not_existed", 43 | "custom_response":true, 44 | "action":"block" 45 | }], 46 | "config_version":"0.36", 47 | "static_file_rule":[], 48 | "proxy_pass_rule":[], 49 | "summary_collect_rule":[], 50 | "summary_group_persistent_enable":true, 51 | "admin":[{ 52 | "user":"verynginx", 53 | "password":"verynginx", 54 | "enable":true 55 | }], 56 | "dashboard_host":"", 57 | "response":{ 58 | "hit":{ 59 | "content_type":"text/html", 60 | "body":"hited" 61 | }, 62 | "didn'thit":{ 63 | "content_type":"text/html", 64 | "body":"didn't hited" 65 | } 66 | }, 67 | "scheme_lock_enable":true, 68 | "uri_rewrite_rule":[], 69 | "cookie_prefix":"verynginx", 70 | "filter_enable":true, 71 | "browser_verify_enable":true, 72 | "scheme_lock_rule":[], 73 | "redirect_enable":true, 74 | "backend_upstream":{ 75 | }, 76 | "frequency_limit_enable":true, 77 | "matcher":{ 78 | "referer_equal":{ 79 | "URI":{ 80 | "value":"/test_referer_equal", 81 | "operator":"=" 82 | }, 83 | "Referer":{ 84 | "value":"vntestflag", 85 | "operator":"=" 86 | } 87 | }, 88 | "referer_not_existed":{ 89 | "URI":{ 90 | "value":"/test_referer_not_existed", 91 | "operator":"=" 92 | }, 93 | "Referer":{ 94 | "operator":"!Exist" 95 | } 96 | }, 97 | "referer_existed":{ 98 | "URI":{ 99 | "value":"/test_referer_existed", 100 | "operator":"=" 101 | }, 102 | "Referer":{ 103 | "operator":"Exist" 104 | } 105 | }, 106 | "all_request":{ 107 | }, 108 | "referer_match":{ 109 | "URI":{ 110 | "value":"/test_referer_match", 111 | "operator":"=" 112 | }, 113 | "Referer":{ 114 | "value":".*test.*", 115 | "operator":"≈" 116 | } 117 | }, 118 | "referer_not_equal":{ 119 | "URI":{ 120 | "value":"/test_referer_not_equal", 121 | "operator":"=" 122 | }, 123 | "Referer":{ 124 | "value":"vntestflag", 125 | "operator":"!=" 126 | } 127 | }, 128 | "referer_not_match":{ 129 | "URI":{ 130 | "value":"/test_referer_not_match", 131 | "operator":"=" 132 | }, 133 | "Referer":{ 134 | "value":".*test.*", 135 | "operator":"!≈" 136 | } 137 | } 138 | }, 139 | "static_file_enable":true, 140 | "summary_temporary_period":60, 141 | "summary_group_temporary_enable":true, 142 | "summary_with_host":false, 143 | "frequency_limit_rule":[], 144 | "uri_rewrite_enable":true, 145 | "base_uri":"/verynginx", 146 | "summary_request_enable":true, 147 | "proxy_pass_enable":true, 148 | "readonly":false, 149 | "browser_verify_rule":[] 150 | } -------------------------------------------------------------------------------- /verynginx/configs/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file !.gitignore 4 | -------------------------------------------------------------------------------- /verynginx/dashboard/css/bs_callout.css: -------------------------------------------------------------------------------- 1 | .bs-callout { 2 | padding: 20px; 3 | margin: 20px 0; 4 | border: 1px solid #eee; 5 | border-left-width: 5px; 6 | border-radius: 3px; 7 | } 8 | .bs-callout h4 { 9 | margin-top: 0; 10 | margin-bottom: 5px; 11 | } 12 | .bs-callout p:last-child { 13 | margin-bottom: 0; 14 | } 15 | .bs-callout code { 16 | border-radius: 3px; 17 | } 18 | .bs-callout+.bs-callout { 19 | margin-top: -5px; 20 | } 21 | .bs-callout-default { 22 | border-left-color: #777; 23 | } 24 | .bs-callout-default h4 { 25 | color: #777; 26 | } 27 | .bs-callout-primary { 28 | border-left-color: #428bca; 29 | } 30 | .bs-callout-primary h4 { 31 | color: #428bca; 32 | } 33 | .bs-callout-success { 34 | border-left-color: #5cb85c; 35 | } 36 | .bs-callout-success h4 { 37 | color: #5cb85c; 38 | } 39 | .bs-callout-danger { 40 | border-left-color: #d9534f; 41 | } 42 | .bs-callout-danger h4 { 43 | color: #d9534f; 44 | } 45 | .bs-callout-warning { 46 | border-left-color: #f0ad4e; 47 | } 48 | .bs-callout-warning h4 { 49 | color: #f0ad4e; 50 | } 51 | .bs-callout-info { 52 | border-left-color: #5bc0de; 53 | } 54 | .bs-callout-info h4 { 55 | color: #5bc0de; 56 | } 57 | -------------------------------------------------------------------------------- /verynginx/dashboard/css/pace.css: -------------------------------------------------------------------------------- 1 | .pace { 2 | -webkit-pointer-events: none; 3 | pointer-events: none; 4 | 5 | -webkit-user-select: none; 6 | -moz-user-select: none; 7 | user-select: none; 8 | } 9 | 10 | .pace-inactive { 11 | display: none; 12 | } 13 | 14 | .pace .pace-progress { 15 | background: #29d; 16 | position: fixed; 17 | z-index: 2000; 18 | top: 0; 19 | right: 100%; 20 | width: 100%; 21 | height: 2px; 22 | } 23 | -------------------------------------------------------------------------------- /verynginx/dashboard/css/tables.css: -------------------------------------------------------------------------------- 1 | .table-bordered>tbody>tr>td, 2 | .table-bordered>tbody>tr>th, 3 | .table-bordered>tfoot>tr>td, 4 | .table-bordered>tfoot>tr>th, 5 | .table-bordered>thead>tr>td { 6 | border-bottom: none; 7 | border-right: none; 8 | } 9 | 10 | #page_summary { 11 | padding-left: 10px; 12 | padding-right: 10px; 13 | } 14 | .dataTables_wrapper .dataTables_info { 15 | color: #AAA; 16 | } 17 | 18 | #summary_title { 19 | font-size:1.5em; 20 | font-weight:bold; 21 | } 22 | 23 | #summary_type_note { 24 | color:#AAA; 25 | } 26 | 27 | #summary_action_bar { 28 | margin-top:10px; 29 | margin-bottom:10px; 30 | } 31 | 32 | #summary_filter_label { 33 | display:inline-block; 34 | font-size:1.3em; 35 | margin-right:10px; 36 | } 37 | 38 | #summary_filter_input { 39 | display:inline-block; 40 | width:200px; 41 | } 42 | 43 | #summary_control_label { 44 | display:inline-block; 45 | font-size:1.3em; 46 | margin-right:10px; 47 | } 48 | 49 | #summary_unmatched_table_filter { 50 | display:none; 51 | } 52 | 53 | #summary_matched_table_filter { 54 | display:none; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /verynginx/dashboard/css/webInterface.css: -------------------------------------------------------------------------------- 1 | .btn_up,.btn_down{ 2 | color:#337CB3; 3 | } 4 | .btn_edit{ 5 | color:#337CB3; 6 | } 7 | .btn_del{ 8 | color:#337CB3; 9 | } 10 | 11 | .leftnav { 12 | color:#777; 13 | } 14 | 15 | .leftnav:hover { 16 | color:#000; 17 | } 18 | 19 | .leftnav_1 { 20 | font-size:1.1em; 21 | padding-top:5px; 22 | padding-bottom:5px; 23 | } 24 | 25 | .leftnav_1.active { 26 | font-weight:bold; 27 | color:#333; 28 | } 29 | 30 | .leftnav_2 { 31 | padding-left:20px; 32 | color:#888; 33 | } 34 | 35 | .leftnav_2.active { 36 | font-weight:bold; 37 | border-left:solid; 38 | border-width:2px; 39 | border-color:#4078c0; 40 | color:#4078c0; 41 | } 42 | 43 | .config_title { 44 | font-size:2em; 45 | font-weight:bold; 46 | margin-bottom:10px; 47 | } 48 | 49 | .config_title_2 { 50 | font-size:1.2em; 51 | font-weight:bold; 52 | margin-bottom:10px; 53 | } 54 | 55 | .config_sub_title { 56 | font-size:1.2em; 57 | font-weight:bold; 58 | margin-top:15px; 59 | margin-bottom:10px; 60 | } 61 | 62 | .config_table thead{ 63 | font-weight:bold; 64 | } 65 | 66 | .config_table > tbody > tr > td:first-child{ 67 | font-weight:bold; 68 | color:#666; 69 | } 70 | 71 | .config_matcher_block{ 72 | border-width: 1px; 73 | border-style:solid; 74 | border-radius: 5px; 75 | padding-right:5px; 76 | margin-top:5px; 77 | margin-bottom:5px; 78 | margin-right:5px; 79 | margin-left:5px; 80 | border-color:#CCC; 81 | background-color:#F5F5F5; 82 | position:relative; 83 | display:inline-block; 84 | } 85 | 86 | .config_matcher_block_type{ 87 | padding-left:5px; 88 | padding-right:3px; 89 | font-weight:bold; 90 | display:inline-block; 91 | } 92 | 93 | .config_matcher_block_operator{ 94 | font-weight:bold; 95 | color:#999; 96 | font-family:Times; 97 | } 98 | 99 | .config_matcher_block_value{ 100 | color: #555; 101 | word-break: break-word; 102 | } 103 | 104 | .config_matcher_block_btn_delete{ 105 | float:right; 106 | margin-left:5px; 107 | margin-top: 2px; 108 | margin-right: 2px; 109 | color: #666; 110 | } 111 | 112 | .config_node_block_weight{ 113 | color: #888; 114 | } 115 | 116 | .matched { 117 | font-weight:bold; 118 | color:#337AB7; 119 | } 120 | 121 | .config_test_container { 122 | padding-left:0; 123 | padding-right:0; 124 | margin-bottom:15px; 125 | } 126 | 127 | .config_test_output { 128 | font-size:1.4em; 129 | } 130 | 131 | .config_test_sub_output { 132 | font-size:1.2em; 133 | color:#BBB; 134 | } 135 | 136 | .config_form_line{ 137 | padding-top:5px; 138 | } 139 | 140 | .config_card{ 141 | background-color:#F0F0F0; 142 | border:solid; 143 | border-width:1px ; 144 | border-radius:3px; 145 | border-color:#CCC; 146 | display:inline-block; 147 | margin-left:5px; 148 | margin-right:5px; 149 | padding-left:3px; 150 | padding-right:3px; 151 | } 152 | 153 | .editing { 154 | background-color:#EEE; 155 | } 156 | 157 | .no_break { 158 | white-space: nowrap; 159 | } 160 | 161 | .monitor_container{ 162 | position:relative; 163 | } 164 | 165 | .status_info_title{ 166 | font-size:1.2em; 167 | } 168 | 169 | .status_info_value{ 170 | font-size:1.2em; 171 | color:#AAA; 172 | margin-left:20px; 173 | font-weight:bold; 174 | } 175 | 176 | .status_group_title { 177 | font-size:1.5em; 178 | font-weight: bold; 179 | margin-bottom:12px; 180 | display:inline-block; 181 | } 182 | 183 | .status_group_title_desc { 184 | font-weight:bold; 185 | display:inline-block; 186 | color:#BBB; 187 | } 188 | 189 | .status_sub_group_title { 190 | font-size:1.2em; 191 | font-weight: bold; 192 | margin-top:8px; 193 | margin-bottom:8px; 194 | display:inline-block; 195 | } 196 | 197 | .status_sub_group_title_desc { 198 | font-weight:bold; 199 | color:#BBB; 200 | display:inline-block; 201 | } 202 | 203 | .status_color_mark { 204 | width:14px; 205 | height:14px; 206 | display:inline-block; 207 | border-radius:3px; 208 | text-align:center; 209 | } 210 | 211 | .status_color_desc { 212 | color:#999; 213 | } 214 | 215 | .vn_modal_mid { 216 | width:400px ; 217 | } 218 | 219 | .vn-config-box { 220 | max-width:700px; 221 | margin-top:15px; 222 | } 223 | 224 | .vn-config-label { 225 | text-align:right; 226 | margin-top:5px; 227 | } 228 | 229 | .tips_container { 230 | margin-right:5px; 231 | padding:5px; 232 | border:solid; 233 | border-color:lightgray; 234 | border-width:1px; 235 | border-radius:5px; 236 | margin-bottom:20px; 237 | } 238 | 239 | .tips_tips { 240 | color:#BBB; 241 | font-size:0.9em; 242 | } 243 | 244 | .tips_title { 245 | font-size:0.9em; 246 | font-weight:bold; 247 | } 248 | 249 | .tips_content { 250 | font-size:0.8em; 251 | font-color:gray; 252 | } 253 | 254 | .tips_content ul { 255 | padding-left:20px; 256 | } 257 | 258 | #config_bottom_bar { 259 | position:fixed; 260 | bottom:0px; 261 | background:rgba(0,0,0,0.6); 262 | color:#fff; 263 | width:100%; 264 | height:45px; 265 | } 266 | 267 | #config_bottom_bar_message { 268 | display:inline-block; 269 | font-size:1.2em; 270 | margin-left:10px; 271 | margin-top:10px; 272 | font-weight:lighter; 273 | } 274 | 275 | #config_bottom_bar_btns { 276 | display:inline-block; 277 | margin-right:20px; 278 | margin-top:7px; 279 | } 280 | 281 | .bottom_bar_btn { 282 | border-color:#fff; 283 | background-color:rgba(0,0,0,0); 284 | color:#fff; 285 | } 286 | 287 | .bottom_bar_btn_save { 288 | font-weight:bold; 289 | } 290 | 291 | .notice{ 292 | font-size:1.2em; 293 | border-style:solid; 294 | border-left-width:3px; 295 | border-right-width:1px; 296 | border-top-width:1px; 297 | border-bottom-width:1px; 298 | padding:5px; 299 | border-color:#ddd; 300 | color:#999; 301 | border-radius:3px; 302 | margin-top:15px; 303 | } 304 | 305 | .editing_rule_name{ 306 | font-weight:bold; 307 | color:#333; 308 | } 309 | 310 | #interface_login { 311 | width:500px; 312 | margin-left:auto; 313 | margin-right:auto; 314 | margin-top:50px; 315 | } 316 | 317 | #about_title { 318 | font-size:4em; 319 | color:#CCC; 320 | } 321 | 322 | #about_sub_title { 323 | font-size:1.5em; 324 | color:#333; 325 | } 326 | 327 | #about_sub_btn_container { 328 | text-align:center; 329 | margin-top:30px 330 | } 331 | 332 | #about_sub_btn { 333 | float: none; 334 | margin: 0 auto; 335 | font-size: 1.8em; 336 | color: #999; 337 | font-weight: lighter; 338 | border: solid; 339 | border-width: 1px; 340 | padding-top: 5px; 341 | padding-bottom: 5px; 342 | padding-left: 40px; 343 | padding-right: 40px; 344 | border-radius: 5px; 345 | transition: all 0.5s; 346 | } 347 | 348 | #about_sub_btn:hover { 349 | color:#337AB7; 350 | border-color:#337AB7; 351 | text-decoration:none; 352 | } 353 | 354 | .summary_url_table th { 355 | font-size: 14px; 356 | font-weight: normal; 357 | color: #777; 358 | } 359 | 360 | .summary_url_table td { 361 | word-break: break-word; 362 | } 363 | 364 | .summary_sub_title { 365 | font-weight: bold; 366 | margin-bottom: 5px; 367 | color: #555; 368 | } 369 | 370 | .vn-has-error { 371 | border-color: #a94442; 372 | } 373 | 374 | .vn-has-error:focus { 375 | border-color: #a94442; 376 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; 377 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483 378 | } 379 | 380 | .vn_summary_detail_btn { 381 | border-color:#bbb; 382 | color:#bbb; 383 | background-color:#fff; 384 | } 385 | 386 | .vn_summary_detail_btn:hover { 387 | border-color:#4078c0; 388 | color:#4078c0; 389 | background-color:#fff; 390 | } 391 | 392 | .vn_summary_detail_popover_table { 393 | } 394 | 395 | .vn_summary_detail_popover_table td { 396 | padding-right:8px; 397 | } 398 | 399 | .vn_summary_detail_popover_count { 400 | color:#666; 401 | font-weight:bold; 402 | } 403 | 404 | .vn_summary_detail_popover_rate { 405 | color:#888; 406 | } 407 | 408 | #config_modal_matcher_input_group .input_group_container{ 409 | margin-top:10px; 410 | margin-bottom:10px; 411 | padding:5px; 412 | border:solid; 413 | border-width:1px; 414 | border-color:#4078c0; 415 | border-radius:10px; 416 | } 417 | 418 | #config_modal_matcher_input_group .input_group_title{ 419 | font-weight: bold; 420 | padding-left: 30px; 421 | padding-bottom: 5px; 422 | margin-top: -15px; 423 | color:#4078c0; 424 | } 425 | 426 | #config_modal_matcher_input_group .input_item{ 427 | margin-bottom:10px; 428 | } 429 | 430 | #config_modal_matcher_input_group .input_item_title{ 431 | font-weight:bold; 432 | text-align:right; 433 | } 434 | 435 | #config_modal_matcher_input_group .input_item_inputer{ 436 | } 437 | -------------------------------------------------------------------------------- /verynginx/dashboard/js/config.js: -------------------------------------------------------------------------------- 1 | var config = new Object(); 2 | 3 | Vue.config.debug = true; 4 | config.config_vm = null; 5 | config.verynginx_config = {}; 6 | 7 | config.original_config_json = null; 8 | 9 | //the reusable matcher condition template 10 | config.vue_component_condition = Vue.extend({ 11 | props : ['matcher_conditions','del_action'], 12 | template: '<template v-for="(condition_name,condition_value) in matcher_conditions | orderBy \'v\'">\ 13 | <div class="config_matcher_block">\ 14 | <span class="glyphicon glyphicon-remove config_matcher_block_btn_delete" v-if="(del_action != null)" onclick="{{del_action}}"></span>\ 15 | <span class="config_matcher_block_type">{{condition_name}}</span>\ 16 | <span class="config_matcher_block_name">\ 17 | <template v-if="(condition_value.name_operator != null)">[ name\ 18 | {{ condition_value.name_operator}}\ 19 | <template v-if="(condition_value.name_value != null)">\ 20 | {{condition_value.name_value }}\ 21 | </template>\ 22 | ]</template>\ 23 | </span>\ 24 | <span class="config_matcher_block_operator">{{condition_value.operator | show_operator}}</span>\ 25 | <span class="config_matcher_block_value" >{{condition_value.value}}</span>\ 26 | </div>\ 27 | </template>' 28 | }); 29 | 30 | config.vue_upstream_node = Vue.extend({ 31 | props : ['node','del_action'], 32 | template: '<template v-for="(node_name,node_value) in node ">\ 33 | <div class="config_matcher_block">\ 34 | <span class="glyphicon glyphicon-remove config_matcher_block_btn_delete" v-if="(del_action != null)" onclick="{{del_action}}"></span>\ 35 | <span class="config_matcher_block_type">{{node_name}}</span>\ 36 | <span class="config_matcher_block_name">\ 37 | {{node_value.scheme}}://{{node_value.host}}<template v-if="(node_value.port.length != 0)">:{{node_value.port}};</template>\ 38 | </span>\ 39 | <span class="config_node_block_weight"> weight:{{node_value.weight}}</span>\ 40 | </div>\ 41 | </template>' 42 | }); 43 | 44 | Vue.component('condition', config.vue_component_condition ); 45 | Vue.component('upstream', config.vue_upstream_node ); 46 | 47 | Vue.filter('show_operator', function (operator) { 48 | return operator; 49 | }); 50 | 51 | config.config_changed = function(){ 52 | 53 | if( config.config_vm == null ) 54 | return false; 55 | 56 | var original_config = JSON.parse( config.original_config_json ); 57 | var new_config = JSON.parse( config.config_vm.all_config_json ); 58 | 59 | if( _.isEqual( original_config, new_config ) == true ) 60 | return false; 61 | else 62 | return true; 63 | } 64 | 65 | config.refresh_bottom_bar = function(){ 66 | 67 | if( config.config_changed() ){ 68 | $('#config_bottom_div').show(); 69 | }else{ 70 | $('#config_bottom_div').hide(); 71 | } 72 | }; 73 | 74 | config.reset_input_form = function(){ 75 | util.reset_input_area('.config_form') 76 | } 77 | 78 | config.get_config = function(){ 79 | $.get("./config",function(data,status){ 80 | config.original_config_json = JSON.stringify( data , null, 2); 81 | config.verynginx_config = data; 82 | 83 | if( config.config_vm != null ){ 84 | config.config_vm.$set( 'config_now', data); 85 | dashboard.notify("Reread config success"); 86 | return; 87 | } 88 | 89 | config.config_vm = new Vue({ 90 | el: '#verynginx_config', 91 | data: { 92 | 'config_now':config.verynginx_config, 93 | 'editor':{} 94 | }, 95 | computed : { 96 | all_config_json: function(){ 97 | return JSON.stringify( this.config_now , null, 2); 98 | } 99 | }, 100 | ready: function(){ 101 | this.$nextTick( config.reset_input_form ); 102 | } 103 | }); 104 | 105 | config.config_vm.$watch('all_config_json',config.refresh_bottom_bar); 106 | }); 107 | } 108 | 109 | 110 | config.save_config = function(){ 111 | console.log("save_config"); 112 | var config_json = JSON.stringify( config.config_vm.$data['config_now'] , null, 2); 113 | 114 | //step 1, use encodeURIComponent to escape special char 115 | var config_json_escaped = window.encodeURIComponent( config_json ); 116 | //step 2, use base64 to encode data to avoid be blocked by verynginx args filter 117 | var config_json_escaped_base64 = window.btoa( config_json_escaped ); 118 | 119 | $.post("./config",{ config:config_json_escaped_base64 },function(data){ 120 | console.log(data); 121 | if( data['ret'] == 'success' ){ 122 | config.original_config_json = config.config_vm.all_config_json; 123 | config.refresh_bottom_bar(); 124 | dashboard.notify("Save config success."); 125 | }else{ 126 | dashboard.show_notice( 'warning', "Save config failed [" + data['err'] + "]."); 127 | } 128 | }); 129 | } 130 | 131 | //modify: give group, index ,value 132 | //delete: let value = null 133 | //add: let index == null 134 | config.config_mod = function(rule_group_name,index,value){ 135 | 136 | if( index == null && value == null ){ 137 | //is a error call 138 | return; 139 | } 140 | 141 | console.log('-->',rule_group_name,index,value); 142 | if( value == null ){ 143 | if( typeof index == 'string' ){ 144 | Vue.delete( config.verynginx_config[rule_group_name], index ); 145 | }else{ 146 | config.verynginx_config[rule_group_name].splice( index, 1 ); 147 | } 148 | }else{ 149 | if( index == undefined || index == null ){ 150 | config.verynginx_config[rule_group_name].push(value); 151 | }else if( typeof index == 'string' ){ 152 | Vue.set( config.verynginx_config[rule_group_name], index ,value ); 153 | }else{ 154 | config.verynginx_config[rule_group_name].$set( index, value ); 155 | } 156 | } 157 | } 158 | 159 | 160 | config.config_move_up = function(rule_group_name,index){ 161 | 162 | if(index == 0){ 163 | dashboard.notify("The item already at the first"); 164 | return; 165 | } 166 | 167 | var tmp = config.verynginx_config[rule_group_name][index-1]; 168 | config.verynginx_config[rule_group_name].$set(index-1, config.verynginx_config[rule_group_name][index]); 169 | config.verynginx_config[rule_group_name].$set(index, tmp); 170 | } 171 | 172 | config.config_move_down = function(rule_group_name,index){ 173 | if(index >= config.verynginx_config[rule_group_name].length - 1){ 174 | dashboard.notify("The item already at the bottom"); 175 | return; 176 | } 177 | 178 | var tmp = config.verynginx_config[rule_group_name][index+1]; 179 | config.verynginx_config[rule_group_name].$set(index+1, config.verynginx_config[rule_group_name][index]); 180 | config.verynginx_config[rule_group_name].$set(index, tmp); 181 | } 182 | 183 | 184 | config.edit_flag_set = function( group, flag ){ 185 | var config_group = config.verynginx_config[ group ]; 186 | config_group = JSON.parse( JSON.stringify(config_group) ); 187 | 188 | if( flag != null ){ 189 | Object.defineProperty( config_group , "_editing", { value : flag, enumerable:false, writable:true }); 190 | } 191 | //reset data to refresh the view 192 | config.config_vm.$set( 'config_now.' + group, config_group ); 193 | } 194 | 195 | //set a rule to edit status and fill data of the rule into editor form 196 | //default: include_key == undefined 197 | config.config_edit_begin = function( rule_group_name, index, form_id, index_key_name ){ 198 | var config_group = config.verynginx_config[ rule_group_name ]; 199 | var data = util.clone( config_group[index] ); 200 | if( index_key_name != undefined ){ 201 | data[index_key_name] = index; 202 | } 203 | config.edit_flag_set( rule_group_name, index ); 204 | vnform.set_data( form_id, data ); 205 | } 206 | 207 | config.config_edit_save = function( rule_group_name, form_id , index_key_name ){ 208 | var editing = config.verynginx_config[rule_group_name]._editing; 209 | var err_msg = vnform.verify_form( form_id ); 210 | if( err_msg != null ){ 211 | dashboard.show_notice('warning', err_msg ); 212 | return; 213 | } 214 | 215 | var value = vnform.get_data( form_id ); 216 | if( editing != undefined ){ 217 | config.verynginx_config[rule_group_name]._editing = null; 218 | } 219 | 220 | if( index_key_name != undefined){ 221 | editing = value[index_key_name]; 222 | delete value[index_key_name]; 223 | } 224 | 225 | config.config_mod( rule_group_name, editing, value ); 226 | config.edit_flag_set( rule_group_name, null ); 227 | vnform.reset( form_id ); 228 | } 229 | 230 | config.config_edit_cacel = function( rule_group_name, form_id ){ 231 | config.edit_flag_set( rule_group_name, null ); 232 | 233 | if( form_id != undefined ){ 234 | vnform.reset( form_id ); 235 | } 236 | } 237 | 238 | 239 | //for matcher only 240 | config.config_matcher_delete_condition = function( matcher_name, condition_name ){ 241 | Vue.delete( config.verynginx_config['matcher'][matcher_name], condition_name ); 242 | } 243 | 244 | //add the content of matcher editor to global config 245 | config.config_matcher_add = function(){ 246 | var matcher_name = matcher_editor.matcher_name(); 247 | 248 | if( matcher_name == '' ){ 249 | dashboard.notify('Name of the matcher mush not be empty'); 250 | return; 251 | } 252 | 253 | if( matcher_name.substring(0,1) == '_' ){ 254 | dashboard.notify('Name of the matcher must not started with "_"'); 255 | return; 256 | } 257 | 258 | if( config.verynginx_config['matcher'][matcher_name] != null ){ 259 | dashboard.notify('Matcher [' + matcher_name + '] already existed'); 260 | return; 261 | } 262 | 263 | Vue.set( config.verynginx_config['matcher'], matcher_name ,matcher_editor.tmp_conditions ); 264 | matcher_editor.clear(); 265 | } 266 | 267 | 268 | 269 | 270 | config.test_match_factory = function( type ){ 271 | 272 | var match_core = function(){ 273 | 274 | var target_str = $(this).val(); 275 | var test_container = $(this).closest('.config_test_container'); 276 | var rule_table_id = test_container.attr('test_rule_table'); 277 | var rule_table = $('#' + rule_table_id); 278 | var test_args = eval(test_container.attr('test_args')); 279 | var test_output = test_container.find('.config_test_output'); 280 | var test_sub_output = test_container.find('.config_test_sub_output'); 281 | 282 | var rows = rule_table.find('tbody > tr'); 283 | var matched_count = 0; 284 | 285 | test_output.text(''); 286 | test_sub_output.text(''); 287 | for( i=0; i<rows.length; i++ ){ 288 | $( rows[i] ).removeClass('matched'); 289 | 290 | if( type == 're' ){ 291 | var re_str = $($(rows[i]).children()[test_args[0]]).text(); 292 | var re_obj = new RegExp(re_str, 'igm' ); 293 | 294 | if( target_str.match(re_obj) != null ){ 295 | $( rows[i] ).addClass('matched'); 296 | matched_count += 1; 297 | } 298 | } 299 | 300 | if( type == 're_replace' ){ 301 | var re_str = $($(rows[i]).children()[test_args[0]]).text(); 302 | var replace_str = $($(rows[i]).children()[test_args[1]]).text(); 303 | var re_obj = new RegExp(re_str, 'igm' ); 304 | 305 | if( target_str.match(re_obj) != null ){ 306 | $( rows[i] ).addClass('matched'); 307 | matched_count += 1; 308 | 309 | if( test_sub_output.text() == ''){ 310 | test_sub_output.text( 'will be redirect to: ' + target_str.replace( re_obj, replace_str ) ); 311 | } 312 | } 313 | } 314 | 315 | if( type == 'equal' ){ 316 | var re_str = $($(rows[i]).children()[test_args[0]]).text(); 317 | 318 | if( target_str == re_str ){ 319 | $( rows[i] ).addClass('matched'); 320 | matched_count += 1; 321 | } 322 | } 323 | 324 | } 325 | 326 | if( target_str == '' && matched_count == 0 ){ 327 | test_output.text(''); 328 | }else{ 329 | test_output.text( matched_count + ' rule matched '); 330 | } 331 | }; 332 | 333 | return match_core; 334 | } 335 | 336 | 337 | 338 | -------------------------------------------------------------------------------- /verynginx/dashboard/js/dashboard.js: -------------------------------------------------------------------------------- 1 | var dashboard = new Object(); 2 | 3 | dashboard.version = '0.3'; 4 | dashboard.disable_log = false; 5 | dashboard.last_failed_jqxhr; 6 | 7 | paceOptions = { 8 | catchupTime: 100, 9 | minTime: 200, 10 | restartOnRequestAfter: -1, 11 | ajax :{ 12 | trackMethods: ['GET','POST'], 13 | ignoreURLs: ['./status'] 14 | } 15 | }; 16 | 17 | dashboard.init = function(){ 18 | 19 | if( dashboard.disable_log == true ){ 20 | window.console={log:function(){}}; 21 | } 22 | 23 | dashboard.switch_to_interface('login'); 24 | $(".init_click").click(); 25 | 26 | $(".btn").mouseup(function(){ 27 | $(this).blur(); 28 | }); 29 | 30 | // Reposition when a modal is shown 31 | $('.modal').on('show.bs.modal', dashboard.modal_reposition); 32 | // Reposition when the window is resized 33 | $(window).on('resize', function() { 34 | $('.modal:visible').each(dashboard.modal_reposition ); 35 | }); 36 | 37 | if( localStorage.dashboard_status_enable_animation == undefined ){ 38 | localStorage.dashboard_status_enable_animation = "true"; 39 | } 40 | 41 | if( localStorage.dashboard_status_refresh_interval == undefined ){ 42 | localStorage.dashboard_status_refresh_interval = '3'; 43 | } 44 | 45 | //add event listener for input event on rule test form 46 | $(".config_test_container").each(function(){ 47 | var test_action = eval( $(this).attr('test_action') ) ; 48 | var form_input = $(this).find(".config_test_input"); 49 | 50 | form_input.on( 'input',test_action ); 51 | }); 52 | 53 | $( document ).ajaxError( dashboard.handle_ajax_error ); 54 | 55 | //init the small vue vm at first 56 | matcher_editor.init(); 57 | upstream_editor.init(); 58 | window.onbeforeunload = dashboard.check_saved; 59 | 60 | dashboard.try_recover_login(); 61 | } 62 | 63 | //if already login( with cookie saved ), let judge it and auto jump to dashboard 64 | dashboard.try_recover_login = function(){ 65 | $.ajax({ 66 | url: "./status", 67 | type: "get", 68 | success: dashboard.start, 69 | beforeSend: util.mark_ajax_slince 70 | }); 71 | } 72 | 73 | dashboard.start = function(){ 74 | dashboard.switch_to_interface('dashboard'); 75 | config.get_config(); 76 | dashboard.notify("Login Success"); 77 | window.setTimeout( monitor.build_chart, 0 ); 78 | window.setTimeout( monitor.start, 0 ); 79 | } 80 | 81 | dashboard.login = function(){ 82 | function login_success(data,status){ 83 | var uri = document.location.pathname; 84 | var path = uri.substring(0, uri.lastIndexOf('/') ); 85 | 86 | for( name in data['cookies'] ){ 87 | $.cookie( name, data['cookies'][name],{ path: path} ); 88 | } 89 | dashboard.start(); 90 | } 91 | 92 | $.post("./login", data=vnform.get_data('login_form'),success=login_success); 93 | } 94 | 95 | dashboard.logout = function(){ 96 | monitor.stop(); 97 | $.cookie( 'verynginx_user', null,{ path: '/verynginx'} ); 98 | $.cookie( 'verynginx_session', null, { path: '/verynginx'} ); 99 | location.reload(); 100 | } 101 | 102 | dashboard.check_saved = function(){ 103 | 104 | if( config.config_changed() ) 105 | return "Configs hasn't been saved. If you leave, then the new configuration will be lost."; 106 | 107 | return null; 108 | } 109 | 110 | dashboard.switch_to_interface = function( name ){ 111 | $(".interface").hide(); 112 | $("#interface_"+name).show(); 113 | } 114 | 115 | dashboard.switch_to_page = function( page ){ 116 | $(".page").hide(); 117 | $("#page_"+page).show(); 118 | 119 | $(".topnav").removeClass("active"); 120 | $("#topnav_"+page).addClass("active"); 121 | 122 | monitor.update_config(); 123 | 124 | //if switch to summary page, make sure has a summary table 125 | if( page == "summary" ){ 126 | data_stat.make_sure_have_table(); 127 | }else if( page == "status" ){ 128 | //generate a resize event to work around chart disapple bug of chart.js 129 | window.setTimeout( function(){ 130 | window.dispatchEvent(new Event('resize')); 131 | }, 200); 132 | } 133 | } 134 | 135 | dashboard.switch_config_nav_group = function( item ){ 136 | 137 | var group_name = $(item).attr("group"); 138 | $(".leftnav_group").hide(); 139 | $(".leftnav_1").removeClass('active'); 140 | $(item).addClass('active'); 141 | 142 | var config_group_container = $(".leftnav_group[group=" + group_name + "]" ); 143 | config_group_container.show(); 144 | 145 | //switch to first children config page 146 | $(".leftnav_group[group=" + group_name + "]" ).children()[0].click(); 147 | } 148 | 149 | dashboard.switch_to_config = function( item ){ 150 | var config_name = $(item).attr("config_name"); 151 | $(".config_container").hide(); 152 | $("#config_" + config_name ).show(); 153 | 154 | $(".leftnav_2").removeClass('active'); 155 | $(item).addClass('active'); 156 | 157 | //show tips of the config 158 | tips.show_tips(config_name); 159 | } 160 | 161 | dashboard.switch_tab_bar = function( group, tag ){ 162 | $(".nav [group=" + group + "]").removeClass('active'); 163 | $(".nav [group=" + group + "][tag=" + tag + "]").addClass('active'); 164 | 165 | $(".nav_box[group=" + group + "]").hide(); 166 | $(".nav_box[group=" + group + "][tag=" + tag + "]").show(); 167 | } 168 | 169 | dashboard.nav_tab_click = function( item ){ 170 | var group = $(item).attr('group'); 171 | var tag = $(item).attr('tag'); 172 | 173 | dashboard.switch_tab_bar( group, tag ); 174 | } 175 | 176 | 177 | dashboard.show_notice = function( type, message ){ 178 | $.smkAlert({ 179 | text: message, 180 | type: type, 181 | position:"top-right", 182 | time:5, 183 | }); 184 | } 185 | 186 | dashboard.notify = function(message){ 187 | $.smkAlert({ 188 | text: message, 189 | type: 'info', 190 | position:"top-right", 191 | time:5, 192 | }); 193 | } 194 | 195 | dashboard.open_modal_dashboard_config = function(){ 196 | //load status dashboard config 197 | $('#status_config_modal').modal('show'); 198 | 199 | var enable_animation = localStorage.dashboard_status_enable_animation; 200 | var refresh_interval = localStorage.dashboard_status_refresh_interval; 201 | 202 | if( enable_animation != undefined ){ 203 | if( enable_animation == "false" ){ 204 | enable_animation = false; 205 | }else{ 206 | enable_animation = true; 207 | } 208 | $('#status_config_modal [name=enable_animation]')[0].checked = enable_animation; 209 | } 210 | 211 | if( refresh_interval != undefined ){ 212 | $('#status_config_modal [name=refresh_interval]').val( refresh_interval ); 213 | } 214 | 215 | dashboard.status_dashboard_update_interval_label(); 216 | } 217 | 218 | dashboard.save_status_dashboard_config = function(){ 219 | 220 | var enable_animation = $('#status_config_modal [name=enable_animation]')[0].checked; 221 | var refresh_interval = $('#status_config_modal [name=refresh_interval]').val(); 222 | 223 | localStorage.dashboard_status_enable_animation = enable_animation; 224 | localStorage.dashboard_status_refresh_interval = refresh_interval; 225 | 226 | $('#status_config_modal').modal('hide'); 227 | monitor.update_config(); 228 | } 229 | 230 | dashboard.status_dashboard_update_interval_label = function(){ 231 | 232 | var refresh_interval = $('#status_config_modal [name=refresh_interval]').val(); 233 | $('#status_config_modal [name=refresh_interval_label]').text(refresh_interval + "s"); 234 | } 235 | 236 | 237 | dashboard.handle_ajax_error = function( e,jqxhr ) { 238 | console.log('ajax_err_handle:',e,jqxhr); 239 | var err = ''; 240 | dashboard.last_failed_jqxhr = jqxhr; 241 | 242 | if( jqxhr.slince == true ){ 243 | return; 244 | } 245 | 246 | if( jqxhr.status != 0 ){ 247 | if( jqxhr.status == 400 && jqxhr.responseJSON != null) { 248 | err = jqxhr.responseJSON['message']; 249 | }else{ 250 | err = 'Ajax request failed[status code = ' + jqxhr.status + ']'; 251 | } 252 | }else{ 253 | err = 'Ajax request failed[Network error]'; 254 | } 255 | 256 | dashboard.show_notice( 'warning', err); 257 | } 258 | 259 | /** 260 | * Vertically center Bootstrap 3 modals so they aren't always stuck at the top 261 | */ 262 | 263 | dashboard.modal_reposition = function() { 264 | var modal = $(this), 265 | dialog = modal.find('.modal-dialog'); 266 | modal.css('display', 'block'); 267 | 268 | // Dividing by two centers the modal exactly, but dividing by three 269 | // or four works better for larger screens. 270 | dialog.css("margin-top", Math.max(0, ($(window).height() - dialog.height()) / 2)); 271 | } 272 | 273 | -------------------------------------------------------------------------------- /verynginx/dashboard/js/data_stat.js: -------------------------------------------------------------------------------- 1 | var data_stat = new Object(); 2 | 3 | data_stat.latest_data = null; 4 | 5 | data_stat.url_table = null; 6 | data_stat.collect_table = null; 7 | 8 | data_stat.search = function( s ){ 9 | if( data_stat.url_table != null ){ 10 | data_stat.url_table.search( s ).draw(); 11 | } 12 | 13 | if( data_stat.collect_table != null ){ 14 | data_stat.collect_table.search( s ).draw(); 15 | } 16 | } 17 | 18 | data_stat.current_group = function(){ 19 | var group = null; 20 | if ($("#def_btn").text() == 'All') { 21 | group = 'persistent'; 22 | } else { 23 | group = 'temporary'; 24 | }; 25 | 26 | return group; 27 | } 28 | 29 | data_stat.tab_switch = function ( item ) { 30 | // 标签切换文字变化 31 | if( $(item).attr('id') == 'summary_data_all' ){ 32 | $("#def_btn").html("All<span class=\"caret\"></span>"); 33 | $("#summary_type_note").css("display", "none"); 34 | }else{ 35 | $("#def_btn").html("Temporary<span class=\"caret\"></span>"); 36 | $("#summary_type_note").css("display", "inline-block"); 37 | } 38 | 39 | data_stat.get_data(); 40 | } 41 | 42 | data_stat.clear_data = function( ){ 43 | var group = data_stat.current_group(); 44 | $.post('./status/clear',data={group:group},function(){ 45 | dashboard.show_notice( 'info', 'Clear data group [' + group + '] success' ); 46 | data_stat.get_data(); 47 | }) 48 | } 49 | 50 | data_stat.make_sure_have_table = function(){ 51 | if( data_stat.url_table == null || data_stat.collect_table == null ){ 52 | data_stat.get_data(); 53 | } 54 | } 55 | 56 | data_stat.fill_data_info_table = function( data_type, data_dict ){ 57 | 58 | var table_id = null; 59 | if(data_type == 'collect'){ 60 | table_id = 'matched_details'; 61 | }else if( data_type == 'uri'){ 62 | table_id = 'unmatched_details'; 63 | }else{ 64 | throw new Error("unknown data_type"); 65 | return; 66 | } 67 | 68 | $('#' + table_id).html(""); // 动态生成表格前将表格清空 69 | 70 | var url_index = 1; 71 | for (var key in data_dict ) { 72 | var data_group = data_dict[key]; 73 | 74 | // 计算访问成功率 75 | var success_count = 0; 76 | var total_count = 0; 77 | for( var status_code in data_group['status'] ){ 78 | var this_status_count = data_group['status'][status_code]; 79 | if( parseInt( status_code ) < 400 ){ 80 | success_count += this_status_count; 81 | } 82 | total_count += this_status_count; 83 | } 84 | 85 | var success_rate = (success_count / total_count) * 100; 86 | var count = parseInt(data_dict[key].count); 87 | var size = parseFloat(data_dict[key].size); 88 | var avg_size = size / count; 89 | var time = parseFloat(data_dict[key].time); 90 | var avg_time = time / count; 91 | 92 | // 动态增加每一列关于各URL/URI的详细访问信息 93 | var dyn_tab = "<tr><td >" + url_index + "</td>" + 94 | "<td>" + util.html_encode(key) + "</td>" + 95 | "<td>" + count + "</td>" + 96 | "<td>" + size + "</td>" + 97 | "<td>" + avg_size.toFixed(2) + "</td>" + 98 | "<td><nobr>" + success_rate.toFixed(2) + '% <button detail_type="' + data_type + '" detail_key="' + util.html_encode(key) + '" class="btn vn_summary_detail_btn btn-xs">Details</button> </nobr></td>' + 99 | "<td>" + time.toFixed(3) + "</td>" + 100 | "<td>" + avg_time.toFixed(3) + "</td></tr>"; 101 | 102 | $('#' + table_id).append(dyn_tab); 103 | 104 | url_index++; // 增加访问序列 105 | } 106 | } 107 | 108 | 109 | data_stat.get_data = function () { 110 | 111 | var url_short = "./summary?type=short"; 112 | var url_long = "./summary?type=long"; 113 | var data_url; 114 | var group = data_stat.current_group(); 115 | 116 | if( group == 'persistent' ){ 117 | data_url = url_long; 118 | }else if( group == 'temporary' ){ 119 | data_url = url_short; 120 | }else{ 121 | return; 122 | } 123 | 124 | $.ajax({ 125 | type: "GET", 126 | url: data_url, 127 | // url: "/verynginx/summary?type=long", 128 | data_Type: "json", 129 | 130 | success: function (json_data) { 131 | data_stat.latest_data = json_data; 132 | var data_uri = json_data['uri']; 133 | var data_collect = json_data['collect']; 134 | 135 | data_stat.json_data = json_data; 136 | 137 | if( data_stat.url_table != null ){ 138 | data_stat.url_table.clear().destroy(); 139 | } 140 | 141 | if( data_stat.collect_table != null ){ 142 | data_stat.collect_table.clear().destroy(); 143 | } 144 | 145 | data_stat.fill_data_info_table( 'uri', data_uri ); 146 | data_stat.fill_data_info_table( 'collect', data_collect ); 147 | 148 | // 添加表格排序 149 | data_stat.url_table = $('#summary_unmatched_table').DataTable( { 150 | autoWidth: false, // 设置表格自动适配宽度 151 | scrollY: "500px", 152 | scrollCollapse: true, 153 | paging: false, // 去掉页头页脚信息 154 | "stripeClasses": [], // 去掉斑马色 155 | renderer: true, 156 | searching: true, // 增加过滤功能 157 | "order": [[ 0, "asc" ]] // 载入时默认使用index升序排列 158 | } ); 159 | 160 | data_stat.collect_table = $('#summary_matched_table').DataTable( { 161 | autoWidth: false, // 设置表格自动适配宽度 162 | scrollY: "500px", 163 | scrollCollapse: true, 164 | paging: false, // 去掉页头页脚信息 165 | "stripeClasses": [], // 去掉斑马色 166 | renderer: true, 167 | searching: true, // 增加过滤功能 168 | "order": [[ 0, "asc" ]] // 载入时默认使用index升序排列 169 | } ); 170 | 171 | $('#summary_unmatched_table tbody').unbind('mouseover'); 172 | $('#summary_unmatched_table tbody').unbind('mouseout'); 173 | $('#summary_matched_table tbody').unbind('mouseover'); 174 | $('#summary_matched_table tbody').unbind('mouseout'); 175 | 176 | $('#summary_unmatched_table tbody').on('mouseover', data_stat.detail_btn_mouse_out ); 177 | $('#summary_unmatched_table tbody').on('mouseout', data_stat.detail_btn_mouse_over ); 178 | $('#summary_matched_table tbody').on('mouseover', data_stat.detail_btn_mouse_out ); 179 | $('#summary_matched_table tbody').on('mouseout', data_stat.detail_btn_mouse_over ); 180 | } 181 | }); 182 | } 183 | 184 | 185 | data_stat.popover_item = null; 186 | 187 | data_stat.clean_popover = function(){ 188 | if( data_stat.popover_item != null ){ 189 | data_stat.popover_item.popover('destroy'); 190 | data_stat.popover_item.popover_item = null; 191 | } 192 | } 193 | 194 | data_stat.detail_btn_mouse_over = function( e ){ 195 | var target = $(e.relatedTarget); 196 | if( target.hasClass('vn_summary_detail_btn') == false ) 197 | return; 198 | 199 | data_stat.clean_popover(); 200 | 201 | var detail_key = target.attr('detail_key'); 202 | var detail_type = target.attr('detail_type'); 203 | 204 | var response_status = null; 205 | var response_count = null; 206 | if( detail_type == 'uri' ){ 207 | response_status = data_stat.latest_data['uri'][detail_key]['status']; 208 | response_count = data_stat.latest_data['uri'][detail_key]['count']; 209 | }else{ 210 | response_status = data_stat.latest_data['collect'][detail_key]['status']; 211 | response_count = data_stat.latest_data['collect'][detail_key]['count']; 212 | } 213 | 214 | var content = "<table class='vn_summary_detail_popover_table'>"; 215 | var status_list = Object.keys(response_status); 216 | for( var i=0; i<status_list.length; i++ ){ 217 | var status_code = status_list[i]; 218 | var rate = (100 * response_status[status_code] / response_count).toFixed(2); 219 | content += "<tr><td><span class='label label-info'>"+status_code+"</span></td>" + 220 | "<td class='vn_summary_detail_popover_count'>" + response_status[status_code] + "</td>" + 221 | "<td class='vn_summary_detail_popover_rate'>" + rate + "%</td></tr>"; 222 | } 223 | 224 | content += "</table>"; 225 | target.popover({ 226 | container:'#page_summary', 227 | animation : false, 228 | html : true, 229 | placement : 'right', //placement of the popover. also can use top, bottom, left or right 230 | title : 'Response Count', //this is the top title bar of the popover. add some basic css 231 | content : content, //this is the content of the html box. add the image here or anything you want really. 232 | }) 233 | target.popover('show'); 234 | data_stat.popover_item = target; 235 | } 236 | 237 | data_stat.detail_btn_mouse_out = function( e ){ 238 | if( $(e.relatedTarget).hasClass('vn_summary_detail_btn') == true ){ 239 | data_stat.clean_popover(); 240 | } 241 | } 242 | 243 | 244 | -------------------------------------------------------------------------------- /verynginx/dashboard/js/monitor.js: -------------------------------------------------------------------------------- 1 | var monitor = new Object(); 2 | 3 | monitor.chart_size = 17; 4 | 5 | monitor.refresh_timer = null; 6 | 7 | monitor.chart_request = null; 8 | monitor.chart_connection = null; 9 | 10 | monitor.latest_status = null; 11 | 12 | monitor.spin_list = []; 13 | 14 | monitor.time_str = function(){ 15 | var time_str = (new Date()).toTimeString(); 16 | return time_str.split(' ')[0]; 17 | } 18 | 19 | 20 | monitor.show_loading_img = function(){ 21 | var default_opts = { 22 | length: 28 23 | ,radius: 32 // The radius of the inner circle 24 | ,width: 9 // The line thickness 25 | ,color: '#5190be' // #rgb or #rrggbb or array of colors 26 | } 27 | var sm_opts = { 28 | length: 28 29 | ,radius: 21 // The radius of the inner circle 30 | ,width: 6 // The line thickness 31 | ,color: '#5190be' // #rgb or #rrggbb or array of colors 32 | } 33 | 34 | var target = $('.monitor_container'); 35 | for( var i=0; i<target.length; i++ ){ 36 | var item = target[i]; 37 | var size = $(item).attr('spin_size'); 38 | var opts = default_opts; 39 | 40 | if( size == "sm" ){ 41 | opts = sm_opts; 42 | } 43 | var spinner = new Spinner( opts ).spin(); 44 | item.appendChild(spinner.el); 45 | monitor.spin_list.push(spinner); 46 | } 47 | } 48 | 49 | monitor.hide_loading_img = function(){ 50 | for( var i=0; i<monitor.spin_list.length; i++ ){ 51 | var spin = monitor.spin_list[i]; 52 | spin.stop(); 53 | } 54 | } 55 | 56 | monitor.build_chart = function(){ 57 | //request chart 58 | monitor.show_loading_img(); 59 | var ctx_request = $("#chart_request").get(0).getContext("2d"); 60 | var options_request={responsive:true}; 61 | var data_request = { 62 | labels: [], 63 | datasets: [ 64 | { 65 | label: "all request", 66 | fillColor: "rgba(220,220,220,0.2)", 67 | strokeColor: "rgba(220,220,220,1)", 68 | pointColor: "rgba(220,220,220,1)", 69 | pointStrokeColor: "#fff", 70 | pointHighlightFill: "#fff", 71 | pointHighlightStroke: "rgba(220,220,220,1)", 72 | data: [] 73 | }, 74 | { 75 | label: "200 request", 76 | fillColor: "rgba(151,187,205,0.2)", 77 | strokeColor: "rgba(151,187,205,1)", 78 | pointColor: "rgba(151,187,205,1)", 79 | pointStrokeColor: "#fff", 80 | pointHighlightFill: "#fff", 81 | pointHighlightStroke: "rgba(151,187,205,1)", 82 | data: [] 83 | } 84 | ] 85 | }; 86 | monitor.chart_request = new Chart(ctx_request).Line(data_request,options_request); 87 | 88 | //connection chart 89 | var ctx_connection = $("#chart_connection").get(0).getContext("2d"); 90 | var options_connection={ responsive:true }; 91 | var data_connection = { 92 | labels: [], 93 | datasets: [ 94 | { 95 | label: "connection", 96 | fillColor: "rgba(220,220,220,0.2)", 97 | strokeColor: "rgba(220,220,220,1)", 98 | pointColor: "rgba(220,220,220,1)", 99 | pointStrokeColor: "#fff", 100 | pointHighlightFill: "#fff", 101 | pointHighlightStroke: "rgba(220,220,220,1)", 102 | data: [] 103 | }, 104 | { 105 | label: "writing", 106 | fillColor: "rgba(151,187,205,0.2)", 107 | strokeColor: "rgba(151,187,205,1)", 108 | pointColor: "rgba(151,187,205,1)", 109 | pointStrokeColor: "#fff", 110 | pointHighlightFill: "#fff", 111 | pointHighlightStroke: "rgba(151,187,205,1)", 112 | data: [] 113 | }, 114 | { 115 | label: "reading", 116 | fillColor: "rgba(151,205,187,0.2)", 117 | strokeColor: "rgba(151,205,187,1)", 118 | pointColor: "rgba(151,205,187,1)", 119 | pointStrokeColor: "#fff", 120 | pointHighlightFill: "#fff", 121 | pointHighlightStroke: "rgba(151,205,187,1)", 122 | data: [] 123 | } 124 | 125 | 126 | ] 127 | }; 128 | monitor.chart_connection = new Chart(ctx_connection).Line(data_connection,options_connection); 129 | 130 | //response time chart 131 | var ctx_response_time = $("#chart_response_time").get(0).getContext("2d"); 132 | var options_response_time={ responsive:true }; 133 | var data_response_time = { 134 | labels: [], 135 | datasets: [ 136 | { 137 | label: "response_time", 138 | fillColor: "rgba(220,220,220,0.2)", 139 | strokeColor: "rgba(220,220,220,1)", 140 | pointColor: "rgba(220,220,220,1)", 141 | pointStrokeColor: "#fff", 142 | pointHighlightFill: "#fff", 143 | pointHighlightStroke: "rgba(220,220,220,1)", 144 | data: [] 145 | }, 146 | ] 147 | }; 148 | monitor.chart_response_time = new Chart(ctx_response_time).Line(data_response_time,options_response_time); 149 | 150 | //traffic 151 | var ctx_traffic = $("#chart_traffic").get(0).getContext("2d"); 152 | var options_traffic={ responsive:true }; 153 | var data_traffic = { 154 | labels: [], 155 | datasets: [ 156 | { 157 | label: "traffic_read", 158 | fillColor: "rgba(220,220,220,0.2)", 159 | strokeColor: "rgba(220,220,220,1)", 160 | pointColor: "rgba(220,220,220,1)", 161 | pointStrokeColor: "#fff", 162 | pointHighlightFill: "#fff", 163 | pointHighlightStroke: "rgba(220,220,220,1)", 164 | data: [] 165 | }, 166 | { 167 | label: "traffic_write", 168 | fillColor: "rgba(151,187,205,0.2)", 169 | strokeColor: "rgba(151,187,205,1)", 170 | pointColor: "rgba(151,187,205,1)", 171 | pointStrokeColor: "#fff", 172 | pointHighlightFill: "#fff", 173 | pointHighlightStroke: "rgba(151,187,205,1)", 174 | data: [] 175 | } 176 | 177 | ] 178 | }; 179 | 180 | monitor.chart_traffic = new Chart(ctx_traffic).Line(data_traffic,options_traffic); 181 | 182 | //add visibilityChange event listener for different web browser 183 | var hidden, state, visibilityChange; 184 | if (typeof document.hidden !== "undefined") { 185 | hidden = "hidden"; 186 | visibilityChange = "visibilitychange"; 187 | state = "visibilityState"; 188 | } else if (typeof document.mozHidden !== "undefined") { 189 | hidden = "mozHidden"; 190 | visibilityChange = "mozvisibilitychange"; 191 | state = "mozVisibilityState"; 192 | } else if (typeof document.msHidden !== "undefined") { 193 | hidden = "msHidden"; 194 | visibilityChange = "msvisibilitychange"; 195 | state = "msVisibilityState"; 196 | } else if (typeof document.webkitHidden !== "undefined") { 197 | hidden = "webkitHidden"; 198 | visibilityChange = "webkitvisibilitychange"; 199 | state = "webkitVisibilityState"; 200 | } 201 | 202 | var on_change = function(){ 203 | if( document[state] != hidden ){ 204 | console.log('on visiable'); 205 | if( localStorage.dashboard_status_enable_animation == "true" ){ 206 | monitor.animation_enable(); 207 | } 208 | }else{ 209 | console.log('on hidden'); 210 | monitor.animation_disable(); 211 | } 212 | }; 213 | 214 | // add event listener for visibilityChange 215 | document.addEventListener( visibilityChange, on_change ); 216 | 217 | } 218 | 219 | monitor.start = function(){ 220 | 221 | var refresh_interval = 3; 222 | if( localStorage.dashboard_status_refresh_interval != undefined ){ 223 | refresh_interval = parseInt( localStorage.dashboard_status_refresh_interval ); 224 | } 225 | 226 | if( monitor.refresh_timer != null ){ 227 | console.log("Error:Monitor is already running"); 228 | return; 229 | } 230 | 231 | if( monitor.chart_request == null || monitor.chart_connection == null || monitor.chart_response_time == null || monitor.chart_traffic == null ){ 232 | console.log("Error:Monitor chart not init"); 233 | return; 234 | } 235 | 236 | var enable_animation = localStorage.dashboard_status_enable_animation; 237 | if( enable_animation == 'true' && $('#page_status').is(":visible") ){ 238 | monitor.animation_enable(); 239 | }else{ 240 | monitor.animation_disable(); 241 | } 242 | 243 | monitor.refresh_timer = window.setInterval( monitor.refresh , refresh_interval * 1000); 244 | monitor.refresh(); 245 | } 246 | 247 | monitor.stop = function(){ 248 | if( monitor.refresh_timer != null ){ 249 | window.clearInterval( monitor.refresh_timer ); 250 | monitor.refresh_timer = null; 251 | } 252 | } 253 | 254 | monitor.animation_disable = function(){ 255 | monitor.chart_request.options['animation'] = false; 256 | monitor.chart_connection.options['animation'] = false; 257 | monitor.chart_response_time.options['animation'] = false; 258 | monitor.chart_traffic.options['animation'] = false; 259 | } 260 | 261 | monitor.animation_enable = function(){ 262 | monitor.chart_request.options['animation'] = true; 263 | monitor.chart_connection.options['animation'] = true; 264 | monitor.chart_response_time.options['animation'] = true; 265 | monitor.chart_traffic.options['animation'] = true; 266 | } 267 | 268 | monitor.refresh = function(){ 269 | //console.log("monitor refresh"); 270 | 271 | $.get("./status",function(data,status){ 272 | if( status != 'success' ){ 273 | return; 274 | } 275 | 276 | //console.log('status:',status); 277 | //console.log('data:',data); 278 | if( monitor.latest_status != null ){ 279 | var time_change = data['time'] - monitor.latest_status['time']; 280 | //console.log('time_change',time_change); 281 | if(time_change == 0 ){ 282 | return; 283 | } 284 | 285 | if( monitor.spin_list.length != 0 ){ 286 | monitor.hide_loading_img(); 287 | } 288 | 289 | var requests_all_change = data['request_all_count'] - monitor.latest_status['request_all_count']; 290 | var requests_success_change = data['request_success_count'] - monitor.latest_status['request_success_count']; 291 | var connections_active = data['connections_active']; 292 | var connections_reading = data['connections_reading']; 293 | var connections_writing = data['connections_writing']; 294 | var avg_request_all = requests_all_change / time_change; 295 | var avg_request_success = requests_success_change / time_change; 296 | var time_str = monitor.time_str(); 297 | var sub_label = ''; 298 | var response_time_change = data['response_time_total'] - monitor.latest_status['response_time_total']; 299 | var avg_response_time = 0; 300 | if( requests_all_change != 0 ){ 301 | avg_response_time = 1000 * response_time_change / requests_all_change ; 302 | } 303 | 304 | var traffic_read_change = data['traffic_read'] - monitor.latest_status['traffic_read']; 305 | var traffic_write_change = data['traffic_write'] - monitor.latest_status['traffic_write']; 306 | 307 | var avg_traffic_read = traffic_read_change / (time_change*1024); 308 | var avg_traffic_write = traffic_write_change / (time_change*1024); 309 | 310 | monitor.chart_request.addData([avg_request_all,avg_request_success], time_str); 311 | monitor.chart_connection.addData( [ connections_active,connections_writing,connections_reading ], sub_label ) 312 | monitor.chart_response_time.addData( [ avg_response_time ], sub_label ) 313 | monitor.chart_traffic.addData( [ avg_traffic_read, avg_traffic_write ], sub_label ) 314 | 315 | while( monitor.chart_request.datasets[0].points.length >= monitor.chart_size ){ 316 | monitor.chart_request.removeData(); 317 | } 318 | 319 | while( monitor.chart_connection.datasets[0].points.length >= monitor.chart_size ){ 320 | monitor.chart_connection.removeData(); 321 | } 322 | 323 | while( monitor.chart_response_time.datasets[0].points.length >= monitor.chart_size ){ 324 | monitor.chart_response_time.removeData(); 325 | } 326 | 327 | while( monitor.chart_traffic.datasets[0].points.length >= monitor.chart_size ){ 328 | monitor.chart_traffic.removeData(); 329 | } 330 | } 331 | monitor.latest_status = data; 332 | }); 333 | } 334 | 335 | monitor.update_config =function(){ 336 | 337 | console.log('monitor.save_config'); 338 | monitor.stop(); 339 | monitor.start(); 340 | } 341 | 342 | 343 | -------------------------------------------------------------------------------- /verynginx/dashboard/js/pace.min.js: -------------------------------------------------------------------------------- 1 | /*! pace 1.0.2 */ 2 | (function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X=[].slice,Y={}.hasOwnProperty,Z=function(a,b){function c(){this.constructor=a}for(var d in b)Y.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a},$=[].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1};for(u={catchupTime:100,initialRate:.03,minTime:250,ghostTime:100,maxProgressPerFrame:20,easeFactor:1.25,startOnPageLoad:!0,restartOnPushState:!0,restartOnRequestAfter:500,target:"body",elements:{checkInterval:100,selectors:["body"]},eventLag:{minSamples:10,sampleCount:3,lagThreshold:3},ajax:{trackMethods:["GET"],trackWebSockets:!0,ignoreURLs:[]}},C=function(){var a;return null!=(a="undefined"!=typeof performance&&null!==performance&&"function"==typeof performance.now?performance.now():void 0)?a:+new Date},E=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame,t=window.cancelAnimationFrame||window.mozCancelAnimationFrame,null==E&&(E=function(a){return setTimeout(a,50)},t=function(a){return clearTimeout(a)}),G=function(a){var b,c;return b=C(),(c=function(){var d;return d=C()-b,d>=33?(b=C(),a(d,function(){return E(c)})):setTimeout(c,33-d)})()},F=function(){var a,b,c;return c=arguments[0],b=arguments[1],a=3<=arguments.length?X.call(arguments,2):[],"function"==typeof c[b]?c[b].apply(c,a):c[b]},v=function(){var a,b,c,d,e,f,g;for(b=arguments[0],d=2<=arguments.length?X.call(arguments,1):[],f=0,g=d.length;g>f;f++)if(c=d[f])for(a in c)Y.call(c,a)&&(e=c[a],null!=b[a]&&"object"==typeof b[a]&&null!=e&&"object"==typeof e?v(b[a],e):b[a]=e);return b},q=function(a){var b,c,d,e,f;for(c=b=0,e=0,f=a.length;f>e;e++)d=a[e],c+=Math.abs(d),b++;return c/b},x=function(a,b){var c,d,e;if(null==a&&(a="options"),null==b&&(b=!0),e=document.querySelector("[data-pace-"+a+"]")){if(c=e.getAttribute("data-pace-"+a),!b)return c;try{return JSON.parse(c)}catch(f){return d=f,"undefined"!=typeof console&&null!==console?console.error("Error parsing inline pace options",d):void 0}}},g=function(){function a(){}return a.prototype.on=function(a,b,c,d){var e;return null==d&&(d=!1),null==this.bindings&&(this.bindings={}),null==(e=this.bindings)[a]&&(e[a]=[]),this.bindings[a].push({handler:b,ctx:c,once:d})},a.prototype.once=function(a,b,c){return this.on(a,b,c,!0)},a.prototype.off=function(a,b){var c,d,e;if(null!=(null!=(d=this.bindings)?d[a]:void 0)){if(null==b)return delete this.bindings[a];for(c=0,e=[];c<this.bindings[a].length;)e.push(this.bindings[a][c].handler===b?this.bindings[a].splice(c,1):c++);return e}},a.prototype.trigger=function(){var a,b,c,d,e,f,g,h,i;if(c=arguments[0],a=2<=arguments.length?X.call(arguments,1):[],null!=(g=this.bindings)?g[c]:void 0){for(e=0,i=[];e<this.bindings[c].length;)h=this.bindings[c][e],d=h.handler,b=h.ctx,f=h.once,d.apply(null!=b?b:this,a),i.push(f?this.bindings[c].splice(e,1):e++);return i}},a}(),j=window.Pace||{},window.Pace=j,v(j,g.prototype),D=j.options=v({},u,window.paceOptions,x()),U=["ajax","document","eventLag","elements"],Q=0,S=U.length;S>Q;Q++)K=U[Q],D[K]===!0&&(D[K]=u[K]);i=function(a){function b(){return V=b.__super__.constructor.apply(this,arguments)}return Z(b,a),b}(Error),b=function(){function a(){this.progress=0}return a.prototype.getElement=function(){var a;if(null==this.el){if(a=document.querySelector(D.target),!a)throw new i;this.el=document.createElement("div"),this.el.className="pace pace-active",document.body.className=document.body.className.replace(/pace-done/g,""),document.body.className+=" pace-running",this.el.innerHTML='<div class="pace-progress">\n <div class="pace-progress-inner"></div>\n</div>\n<div class="pace-activity"></div>',null!=a.firstChild?a.insertBefore(this.el,a.firstChild):a.appendChild(this.el)}return this.el},a.prototype.finish=function(){var a;return a=this.getElement(),a.className=a.className.replace("pace-active",""),a.className+=" pace-inactive",document.body.className=document.body.className.replace("pace-running",""),document.body.className+=" pace-done"},a.prototype.update=function(a){return this.progress=a,this.render()},a.prototype.destroy=function(){try{this.getElement().parentNode.removeChild(this.getElement())}catch(a){i=a}return this.el=void 0},a.prototype.render=function(){var a,b,c,d,e,f,g;if(null==document.querySelector(D.target))return!1;for(a=this.getElement(),d="translate3d("+this.progress+"%, 0, 0)",g=["webkitTransform","msTransform","transform"],e=0,f=g.length;f>e;e++)b=g[e],a.children[0].style[b]=d;return(!this.lastRenderedProgress||this.lastRenderedProgress|0!==this.progress|0)&&(a.children[0].setAttribute("data-progress-text",""+(0|this.progress)+"%"),this.progress>=100?c="99":(c=this.progress<10?"0":"",c+=0|this.progress),a.children[0].setAttribute("data-progress",""+c)),this.lastRenderedProgress=this.progress},a.prototype.done=function(){return this.progress>=100},a}(),h=function(){function a(){this.bindings={}}return a.prototype.trigger=function(a,b){var c,d,e,f,g;if(null!=this.bindings[a]){for(f=this.bindings[a],g=[],d=0,e=f.length;e>d;d++)c=f[d],g.push(c.call(this,b));return g}},a.prototype.on=function(a,b){var c;return null==(c=this.bindings)[a]&&(c[a]=[]),this.bindings[a].push(b)},a}(),P=window.XMLHttpRequest,O=window.XDomainRequest,N=window.WebSocket,w=function(a,b){var c,d,e;e=[];for(d in b.prototype)try{e.push(null==a[d]&&"function"!=typeof b[d]?"function"==typeof Object.defineProperty?Object.defineProperty(a,d,{get:function(){return b.prototype[d]},configurable:!0,enumerable:!0}):a[d]=b.prototype[d]:void 0)}catch(f){c=f}return e},A=[],j.ignore=function(){var a,b,c;return b=arguments[0],a=2<=arguments.length?X.call(arguments,1):[],A.unshift("ignore"),c=b.apply(null,a),A.shift(),c},j.track=function(){var a,b,c;return b=arguments[0],a=2<=arguments.length?X.call(arguments,1):[],A.unshift("track"),c=b.apply(null,a),A.shift(),c},J=function(a){var b;if(null==a&&(a="GET"),"track"===A[0])return"force";if(!A.length&&D.ajax){if("socket"===a&&D.ajax.trackWebSockets)return!0;if(b=a.toUpperCase(),$.call(D.ajax.trackMethods,b)>=0)return!0}return!1},k=function(a){function b(){var a,c=this;b.__super__.constructor.apply(this,arguments),a=function(a){var b;return b=a.open,a.open=function(d,e){return J(d)&&c.trigger("request",{type:d,url:e,request:a}),b.apply(a,arguments)}},window.XMLHttpRequest=function(b){var c;return c=new P(b),a(c),c};try{w(window.XMLHttpRequest,P)}catch(d){}if(null!=O){window.XDomainRequest=function(){var b;return b=new O,a(b),b};try{w(window.XDomainRequest,O)}catch(d){}}if(null!=N&&D.ajax.trackWebSockets){window.WebSocket=function(a,b){var d;return d=null!=b?new N(a,b):new N(a),J("socket")&&c.trigger("request",{type:"socket",url:a,protocols:b,request:d}),d};try{w(window.WebSocket,N)}catch(d){}}}return Z(b,a),b}(h),R=null,y=function(){return null==R&&(R=new k),R},I=function(a){var b,c,d,e;for(e=D.ajax.ignoreURLs,c=0,d=e.length;d>c;c++)if(b=e[c],"string"==typeof b){if(-1!==a.indexOf(b))return!0}else if(b.test(a))return!0;return!1},y().on("request",function(b){var c,d,e,f,g;return f=b.type,e=b.request,g=b.url,I(g)?void 0:j.running||D.restartOnRequestAfter===!1&&"force"!==J(f)?void 0:(d=arguments,c=D.restartOnRequestAfter||0,"boolean"==typeof c&&(c=0),setTimeout(function(){var b,c,g,h,i,k;if(b="socket"===f?e.readyState<2:0<(h=e.readyState)&&4>h){for(j.restart(),i=j.sources,k=[],c=0,g=i.length;g>c;c++){if(K=i[c],K instanceof a){K.watch.apply(K,d);break}k.push(void 0)}return k}},c))}),a=function(){function a(){var a=this;this.elements=[],y().on("request",function(){return a.watch.apply(a,arguments)})}return a.prototype.watch=function(a){var b,c,d,e;return d=a.type,b=a.request,e=a.url,I(e)?void 0:(c="socket"===d?new n(b):new o(b),this.elements.push(c))},a}(),o=function(){function a(a){var b,c,d,e,f,g,h=this;if(this.progress=0,null!=window.ProgressEvent)for(c=null,a.addEventListener("progress",function(a){return h.progress=a.lengthComputable?100*a.loaded/a.total:h.progress+(100-h.progress)/2},!1),g=["load","abort","timeout","error"],d=0,e=g.length;e>d;d++)b=g[d],a.addEventListener(b,function(){return h.progress=100},!1);else f=a.onreadystatechange,a.onreadystatechange=function(){var b;return 0===(b=a.readyState)||4===b?h.progress=100:3===a.readyState&&(h.progress=50),"function"==typeof f?f.apply(null,arguments):void 0}}return a}(),n=function(){function a(a){var b,c,d,e,f=this;for(this.progress=0,e=["error","open"],c=0,d=e.length;d>c;c++)b=e[c],a.addEventListener(b,function(){return f.progress=100},!1)}return a}(),d=function(){function a(a){var b,c,d,f;for(null==a&&(a={}),this.elements=[],null==a.selectors&&(a.selectors=[]),f=a.selectors,c=0,d=f.length;d>c;c++)b=f[c],this.elements.push(new e(b))}return a}(),e=function(){function a(a){this.selector=a,this.progress=0,this.check()}return a.prototype.check=function(){var a=this;return document.querySelector(this.selector)?this.done():setTimeout(function(){return a.check()},D.elements.checkInterval)},a.prototype.done=function(){return this.progress=100},a}(),c=function(){function a(){var a,b,c=this;this.progress=null!=(b=this.states[document.readyState])?b:100,a=document.onreadystatechange,document.onreadystatechange=function(){return null!=c.states[document.readyState]&&(c.progress=c.states[document.readyState]),"function"==typeof a?a.apply(null,arguments):void 0}}return a.prototype.states={loading:0,interactive:50,complete:100},a}(),f=function(){function a(){var a,b,c,d,e,f=this;this.progress=0,a=0,e=[],d=0,c=C(),b=setInterval(function(){var g;return g=C()-c-50,c=C(),e.push(g),e.length>D.eventLag.sampleCount&&e.shift(),a=q(e),++d>=D.eventLag.minSamples&&a<D.eventLag.lagThreshold?(f.progress=100,clearInterval(b)):f.progress=100*(3/(a+3))},50)}return a}(),m=function(){function a(a){this.source=a,this.last=this.sinceLastUpdate=0,this.rate=D.initialRate,this.catchup=0,this.progress=this.lastProgress=0,null!=this.source&&(this.progress=F(this.source,"progress"))}return a.prototype.tick=function(a,b){var c;return null==b&&(b=F(this.source,"progress")),b>=100&&(this.done=!0),b===this.last?this.sinceLastUpdate+=a:(this.sinceLastUpdate&&(this.rate=(b-this.last)/this.sinceLastUpdate),this.catchup=(b-this.progress)/D.catchupTime,this.sinceLastUpdate=0,this.last=b),b>this.progress&&(this.progress+=this.catchup*a),c=1-Math.pow(this.progress/100,D.easeFactor),this.progress+=c*this.rate*a,this.progress=Math.min(this.lastProgress+D.maxProgressPerFrame,this.progress),this.progress=Math.max(0,this.progress),this.progress=Math.min(100,this.progress),this.lastProgress=this.progress,this.progress},a}(),L=null,H=null,r=null,M=null,p=null,s=null,j.running=!1,z=function(){return D.restartOnPushState?j.restart():void 0},null!=window.history.pushState&&(T=window.history.pushState,window.history.pushState=function(){return z(),T.apply(window.history,arguments)}),null!=window.history.replaceState&&(W=window.history.replaceState,window.history.replaceState=function(){return z(),W.apply(window.history,arguments)}),l={ajax:a,elements:d,document:c,eventLag:f},(B=function(){var a,c,d,e,f,g,h,i;for(j.sources=L=[],g=["ajax","elements","document","eventLag"],c=0,e=g.length;e>c;c++)a=g[c],D[a]!==!1&&L.push(new l[a](D[a]));for(i=null!=(h=D.extraSources)?h:[],d=0,f=i.length;f>d;d++)K=i[d],L.push(new K(D));return j.bar=r=new b,H=[],M=new m})(),j.stop=function(){return j.trigger("stop"),j.running=!1,r.destroy(),s=!0,null!=p&&("function"==typeof t&&t(p),p=null),B()},j.restart=function(){return j.trigger("restart"),j.stop(),j.start()},j.go=function(){var a;return j.running=!0,r.render(),a=C(),s=!1,p=G(function(b,c){var d,e,f,g,h,i,k,l,n,o,p,q,t,u,v,w;for(l=100-r.progress,e=p=0,f=!0,i=q=0,u=L.length;u>q;i=++q)for(K=L[i],o=null!=H[i]?H[i]:H[i]=[],h=null!=(w=K.elements)?w:[K],k=t=0,v=h.length;v>t;k=++t)g=h[k],n=null!=o[k]?o[k]:o[k]=new m(g),f&=n.done,n.done||(e++,p+=n.tick(b));return d=p/e,r.update(M.tick(b,d)),r.done()||f||s?(r.update(100),j.trigger("done"),setTimeout(function(){return r.finish(),j.running=!1,j.trigger("hide")},Math.max(D.ghostTime,Math.max(D.minTime-(C()-a),0)))):c()})},j.start=function(a){v(D,a),j.running=!0;try{r.render()}catch(b){i=b}return document.querySelector(".pace")?(j.trigger("start"),j.go()):setTimeout(j.start,50)},"function"==typeof define&&define.amd?define(["pace"],function(){return j}):"object"==typeof exports?module.exports=j:D.startOnPageLoad&&j.start()}).call(this); 3 | -------------------------------------------------------------------------------- /verynginx/dashboard/js/tips.js: -------------------------------------------------------------------------------- 1 | var tips = new Object(); 2 | 3 | tips.tips_vm = null; 4 | tips.show_tips = function(group){ 5 | 6 | $('.tips_content').collapse('hide'); 7 | 8 | if(tips.tips_vm != null){ 9 | tips.tips_vm.$data = {tips:tips.data[group]}; 10 | return; 11 | } 12 | 13 | tips.tips_vm = new Vue({ 14 | el: '#verynginx_tips', 15 | data: {tips:tips.data[group]}, 16 | }); 17 | 18 | } 19 | 20 | tips.toggle = function(tips_container){ 21 | $(tips_container).children(':last').collapse('toggle'); 22 | } 23 | 24 | tips.data = { 25 | 'basic_matcher':[ 26 | {"tips":"Purpose","content":"A Matcher used to match request"}, 27 | {"tips":"Introduce","content":"When a request match all condition in a matcher, the request hit the matcher"}, 28 | {"tips":"Usage","content":["You can add one or more conditions to a matcher", 29 | "A empty matcher will match all request"]} 30 | ], 31 | 'action_scheme_lock':[ 32 | {"tips":"Purpose","content":"Lock all request on http or https"}, 33 | {"tips":"Introduce","content":"This action will check if the scheme current using fit to the rule. If scheme wrong, it will give a 302 redirect to the right scheme" }, 34 | {"tips":"Usage","content":["https/http means only https/http,both means not limit", 35 | "From top to bottom to match, and only use the first match rule"] 36 | 37 | }, 38 | ], 39 | 'action_redirect':[ 40 | {"tips":"Purpose","content":"Redirect to other address"}, 41 | {"tips":"Usage","content":["From top to bottom to match, and only use the first match rule"]} 42 | ], 43 | 'filter_ipwhitelist':[ 44 | {"tips":"功能介绍","content":"IP白名单功能可以指定免过滤的IP"}, 45 | {"tips":"实现原理","content":"来自该列表中IP的访问请求将跳过过滤阶段"}, 46 | {"tips":"配置说明","content":"请填写完整的IP地址"} 47 | ], 48 | 'filter_ip':[ 49 | {"tips":"功能介绍","content":"IP过滤功能可以拦截来自某些IP的所有访问"}, 50 | {"tips":"实现原理","content":"来自该列表中IP的访问请求将返回503"}, 51 | {"tips":"配置说明","content":"请填写完整的IP地址"} 52 | ], 53 | 'filter_useragent':[ 54 | {"tips":"功能介绍","content":"UserAgent过滤功能可以拦截来自某些客户端访问"}, 55 | {"tips":"实现原理","content":"本功能在收到一个请求时,检查请求所携带的useragent是否和规则一致,如果一致则返回503禁止访问"}, 56 | {"tips":"配置说明","content":["参数UserAgent为一个正则表达式,用来匹配请求的UserAgent", 57 | "规则匹配时不区分大小写,按照从上到下的顺序进行匹配,有一条规则匹配到即被拦截"]} 58 | ], 59 | 'filter_uri':[ 60 | {"tips":"功能介绍","content":"URI过滤功能可以拦截对某些URI的访问请求"}, 61 | {"tips":"实现原理","content":"本功能在收到一个请求时,检查所请求的URI是否和规则一致,如果一致则返回503禁止访问"}, 62 | {"tips":"配置说明","content":["参数URI为一个正则表达式,用来匹配请求的URI", 63 | "URI和Nginx的变量URI一致,表示请求地址中域名之后的部分,不包含查询字符串", 64 | "规则匹配时不区分大小写,按照从上到下的顺序进行匹配,有一条规则匹配到即被拦截"]} 65 | ], 66 | 'filter_arg':[ 67 | {"tips":"功能介绍","content":"参数过滤功能可以拦截带有危险参数的访问请求"}, 68 | {"tips":"实现原理","content":"本功能在收到一个请求时,检查请求所携带的参数是否和规则一致,如果一致则返回503禁止访问"}, 69 | {"tips":"配置说明","content":["参数ARG为一个正则表达式,用来匹配请求所携带的参数值", 70 | "规则将检查GET和POST请求的每一个参数", 71 | "规则匹配时不区分大小写,按照从上到下的顺序进行匹配,有一条规则匹配到即被拦截"]} 72 | ], 73 | 'summarg_request':[ 74 | {"tips":"功能介绍","content":"访问统计功能可以统计各URI的访问情况"}, 75 | ], 76 | 77 | 'system_allconfig':[ 78 | {"tips":"功能介绍","content":"可以在这里看到全部的配置情况"}, 79 | {"tips":"操作说明","content":["点击保存配置将保存全部配置到服务器,并即刻生效", 80 | "点击读取配置将从服务器获取当前使用的配置", 81 | "配置保存在VeryNginx目录下的config.json文件。备份/删除 该文件可以 备份/恢复默认 设置"]} 82 | ], 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /verynginx/dashboard/js/upstream_editor.js: -------------------------------------------------------------------------------- 1 | var upstream_editor = new Object(); 2 | 3 | upstream_editor.tmp_node_vm = null; 4 | //upstream_editor.tmp_node = []; 5 | 6 | upstream_editor.tmp_node = {}; 7 | 8 | upstream_editor.init = function(){ 9 | 10 | upstream_editor.tmp_node_vm = new Vue({ 11 | el: '#config_proxy_upstream_editor_node', 12 | data: { 13 | node:upstream_editor.tmp_node 14 | }, 15 | }); 16 | } 17 | 18 | upstream_editor.tmp_node_delete = function( btn ){ 19 | 20 | //console.log('tmp_conditions_delete:',btn); 21 | var key = $(btn).parent().children('.config_matcher_block_type').text(); 22 | //console.log('key:',key); 23 | 24 | Vue.delete( upstream_editor.tmp_node, key ); 25 | } 26 | 27 | upstream_editor.get_data = function(){ 28 | var data = {}; 29 | data['name'] = $('#config_upstream_form [name=name]').val(); 30 | data['method'] = $('#config_upstream_form [name=method]').val(); 31 | data['node'] = upstream_editor.tmp_node; 32 | 33 | return data; 34 | } 35 | 36 | upstream_editor.set_data = function( data ){ 37 | $('#config_upstream_form [name=name]').val( data['name'] ); 38 | $('#config_upstream_form [name=method]').val( data['method'] ); 39 | upstream_editor.tmp_node = data['node']; 40 | upstream_editor.tmp_node_vm.$data = {node:upstream_editor.tmp_node}; 41 | } 42 | 43 | upstream_editor.modal_node_open = function(){ 44 | $('#config_modal_node').modal('show'); 45 | } 46 | 47 | upstream_editor.modal_node_save = function(){ 48 | var data = vnform.get_data('config_modal_node_form'); 49 | console.log( data ); 50 | var node_name = data['name']; 51 | delete data['name']; 52 | 53 | //verify 54 | var err_msg = vnform.verify_form( "config_modal_node_form" ); 55 | if( err_msg != null ){ 56 | dashboard.show_notice('warning', err_msg ); 57 | return; 58 | } 59 | 60 | Vue.set(upstream_editor.tmp_node, node_name, data); 61 | $('#config_modal_node').modal('hide'); 62 | upstream_editor.clean_modal(); 63 | } 64 | 65 | upstream_editor.reset = function(){ 66 | 67 | console.log('upstream_editor.reset'); 68 | $('#config_upstream_form [name=name]').val(''); 69 | $('#config_upstream_form [name=method]').val('random'); 70 | 71 | upstream_editor.tmp_node = {}; 72 | upstream_editor.tmp_node_vm.$data = {node:upstream_editor.tmp_node}; 73 | } 74 | 75 | upstream_editor.clean_modal = function(){ 76 | $('#config_modal_node input').val(''); 77 | } 78 | -------------------------------------------------------------------------------- /verynginx/dashboard/js/util.js: -------------------------------------------------------------------------------- 1 | var util = new Object(); 2 | 3 | util.html_encode = function( value ){ 4 | return $('<div/>').text(value).html(); 5 | } 6 | 7 | util.html_decode = function( value ){ 8 | return $('<div/>').html(value).text(); 9 | } 10 | 11 | util.clone = function( data ){ 12 | return JSON.parse( JSON.stringify( data ) ); 13 | } 14 | 15 | util.sync_vue_model = function( selector ){ 16 | $( selector ).find( 'input,textarea,select' ).each( function(){ 17 | util.dispatchEvent( this,'change') 18 | }); 19 | } 20 | 21 | util.reset_input_area = function( selector ){ 22 | //reset inpu 23 | $( selector ).find('input[type="text"],textarea').each(function(){ 24 | $(this).val(""); 25 | }); 26 | 27 | $( selector ).find('input[type="checkbox"]').each(function(){ 28 | this.checked = false; 29 | }); 30 | 31 | //reset select 32 | $( selector ).find('select').each(function(){ 33 | $(this).prop('selectedIndex', 0); 34 | }); 35 | 36 | util.sync_vue_model( selector ); 37 | }; 38 | 39 | util.dispatchEvent = function( element, event_name ){ 40 | if ("createEvent" in document) { 41 | var evt = document.createEvent("HTMLEvents"); 42 | evt.initEvent( event_name , false, true); 43 | element.dispatchEvent(evt); 44 | }else{ 45 | element.fireEvent( "on" + event_name); 46 | } 47 | } 48 | 49 | util.mark_ajax_slince = function( request ){ 50 | request.slince = true; 51 | } 52 | 53 | -------------------------------------------------------------------------------- /verynginx/dashboard/js/verify.js: -------------------------------------------------------------------------------- 1 | //提供对输入表单的校验 2 | //符合返回 '' 3 | //不符合返回错误原因描述字符串 4 | 5 | var verify = new Object(); 6 | 7 | verify.unsigned_integer = function() 8 | { 9 | var handle = function(v){ 10 | 11 | if( parseInt(v) != v ) 12 | return "must be integer"; 13 | 14 | if(v.indexOf('-') ==0 ) 15 | return "must be a positive integer"; 16 | 17 | if( parseInt(v) <= 0 ) 18 | return "must be a positive integer"; 19 | } 20 | return handle; 21 | } 22 | 23 | //校验数值范围,需要是整数 24 | verify.integer_in_range = function (min,max) 25 | { 26 | var handle = function(v){ 27 | 28 | if( parseInt(v) != v ) 29 | return "The value must be integer"; 30 | 31 | var err_msg = "The value must between " +min+ " and " + max; 32 | 33 | if( max != null && v > max ){ 34 | return err_msg; 35 | } 36 | 37 | if( min != null && v < min ){ 38 | return err_msg; 39 | } 40 | return null; 41 | } 42 | 43 | return handle; 44 | } 45 | 46 | //校验数值范围,可以是小数 47 | verify.floatRange = function (min,max) 48 | { 49 | var handle = function(v){ 50 | 51 | if( (v<min) && (v<max)) 52 | return "必须在"+min+"和"+max+"之间"; 53 | 54 | return ""; 55 | } 56 | 57 | return handle; 58 | } 59 | 60 | 61 | //校验值是否在数值范围内,或者为0 62 | verify.rangeOrZero = function (min,max) 63 | { 64 | var handle = function(v) { 65 | 66 | var err_message = "必须为整数,在"+min+"和"+max+"之间,或者为0"; 67 | 68 | if(v==0) 69 | return ""; 70 | 71 | if(v.indexOf('.') >=0 ) 72 | return err_message; 73 | 74 | if( ( min <= v ) && ( v <= max ) ){ 75 | return ""; 76 | }else{ 77 | return err_message; 78 | } 79 | } 80 | 81 | return handle; 82 | } 83 | 84 | 85 | //verify length of the string, null means not limit 86 | verify.str_len = function( min,max ) 87 | { 88 | var handle = function(v){ 89 | if( min != null && v.length < min ){ 90 | return "string mast more then " + min + " character"; 91 | } 92 | 93 | if( max != null && v.length > max ){ 94 | return "string mast less then " + max + " character"; 95 | } 96 | return null; 97 | } 98 | 99 | return handle; 100 | } 101 | 102 | verify.ip =function () 103 | { 104 | 105 | var handle = function( ip ){ 106 | var re=/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; 107 | if(re.test(ip)) 108 | { 109 | if( RegExp.$1<256 && RegExp.$2<256 && RegExp.$3<256 && RegExp.$4<256) 110 | return null; 111 | } 112 | 113 | return "The value isn't a valid IP"; 114 | } 115 | 116 | return handle; 117 | } 118 | 119 | verify.port = function() 120 | { 121 | return this.integer_in_range(0,65535); 122 | } 123 | 124 | verify.port_or_blank = function(){ 125 | var handle = function( v ){ 126 | if( v == '') 127 | return null; 128 | 129 | return verify.port()(v) 130 | } 131 | return handle; 132 | } 133 | 134 | verify.url = function(){ 135 | var handle = function(v){ 136 | if( v.indexOf('https://') != 0 && v.indexOf('http://') != 0 ){ 137 | return 'URL should start with a "https://" or "http://" '; 138 | } 139 | } 140 | return handle; 141 | } 142 | 143 | verify.uri = function(){ 144 | var handle = function( v ){ 145 | if( v.length == 0 ){ 146 | return "The value require to contain at least one character"; 147 | } 148 | 149 | if( v.indexOf('/') != 0 ){ 150 | return "The value require to start with '/' "; 151 | } 152 | return null; 153 | } 154 | return handle; 155 | } 156 | 157 | //base uri of VerifyNginx dashboard 158 | verify.base_uri = function(){ 159 | var handle = function(v){ 160 | var v_uri = verify.uri(); 161 | var v_uri_ret = v_uri(v); 162 | 163 | if( v_uri_ret != null) 164 | return v_uri_ret; 165 | 166 | if( v.length > 1 && v.lastIndexOf('/') == v.length - 1 ){ 167 | return "Base URI should not end with '/' "; 168 | } 169 | 170 | return null; 171 | } 172 | return handle; 173 | } 174 | 175 | //url or uri 176 | verify.url_or_uri = function(){ 177 | var handle = function(v){ 178 | var v_uri = verify.uri(); 179 | var v_uri_ret = v_uri(v); 180 | 181 | var v_url = verify.url(); 182 | var v_url_ret = v_url(v); 183 | 184 | if( v_uri_ret != null && v_url_ret != null ){ 185 | return "The string is not a valid uri or url"; 186 | } 187 | 188 | return; 189 | } 190 | return handle; 191 | } 192 | 193 | verify.path = verify.uri; 194 | 195 | verify.ngx_time = function(){ 196 | var handle = function(v){ 197 | 198 | if( v == '' ){ 199 | return "The value can't be empty string"; 200 | } 201 | 202 | if( v == 'epoch'){ 203 | return null; 204 | } 205 | 206 | var map = 'smhd';//char can be used 207 | var err_msg = 'The value must be "epoch" or end with the character in "' + map + '"'; 208 | 209 | var last_char = v.substring( v.length - 1 ); 210 | if( map.indexOf( last_char ) < 0 ){ 211 | return err_msg; 212 | } 213 | 214 | return null; 215 | } 216 | return handle; 217 | } 218 | 219 | verify.host = function(){ 220 | var handle = function(v){ 221 | if( v == '' ){ 222 | return "The value can't empty"; 223 | } 224 | 225 | if( v.indexOf(" ") >= 0 ){ 226 | return "The value can't include ' '"; 227 | } 228 | 229 | return null; 230 | } 231 | return handle; 232 | } 233 | 234 | verify.host_or_empty = function(){ 235 | var handle = function(v){ 236 | if( v == '' ){ 237 | return null; 238 | } 239 | return verify.host()(v); 240 | } 241 | return handle; 242 | } 243 | 244 | 245 | 246 | -------------------------------------------------------------------------------- /verynginx/dashboard/js/vnform.js: -------------------------------------------------------------------------------- 1 | var vnform = new Object(); 2 | 3 | vnform.clean_all_err_mark = function(){ 4 | $(".vn-has-error").removeClass('vn-has-error'); 5 | } 6 | 7 | vnform.add_err_mark = function( item ){ 8 | item.addClass("vn-has-error"); 9 | } 10 | 11 | vnform.verify_input_with_notice = function( input_item ){ 12 | vnform.clean_all_err_mark(); 13 | var err_msg = vnform.verify_input( input_item ); 14 | 15 | if( err_msg != null ) 16 | dashboard.show_notice('warning',err_msg); 17 | } 18 | 19 | vnform.verify_input = function( input_item ){ 20 | var item = $( input_item ); 21 | var verifyer_str = item.attr('vn_data_verify') 22 | 23 | if( verifyer_str == undefined ){ 24 | return null; 25 | } 26 | 27 | if( item.attr('type') == "checkbox" ){ 28 | return null; 29 | } 30 | 31 | var verifyer = eval( verifyer_str ); 32 | var value = item.val(); 33 | 34 | var err_msg = verifyer( value ); 35 | 36 | if( err_msg != null ){ 37 | vnform.add_err_mark( item ); 38 | return err_msg; 39 | } 40 | 41 | return null; 42 | } 43 | 44 | vnform.verify_form = function( form_id ){ 45 | console.log('verify form:',form_id); 46 | vnform.clean_all_err_mark(); 47 | 48 | var inputs = $('#' + form_id).find("input,select,textarea"); 49 | for( var i=0; i < inputs.length; i++ ){ 50 | var err_msg = vnform.verify_input( inputs[i] ); 51 | 52 | if( err_msg != null ) 53 | return err_msg; 54 | } 55 | 56 | return null; 57 | } 58 | 59 | vnform.get_data = function( form_id ){ 60 | 61 | var data = {} 62 | var form_obj = $('#'+form_id); 63 | var form_data_getter = form_obj.attr('vn_data_getter'); 64 | if( form_data_getter != null ){ 65 | data = eval( form_data_getter )( ); 66 | return data; 67 | } 68 | 69 | var inputs = $('#' + form_id).find("input:visible,select:visible,textarea:visible"); 70 | 71 | for( var i=0; i < inputs.length; i++ ){ 72 | var item = $(inputs[i]); 73 | var name = item.attr('name'); 74 | var tagName = item.prop('tagName').toLowerCase(); 75 | if( tagName == "input" || tagName == 'textarea' ){ 76 | 77 | if( item.attr('type') == "checkbox" ){ 78 | var config_group = item.attr('config_group'); 79 | if( config_group != null ){ 80 | if( data[config_group] == null ){ 81 | data[config_group] = []; 82 | } 83 | 84 | if( item.prop( "checked" ) ){ 85 | data[config_group].push( name ); 86 | } 87 | }else{ 88 | if( item.prop( "checked" )){ 89 | data[name] = true; 90 | }else{ 91 | data[name] = false; 92 | } 93 | } 94 | }else{ 95 | var getter = item.attr['vn_data_getter']; 96 | var val = null; 97 | if( getter != null){ 98 | val = eval( getter )(); 99 | }else{ 100 | val = item.val(); 101 | } 102 | data[name] = val; 103 | } 104 | 105 | }else if( item.prop('tagName').toLowerCase() == "select" ){ 106 | data[ name ] = item.val(); 107 | } 108 | } 109 | 110 | //console.log('output data:',data); 111 | return data; 112 | } 113 | 114 | vnform.set_data = function( form_id, data ){ 115 | 116 | var form_obj = $('#'+form_id); 117 | var form_data_setter = form_obj.attr('vn_data_setter'); 118 | if( form_data_setter != null ){ 119 | eval( form_data_setter )( data ); 120 | return; 121 | } 122 | 123 | var inputs = $('#'+form_id).find("input,textarea,select"); 124 | for( var i=0; i < inputs.length; i++ ){ 125 | var item = $(inputs[i]); 126 | var name = item.attr('name'); 127 | var tagName = item.prop('tagName').toLowerCase(); 128 | //console.log('process item',item) 129 | //console.log('name',name) 130 | if( tagName == "input" || tagName == 'textarea' ){ 131 | if( item.attr('type') == "checkbox" ){ 132 | var config_group = item.attr('config_group'); 133 | if( config_group != null && data[config_group] != null ){ 134 | if( data[config_group].indexOf( name ) >= 0 ){ 135 | item.prop('checked',true); 136 | }else{ 137 | item.prop('checked',false); 138 | } 139 | }else{ 140 | if( data[name] == true ){ 141 | item.prop('checked',true); 142 | }else{ 143 | item.prop('checked',false); 144 | } 145 | } 146 | }else{ 147 | item.val( data[name] ); 148 | } 149 | 150 | }else if( item.prop('tagName').toLowerCase() == "select" ){ 151 | item.val( data[ item.attr('name') ] ); 152 | } 153 | } 154 | util.sync_vue_model( '#' + form_id ); 155 | } 156 | 157 | vnform.reset = function(form_id){ 158 | var form_obj = $('#'+form_id); 159 | var form_data_resetter = form_obj.attr('vn_data_resetter'); 160 | if( form_data_resetter != null ){ 161 | eval( form_data_resetter )(); 162 | return; 163 | } 164 | util.reset_input_area( '#' + form_id ); 165 | } 166 | 167 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/backend_proxy.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- @Date : 2016-04-20 3 | -- @Author : Alexa (AlexaZhou@163.com) 4 | -- @Link : 5 | -- @Disc : proxy_pass backend for verynginx 6 | 7 | local _M = {} 8 | 9 | local VeryNginxConfig = require "VeryNginxConfig" 10 | local request_tester = require "request_tester" 11 | local resolver = require "resty.dns.resolver" 12 | local math = require "math" 13 | local util = require "util" 14 | 15 | math.randomseed(ngx.time()) 16 | 17 | 18 | function _M.find_node( upstream ) 19 | 20 | local node_list = upstream['node'] 21 | local balance_method = upstream['method'] 22 | local rate_sum = 0 23 | 24 | for name,node in pairs( node_list ) do 25 | rate_sum = rate_sum + tonumber(node['weight']) 26 | end 27 | --ngx.log( ngx.ERR, 'rate_sum',rate_sum) 28 | 29 | local p = nil 30 | if balance_method == 'ip_hash' then 31 | p = math.fmod( ngx.crc32_short( ngx.var.remote_addr), rate_sum) + 1 32 | else 33 | p = math.random( rate_sum ) 34 | end 35 | 36 | --ngx.log( ngx.ERR, 'p',p) 37 | 38 | for name,node in pairs( node_list ) do 39 | local tmp = tonumber(node['weight']) 40 | if p <= tmp then 41 | return node 42 | else 43 | p = p - tmp 44 | end 45 | end 46 | end 47 | 48 | function _M.filter() 49 | 50 | if VeryNginxConfig.configs["proxy_pass_enable"] ~= true then 51 | return 52 | end 53 | 54 | local matcher_list = VeryNginxConfig.configs['matcher'] 55 | local upstream_list = VeryNginxConfig.configs['backend_upstream'] 56 | 57 | for i, rule in ipairs( VeryNginxConfig.configs["proxy_pass_rule"] ) do 58 | local enable = rule['enable'] 59 | local matcher = matcher_list[ rule['matcher'] ] 60 | if enable == true and request_tester.test( matcher ) == true then 61 | --ngx.log(ngx.STDERR,'upstream:',rule['upstream']) 62 | 63 | local upstream = upstream_list[ rule['upstream'] ] 64 | local node = _M.find_node( upstream ) 65 | 66 | ngx.var.vn_proxy_scheme = node['scheme'] 67 | ngx.var.vn_proxy_host = node['host'] 68 | 69 | if node['port'] == '' then 70 | if node['scheme'] == 'http' then 71 | ngx.var.vn_proxy_port = 80 72 | elseif node['scheme'] == 'https' then 73 | ngx.var.vn_proxy_port = 443 74 | end 75 | else 76 | ngx.var.vn_proxy_port = tonumber( node['port'] ) 77 | end 78 | 79 | --set vn_header_host 80 | if rule['proxy_host'] ~= '' then 81 | ngx.var.vn_header_host = rule['proxy_host'] 82 | else 83 | ngx.var.vn_header_host = ngx.var.host 84 | end 85 | 86 | util.ngx_ctx_dump() 87 | ngx.var.vn_exec_flag = '1' --use the var as a mark, so that lua can know that's a inside redirect 88 | return ngx.exec('@vn_proxy') --will jump out at the exec, so the return not run in fact 89 | end 90 | end 91 | end 92 | 93 | return _M 94 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/backend_static.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- @Date : 2016-04-20 3 | -- @Author : Alexa (AlexaZhou@163.com) 4 | -- @Link : 5 | -- @Disc : static backend for verynginx 6 | 7 | local _M = {} 8 | 9 | local VeryNginxConfig = require "VeryNginxConfig" 10 | local request_tester = require "request_tester" 11 | local util = require "util" 12 | 13 | function _M.filter() 14 | 15 | if VeryNginxConfig.configs["static_file_enable"] ~= true then 16 | return 17 | end 18 | 19 | local matcher_list = VeryNginxConfig.configs['matcher'] 20 | for i, rule in ipairs( VeryNginxConfig.configs["static_file_rule"] ) do 21 | local enable = rule['enable'] 22 | local matcher = matcher_list[ rule['matcher'] ] 23 | if enable == true and request_tester.test( matcher ) == true then 24 | ngx.var.vn_static_root = rule['root'] 25 | ngx.var.vn_static_expires = rule['expires'] 26 | ngx.var.vn_exec_flag = '1'-- use the var as a mark, so that lua can know that's a inside redirect 27 | util.ngx_ctx_dump() 28 | return ngx.exec('@vn_static') -- will jump out at the exec 29 | end 30 | end 31 | end 32 | 33 | return _M 34 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/browser_verify.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- @Date : 2016-02-24 3 | -- @Author : Alexa (AlexaZhou@163.com) 4 | -- @Link : 5 | -- @Disc : verify that the remote client is a browser 6 | 7 | local _M = {} 8 | 9 | local VeryNginxConfig = require "VeryNginxConfig" 10 | local request_tester = require "request_tester" 11 | local encrypt_seed = require "encrypt_seed" 12 | local util = require "util" 13 | 14 | _M.verify_javascript_html = nil 15 | 16 | function _M.sign( mark ) 17 | local ua = ngx.var.http_user_agent 18 | local forwarded = ngx.var.http_x_forwarded_for 19 | 20 | if ua == nil then 21 | ua = '' 22 | end 23 | 24 | if forwarded == nil then 25 | forwarded = '' 26 | end 27 | 28 | local sign = ngx.md5( 'VN' .. ngx.var.remote_addr .. forwarded .. ua .. mark .. encrypt_seed.get_seed() ) 29 | return sign 30 | end 31 | 32 | function _M.verify_cookie() 33 | local sign = _M.sign('cookie') 34 | if ngx.var.http_cookie ~= nil then 35 | if string.find( ngx.var.http_cookie , sign) ~= nil then 36 | return 37 | end 38 | end 39 | 40 | local cookie_prefix = VeryNginxConfig.configs['cookie_prefix'] 41 | ngx.header["Set-Cookie"] = cookie_prefix .. "_sign_cookie=" .. sign .. '; path=/' 42 | 43 | if ngx.var.args ~= nil then 44 | ngx.redirect( ngx.var.scheme.."://"..ngx.var.http_host..ngx.var.uri.."?"..ngx.var.query_string , ngx.HTTP_MOVED_TEMPORARILY) 45 | else 46 | ngx.redirect( ngx.var.scheme.."://"..ngx.var.http_host..ngx.var.uri , ngx.HTTP_MOVED_TEMPORARILY) 47 | end 48 | end 49 | 50 | function _M.verify_javascript() 51 | local sign = _M.sign('javascript') 52 | if ngx.var.http_cookie ~= nil then 53 | if string.find( ngx.var.http_cookie , sign) ~= nil then 54 | return 55 | end 56 | end 57 | 58 | if _M.verify_javascript_html == nil then 59 | local path = VeryNginxConfig.home_path() .."/support/verify_javascript.html" 60 | f = io.open( path, 'r' ) 61 | if f ~= nil then 62 | _M.verify_javascript_html = f:read("*all") 63 | f:close() 64 | end 65 | end 66 | 67 | local cookie_prefix = VeryNginxConfig.configs['cookie_prefix'] 68 | 69 | local redirect_to = nil 70 | local html = _M.verify_javascript_html 71 | 72 | html = string.gsub( html,'INFOCOOKIE',sign ) 73 | html = string.gsub( html,'COOKIEPREFIX',cookie_prefix ) 74 | 75 | if ngx.var.args ~= nil then 76 | redirect_to = ngx.var.scheme.."://"..ngx.var.http_host..ngx.var.uri.."?"..ngx.var.args , ngx.HTTP_MOVED_TEMPORARILY 77 | else 78 | redirect_to = ngx.var.scheme.."://"..ngx.var.http_host..ngx.var.uri , ngx.HTTP_MOVED_TEMPORARILY 79 | end 80 | 81 | html = util.string_replace( html,'INFOURI',redirect_to, 1 ) 82 | 83 | ngx.header.content_type = "text/html" 84 | ngx.header['cache-control'] = "no-cache, no-store, must-revalidate" 85 | ngx.header['pragma'] = "no-cache" 86 | ngx.header['expires'] = "0" 87 | ngx.header.charset = "utf-8" 88 | ngx.say( html ) 89 | 90 | ngx.exit(200) 91 | end 92 | 93 | function _M.filter() 94 | if VeryNginxConfig.configs["browser_verify_enable"] ~= true then 95 | return 96 | end 97 | 98 | local matcher_list = VeryNginxConfig.configs['matcher'] 99 | for i,rule in ipairs( VeryNginxConfig.configs["browser_verify_rule"] ) do 100 | local enable = rule['enable'] 101 | local matcher = matcher_list[ rule['matcher'] ] 102 | if enable == true and request_tester.test( matcher ) == true then 103 | local verify_cookie,verify_javascript = false,false 104 | 105 | for idx,verify_type in ipairs( rule['type']) do 106 | if verify_type == 'cookie' then 107 | verify_cookie = true 108 | elseif verify_type == 'javascript' then 109 | verify_javascript = true 110 | end 111 | end 112 | 113 | if verify_cookie == true then 114 | _M.verify_cookie() 115 | end 116 | 117 | if verify_javascript == true then 118 | _M.verify_javascript() 119 | end 120 | 121 | return 122 | end 123 | end 124 | end 125 | 126 | return _M 127 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/cookie.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) 2013 Jiale Zhi (calio), Cloudflare Inc. 2 | -- See RFC6265 http://tools.ietf.org/search/rfc6265 3 | -- require "luacov" 4 | 5 | local type = type 6 | local byte = string.byte 7 | local sub = string.sub 8 | local format = string.format 9 | local log = ngx.log 10 | local ERR = ngx.ERR 11 | local ngx_header = ngx.header 12 | 13 | local EQUAL = byte("=") 14 | local SEMICOLON = byte(";") 15 | local SPACE = byte(" ") 16 | local HTAB = byte("\t") 17 | 18 | -- table.new(narr, nrec) 19 | local ok, new_tab = pcall(require, "table.new") 20 | if not ok then 21 | new_tab = function () return {} end 22 | end 23 | 24 | local ok, clear_tab = pcall(require, "table.clear") 25 | if not ok then 26 | clear_tab = function(tab) for k, _ in pairs(tab) do tab[k] = nil end end 27 | end 28 | 29 | local _M = new_tab(0, 2) 30 | 31 | _M._VERSION = '0.01' 32 | 33 | 34 | local function get_cookie_table(text_cookie) 35 | if type(text_cookie) ~= "string" then 36 | log(ERR, format("expect text_cookie to be \"string\" but found %s", 37 | type(text_cookie))) 38 | return {} 39 | end 40 | 41 | local EXPECT_KEY = 1 42 | local EXPECT_VALUE = 2 43 | local EXPECT_SP = 3 44 | 45 | local n = 0 46 | local len = #text_cookie 47 | 48 | for i=1, len do 49 | if byte(text_cookie, i) == SEMICOLON then 50 | n = n + 1 51 | end 52 | end 53 | 54 | local cookie_table = new_tab(0, n + 1) 55 | 56 | local state = EXPECT_SP 57 | local i = 1 58 | local j = 1 59 | local key, value 60 | 61 | while j <= len do 62 | if state == EXPECT_KEY then 63 | if byte(text_cookie, j) == EQUAL then 64 | key = sub(text_cookie, i, j - 1) 65 | state = EXPECT_VALUE 66 | i = j + 1 67 | end 68 | elseif state == EXPECT_VALUE then 69 | if byte(text_cookie, j) == SEMICOLON 70 | or byte(text_cookie, j) == SPACE 71 | or byte(text_cookie, j) == HTAB 72 | then 73 | value = sub(text_cookie, i, j - 1) 74 | cookie_table[key] = value 75 | 76 | key, value = nil, nil 77 | state = EXPECT_SP 78 | i = j + 1 79 | end 80 | elseif state == EXPECT_SP then 81 | if byte(text_cookie, j) ~= SPACE 82 | and byte(text_cookie, j) ~= HTAB 83 | then 84 | state = EXPECT_KEY 85 | i = j 86 | j = j - 1 87 | end 88 | end 89 | j = j + 1 90 | end 91 | 92 | if key ~= nil and value == nil then 93 | cookie_table[key] = sub(text_cookie, i) 94 | end 95 | 96 | return cookie_table 97 | end 98 | 99 | function _M.new(self) 100 | local _cookie = ngx.var.http_cookie 101 | --if not _cookie then 102 | --return nil, "no cookie found in current request" 103 | --end 104 | return setmetatable({ _cookie = _cookie, set_cookie_table = new_tab(4, 0) }, 105 | { __index = self }) 106 | end 107 | 108 | function _M.get(self, key) 109 | if not self._cookie then 110 | return nil, "no cookie found in the current request" 111 | end 112 | if self.cookie_table == nil then 113 | self.cookie_table = get_cookie_table(self._cookie) 114 | end 115 | 116 | return self.cookie_table[key] 117 | end 118 | 119 | function _M.get_all(self) 120 | if not self._cookie then 121 | return nil, "no cookie found in the current request" 122 | end 123 | 124 | if self.cookie_table == nil then 125 | self.cookie_table = get_cookie_table(self._cookie) 126 | end 127 | 128 | return self.cookie_table 129 | end 130 | 131 | local function bake(cookie) 132 | if not cookie.key or not cookie.value then 133 | return nil, 'missing cookie field "key" or "value"' 134 | end 135 | 136 | if cookie["max-age"] then 137 | cookie.max_age = cookie["max-age"] 138 | end 139 | local str = cookie.key .. "=" .. cookie.value 140 | .. (cookie.expires and "; Expires=" .. cookie.expires or "") 141 | .. (cookie.max_age and "; Max-Age=" .. cookie.max_age or "") 142 | .. (cookie.domain and "; Domain=" .. cookie.domain or "") 143 | .. (cookie.path and "; Path=" .. cookie.path or "") 144 | .. (cookie.secure and "; Secure" or "") 145 | .. (cookie.httponly and "; HttpOnly" or "") 146 | .. (cookie.extension and "; " .. cookie.extension or "") 147 | return str 148 | end 149 | 150 | function _M.set(self, cookie) 151 | local cookie_str, err = bake(cookie) 152 | if not cookie_str then 153 | return nil, err 154 | end 155 | 156 | local set_cookie = ngx_header['Set-Cookie'] 157 | local set_cookie_type = type(set_cookie) 158 | local t = self.set_cookie_table 159 | clear_tab(t) 160 | 161 | if set_cookie_type == "string" then 162 | -- only one cookie has been setted 163 | if set_cookie ~= cookie_str then 164 | t[1] = set_cookie 165 | t[2] = cookie_str 166 | ngx_header['Set-Cookie'] = t 167 | end 168 | elseif set_cookie_type == "table" then 169 | -- more than one cookies has been setted 170 | local size = #set_cookie 171 | 172 | -- we can not set cookie like ngx.header['Set-Cookie'][3] = val 173 | -- so create a new table, copy all the values, and then set it back 174 | for i=1, size do 175 | t[i] = ngx_header['Set-Cookie'][i] 176 | if t[i] == cookie_str then 177 | -- new cookie is duplicated 178 | return true 179 | end 180 | end 181 | t[size + 1] = cookie_str 182 | ngx_header['Set-Cookie'] = t 183 | else 184 | -- no cookie has been setted 185 | ngx_header['Set-Cookie'] = cookie_str 186 | end 187 | return true 188 | end 189 | 190 | return _M 191 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/encrypt_seed.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- -- @Date : 2016-02-02 13:37 3 | -- -- @Author : Alexa (AlexaZhou@163.com) 4 | -- -- @Link : 5 | -- -- @Disc : auto generate encrypt_seed 6 | 7 | local VeryNginxConfig = require "VeryNginxConfig" 8 | local dkjson = require "dkjson" 9 | 10 | 11 | local _M = {} 12 | _M.seed = nil 13 | 14 | function _M.get_seed() 15 | 16 | --return seed from memory 17 | if _M.seed ~= nil then 18 | return _M.seed 19 | end 20 | 21 | --return saved seed 22 | local seed_path = VeryNginxConfig.home_path() .. "/configs/encrypt_seed.json" 23 | 24 | local file = io.open( seed_path, "r") 25 | if file ~= nil then 26 | local data = file:read("*all"); 27 | file:close(); 28 | local tmp = dkjson.decode( data ) 29 | 30 | _M.seed = tmp['encrypt_seed'] 31 | 32 | return _M.seed 33 | end 34 | 35 | 36 | --if no saved seed, generate a new seed and saved 37 | _M.seed = ngx.md5( ngx.now() ) 38 | local new_seed_json = dkjson.encode( { ["encrypt_seed"]= _M.seed }, {indent=true} ) 39 | local file,err = io.open( seed_path, "w") 40 | 41 | if file ~= nil then 42 | file:write( new_seed_json ) 43 | file:close() 44 | return _M.seed 45 | else 46 | ngx.log(ngx.STDERR, 'save encrypt_seed failed' ) 47 | return '' 48 | end 49 | 50 | end 51 | 52 | function _M.generate() 53 | end 54 | 55 | return _M 56 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/filter.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- @Date : 2016-01-02 00:46 3 | -- @Author : Alexa (AlexaZhou@163.com) 4 | -- @Link : 5 | -- @Disc : filter request'uri maybe attack 6 | 7 | local _M = {} 8 | 9 | local VeryNginxConfig = require "VeryNginxConfig" 10 | local request_tester = require "request_tester" 11 | 12 | 13 | function _M.filter() 14 | 15 | if VeryNginxConfig.configs["filter_enable"] ~= true then 16 | return 17 | end 18 | 19 | local matcher_list = VeryNginxConfig.configs['matcher'] 20 | local response_list = VeryNginxConfig.configs['response'] 21 | local response = nil 22 | 23 | for i,rule in ipairs( VeryNginxConfig.configs["filter_rule"] ) do 24 | local enable = rule['enable'] 25 | local matcher = matcher_list[ rule['matcher'] ] 26 | if enable == true and request_tester.test( matcher ) == true then 27 | local action = rule['action'] 28 | if action == 'accept' then 29 | return 30 | else 31 | if rule['response'] ~= nil then 32 | ngx.status = tonumber( rule['code'] ) 33 | response = response_list[rule['response']] 34 | if response ~= nil then 35 | ngx.header.content_type = response['content_type'] 36 | ngx.say( response['body'] ) 37 | ngx.exit( ngx.HTTP_OK ) 38 | end 39 | else 40 | ngx.exit( tonumber( rule['code'] ) ) 41 | end 42 | end 43 | end 44 | end 45 | end 46 | 47 | return _M 48 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/frequency_limit.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- @Date : 2016-04-20 23:13 3 | -- @Author : Alexa (AlexaZhou@163.com) 4 | -- @Link : 5 | -- @Disc : request frequency limit 6 | 7 | local _M = {} 8 | 9 | 10 | local VeryNginxConfig = require "VeryNginxConfig" 11 | local request_tester = require "request_tester" 12 | local util = require "util" 13 | 14 | local limit_dict = ngx.shared.frequency_limit 15 | 16 | function _M.filter() 17 | 18 | if VeryNginxConfig.configs["frequency_limit_enable"] ~= true then 19 | return 20 | end 21 | 22 | local matcher_list = VeryNginxConfig.configs['matcher'] 23 | local response_list = VeryNginxConfig.configs['response'] 24 | local response = nil 25 | 26 | for i, rule in ipairs( VeryNginxConfig.configs["frequency_limit_rule"] ) do 27 | local enable = rule['enable'] 28 | local matcher = matcher_list[ rule['matcher'] ] 29 | if enable == true and request_tester.test( matcher ) == true then 30 | 31 | local key = i 32 | if util.existed( rule['separate'], 'ip' ) then 33 | key = key..'-'..ngx.var.remote_addr 34 | end 35 | 36 | if util.existed( rule['separate'], 'uri' ) then 37 | key = key..'-'..ngx.var.uri 38 | end 39 | 40 | local time = rule['time'] 41 | local count = rule['count'] 42 | local code = rule['code'] 43 | 44 | --ngx.log(ngx.STDERR,'-----'); 45 | --ngx.log(ngx.STDERR,key); 46 | 47 | local count_now = limit_dict:get( key ) 48 | --ngx.log(ngx.STDERR, tonumber(count_now) ); 49 | 50 | if count_now == nil then 51 | limit_dict:set( key, 1, tonumber(time) ) 52 | count_now = 0 53 | end 54 | 55 | limit_dict:incr( key, 1 ) 56 | 57 | if count_now > tonumber(count) then 58 | if rule['response'] ~= nil then 59 | ngx.status = tonumber( rule['code'] ) 60 | response = response_list[rule['response']] 61 | if response ~= nil then 62 | ngx.header.content_type = response['content_type'] 63 | ngx.say( response['body'] ) 64 | end 65 | ngx.exit( ngx.HTTP_OK ) 66 | else 67 | ngx.exit( tonumber( rule['code'] ) ) 68 | end 69 | end 70 | 71 | return 72 | end 73 | end 74 | end 75 | 76 | return _M 77 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/json.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- @Date : 2016-03-08 3 | -- @Author : Alexa (AlexaZhou@163.com) 4 | -- @Link : 5 | -- @Disc : import cjson module , but use dkjson to replace if cjson module is not ready 6 | 7 | local _M = {} 8 | 9 | _M.json = nil 10 | 11 | function _M.import_cjson() 12 | _M.json = require 'cjson' 13 | end 14 | 15 | if pcall( _M.import_cjson ) ~= true then 16 | _M.json = require 'dkjson' 17 | end 18 | 19 | return _M.json 20 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/redirect.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- @Date : 2016-01-02 20:39 3 | -- @Author : Alexa (AlexaZhou@163.com) 4 | -- @Link : 5 | -- @Disc : redirect path 6 | 7 | local _M = {} 8 | 9 | local VeryNginxConfig = require "VeryNginxConfig" 10 | local request_tester = require "request_tester" 11 | 12 | 13 | function _M.run() 14 | 15 | if VeryNginxConfig.configs["redirect_enable"] ~= true then 16 | return 17 | end 18 | 19 | local new_url = nil 20 | local re_gsub = ngx.re.gsub 21 | local ngx_var = ngx.var 22 | local ngx_redirect = ngx.redirect 23 | local ngx_var_uri = ngx_var.uri 24 | local ngx_var_scheme = ngx_var.scheme 25 | local ngx_var_host = ngx_var.host 26 | local matcher_list = VeryNginxConfig.configs['matcher'] 27 | 28 | 29 | for i, rule in ipairs( VeryNginxConfig.configs["redirect_rule"] ) do 30 | local enable = rule['enable'] 31 | local matcher = matcher_list[ rule['matcher'] ] 32 | if enable == true and request_tester.test( matcher ) == true then 33 | replace_re = rule['replace_re'] 34 | if replace_re ~= nil and string.len( replace_re ) > 0 then 35 | new_url = re_gsub( ngx_var_uri, replace_re, rule['to_uri'] ) 36 | else 37 | new_url = rule['to_uri'] 38 | end 39 | 40 | if new_url ~= ngx_var_uri then 41 | 42 | if string.find( new_url, 'http') ~= 1 then 43 | new_url = ngx_var_scheme.."://"..ngx_var_host..new_url 44 | end 45 | 46 | if ngx_var.args ~= nil then 47 | ngx_redirect( new_url.."?"..ngx_var.args , ngx.HTTP_MOVED_TEMPORARILY) 48 | else 49 | ngx_redirect( new_url , ngx.HTTP_MOVED_TEMPORARILY) 50 | end 51 | end 52 | return 53 | end 54 | end 55 | 56 | end 57 | 58 | return _M 59 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/request_tester.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- @Date : 2016-02-06 22:26 3 | -- @Author : Alexa (AlexaZhou@163.com) 4 | -- @Link : 5 | -- @Disc : test a request hit a matcher or not 6 | 7 | local cookie = require "cookie" 8 | 9 | local _M = {} 10 | 11 | local tester = {} 12 | 13 | function _M.test( matcher ) 14 | if matcher == nil then 15 | return false 16 | end 17 | 18 | for name, v in pairs( matcher ) do 19 | if tester[name] ~= nil then 20 | if tester[name]( v ) ~= true then 21 | return false 22 | end 23 | end 24 | end 25 | 26 | return true 27 | end 28 | 29 | --test_var is a basic test method, used by other test method 30 | function _M.test_var( match_operator, match_value, target_var ) 31 | 32 | if match_operator == "=" then 33 | if target_var == match_value then 34 | return true 35 | end 36 | elseif match_operator == "*" then 37 | return true 38 | elseif match_operator == "!=" then 39 | if target_var ~= match_value then 40 | return true 41 | end 42 | elseif match_operator == '≈' then 43 | if type(target_var) == 'string' and ngx.re.find( target_var, match_value, 'isjo' ) ~= nil then 44 | return true 45 | end 46 | elseif match_operator == '!≈' then 47 | if type(target_var) ~= 'string' or ngx.re.find( target_var, match_value, 'isjo' ) == nil then 48 | return true 49 | end 50 | elseif match_operator == 'Exist' then 51 | if target_var ~= nil then 52 | return true 53 | end 54 | elseif match_operator == '!Exist' then 55 | if target_var == nil then 56 | return true 57 | end 58 | elseif match_operator == '!' then 59 | if target_var == nil then 60 | return true 61 | end 62 | end 63 | 64 | return false 65 | end 66 | 67 | 68 | --test a group of var in table with a condition 69 | function _M.test_many_var( var_table, condition ) 70 | 71 | local find = ngx.re.find 72 | local test_var = _M.test_var 73 | 74 | local name_operator = condition['name_operator'] 75 | local name_value = condition['name_value'] 76 | local operator = condition['operator'] 77 | local value = condition['value'] 78 | 79 | -- Insert !Exist Check here as it is only applied to operator 80 | if operator == '!Exist' then 81 | for k, v in pairs(var_table) do 82 | if test_var ( name_operator, name_value, k ) == true then 83 | return false 84 | end 85 | end 86 | return true 87 | else 88 | -- Normal process 89 | for k, v in pairs(var_table) do 90 | if test_var( name_operator, name_value, k ) == true then 91 | if test_var( operator, value, v ) == true then -- if any one value match the condition, means the matcher has been hited 92 | return true 93 | end 94 | end 95 | end 96 | end 97 | 98 | return false 99 | end 100 | 101 | function _M.test_uri( condition ) 102 | local uri = ngx.var.uri; 103 | return _M.test_var( condition['operator'], condition['value'], uri ) 104 | end 105 | 106 | function _M.test_ip( condition ) 107 | local remote_addr = ngx.var.remote_addr 108 | return _M.test_var( condition['operator'], condition['value'], remote_addr ) 109 | end 110 | 111 | function _M.test_ua( condition ) 112 | local http_user_agent = ngx.var.http_user_agent; 113 | return _M.test_var( condition['operator'], condition['value'], http_user_agent ) 114 | end 115 | 116 | function _M.test_referer( condition ) 117 | local http_referer = ngx.var.http_referer; 118 | return _M.test_var( condition['operator'], condition['value'], http_referer ) 119 | end 120 | 121 | --uncompleted 122 | function _M.test_method( condition ) 123 | local method_name = ngx.req.get_method() 124 | return _M.test_var( condition['operator'], condition['value'], method_name ) 125 | end 126 | 127 | function _M.test_args( condition ) 128 | local find = ngx.re.find 129 | local test_var = _M.test_var 130 | 131 | local name_operator = condition['name_operator'] 132 | local name_value = condition['name_value'] 133 | local operator = condition['operator'] 134 | local value = condition['value'] 135 | 136 | --handle args behind uri 137 | for k,v in pairs( ngx.req.get_uri_args()) do 138 | if test_var( name_operator, name_value, k ) == true then 139 | if type(v) == "table" then 140 | for arg_idx,arg_value in ipairs(v) do 141 | if test_var( operator, value, arg_value ) == true then 142 | return true 143 | end 144 | end 145 | else 146 | if test_var( operator, value, v ) == true then 147 | return true 148 | end 149 | end 150 | end 151 | end 152 | 153 | ngx.req.read_body() 154 | --ensure body has not be cached into temp file 155 | if ngx.req.get_body_file() ~= nil then 156 | return false 157 | end 158 | 159 | local body_args,err = ngx.req.get_post_args() 160 | if body_args == nil then 161 | ngx.say("failed to get post args: ", err) 162 | return false 163 | end 164 | 165 | --check args in body 166 | for k,v in pairs( body_args ) do 167 | if test_var( name_operator, name_value, k ) == true then 168 | if type(v) == "table" then 169 | for arg_idx,arg_value in ipairs(v) do 170 | if test_var( operator, value, arg_value ) == true then 171 | return true 172 | end 173 | end 174 | else 175 | if test_var( operator, value, v ) == true then 176 | return true 177 | end 178 | end 179 | end 180 | end 181 | 182 | return false 183 | end 184 | 185 | function _M.test_host( condition ) 186 | local hostname = ngx.var.host 187 | return _M.test_var( condition['operator'], condition['value'], hostname ) 188 | end 189 | 190 | function _M.test_header( condition ) 191 | local header_table = ngx.req.get_headers() 192 | return _M.test_many_var( header_table, condition ) 193 | end 194 | 195 | function _M.test_cookie( condition ) 196 | local cookie_obj, err = cookie:new() 197 | local cookie_table = cookie_obj:get_all() 198 | 199 | if cookie_table == nil then 200 | cookie_table = {} 201 | end 202 | return _M.test_many_var( cookie_table, condition ) 203 | end 204 | 205 | tester["URI"] = _M.test_uri 206 | tester["IP"] = _M.test_ip 207 | tester["UserAgent"] = _M.test_ua 208 | tester["Method"] = _M.test_method 209 | tester["Args"] = _M.test_args 210 | tester["Referer"] = _M.test_referer 211 | tester["Host"] = _M.test_host 212 | tester["Header"] = _M.test_header 213 | tester["Cookie"] = _M.test_cookie 214 | 215 | 216 | return _M 217 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/router.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- -- @Date : 2016-01-02 00:35 3 | -- -- @Author : Alexa (AlexaZhou@163.com) 4 | -- -- @Link : 5 | -- -- @Disc : url router of verynginx's control panel 6 | 7 | local summary = require "summary" 8 | local status = require "status" 9 | local cookie = require "cookie" 10 | local VeryNginxConfig = require "VeryNginxConfig" 11 | local encrypt_seed = require "encrypt_seed" 12 | local json = require "json" 13 | local util = require "util" 14 | 15 | local _M = {} 16 | 17 | _M.url_route = {} 18 | _M.mime_type = {} 19 | _M.mime_type['.js'] = "application/x-javascript" 20 | _M.mime_type['.css'] = "text/css" 21 | _M.mime_type['.html'] = "text/html" 22 | 23 | 24 | function _M.filter() 25 | local method = ngx.req.get_method() 26 | local uri = ngx.var.uri 27 | local base_uri = VeryNginxConfig.configs['base_uri'] 28 | local dashboard_host = VeryNginxConfig.configs['dashboard_host'] 29 | 30 | if dashboard_host ~= '' then 31 | if ngx.var.host ~= dashboard_host then 32 | return 33 | end 34 | end 35 | 36 | if string.find( uri, base_uri ) == 1 then 37 | local path = string.sub( uri, string.len( base_uri ) + 1 ) 38 | 39 | for i,item in ipairs( _M.route_table ) do 40 | if method == item['method'] and path == item['path'] then 41 | ngx.header.content_type = "application/json" 42 | ngx.header.charset = "utf-8" 43 | 44 | if item['auth'] == true and _M.check_session() == false then 45 | local info = json.encode({["ret"]="failed",["message"]="need login"}) 46 | ngx.status = 400 47 | ngx.say( info ) 48 | else 49 | ngx.say( item['handle']() ) 50 | end 51 | ngx.exit( ngx.HTTP_OK ) 52 | end 53 | end 54 | 55 | ngx.req.set_uri( path ) 56 | ngx.var.vn_static_root = VeryNginxConfig.home_path() .."/dashboard" 57 | ngx.var.vn_exec_flag = '1'-- use the var as a mark, so that lua can know that's a inside redirect 58 | util.ngx_ctx_dump() 59 | return ngx.exec('@vn_static') -- will jump out at the exec 60 | end 61 | end 62 | 63 | function _M.check_session() 64 | -- get all cookies 65 | local user, session 66 | 67 | local cookie_obj, err = cookie:new() 68 | local fields = cookie_obj:get_all() 69 | if not fields then 70 | return false 71 | end 72 | 73 | local cookie_prefix = VeryNginxConfig.configs['cookie_prefix'] 74 | 75 | user = fields[ cookie_prefix .. '_user'] 76 | session = fields[ cookie_prefix .. '_session'] 77 | 78 | if user == nil or session == nil then 79 | return false 80 | end 81 | 82 | for i,v in ipairs( VeryNginxConfig.configs['admin'] ) do 83 | if v["user"] == user and v["enable"] == true then 84 | if session == ngx.md5( encrypt_seed.get_seed()..v["user"]) then 85 | return true 86 | else 87 | return false 88 | end 89 | end 90 | end 91 | 92 | return false 93 | end 94 | 95 | 96 | function _M.login() 97 | local args = util.get_request_args() 98 | 99 | for i,v in ipairs( VeryNginxConfig.configs['admin'] ) do 100 | if v['user'] == args['user'] and v['password'] == args["password"] and v['enable'] == true then 101 | local cookie_prefix = VeryNginxConfig.configs['cookie_prefix'] 102 | local session = ngx.md5(encrypt_seed.get_seed()..v['user']) 103 | 104 | local data = {} 105 | data['ret'] = 'success' 106 | data['cookies'] = { 107 | [cookie_prefix .. '_session'] = session, 108 | [cookie_prefix .. '_user'] = v['user'], 109 | } 110 | return json.encode( data ) 111 | end 112 | end 113 | 114 | ngx.status = 400 115 | return json.encode({["ret"]="failed",["message"]="Username and password not match"}) 116 | end 117 | 118 | _M.route_table = { 119 | { ['method'] = "POST", ['auth']= false, ["path"] = "/login", ['handle'] = _M.login }, 120 | { ['method'] = "GET", ['auth']= true, ["path"] = "/summary", ['handle'] = summary.report }, 121 | { ['method'] = "GET", ['auth']= true, ["path"] = "/status", ['handle'] = status.report }, 122 | { ['method'] = "POST", ['auth']= true, ["path"] = "/status/clear", ['handle'] = summary.clear }, 123 | { ['method'] = "GET", ['auth']= true, ["path"] = "/config", ['handle'] = VeryNginxConfig.report }, 124 | { ['method'] = "POST", ['auth']= true, ["path"] = "/config", ['handle'] = VeryNginxConfig.set }, 125 | { ['method'] = "GET", ['auth']= true, ["path"] = "/loadconfig", ['handle'] = VeryNginxConfig.load_from_file }, 126 | } 127 | 128 | 129 | 130 | return _M 131 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/scheme_lock.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- @Date : 2015-10-25 15:56:46 3 | -- @Author : Alexa (AlexaZhou@163.com) 4 | -- @Link : 5 | -- @Disc : redirect request to right scheme 6 | 7 | local _M = {} 8 | 9 | 10 | local VeryNginxConfig = require "VeryNginxConfig" 11 | local request_tester = require "request_tester" 12 | 13 | 14 | function scheme_judge(uri) 15 | local ngx_re_find = ngx.re.find 16 | local matcher_list = VeryNginxConfig.configs['matcher'] 17 | 18 | for i, rule in ipairs( VeryNginxConfig.configs["scheme_lock_rule"] ) do 19 | local enable = rule['enable'] 20 | local matcher = matcher_list[ rule['matcher'] ] 21 | if enable == true and request_tester.test( matcher ) == true then 22 | return rule['scheme'] 23 | end 24 | end 25 | return 'none' 26 | end 27 | 28 | function _M.run() 29 | 30 | if VeryNginxConfig.configs["scheme_lock_enable"] ~= true then 31 | return 32 | end 33 | 34 | local ngx_var = ngx.var 35 | local scheme = scheme_judge( ngx_var.uri ) 36 | if scheme == "none" or scheme == ngx_var.scheme then 37 | return 38 | end 39 | 40 | -- Used on VeryNginx behind Proxy situation 41 | if scheme == ngx.req.get_headers()["X-Forwarded-Proto"] then 42 | -- ngx.log(ngx.STDERR, "Compare the protocol from more frontend level proxy, ", ngx.req.get_headers()["X-Forwarded-Protol"]) 43 | return 44 | end 45 | 46 | if ngx_var.args ~= nil then 47 | ngx.redirect( scheme.."://"..ngx_var.host..ngx_var.uri.."?"..ngx_var.args , ngx.HTTP_MOVED_TEMPORARILY) 48 | else 49 | ngx.redirect( scheme.."://"..ngx_var.host..ngx_var.uri , ngx.HTTP_MOVED_TEMPORARILY) 50 | end 51 | end 52 | 53 | return _M 54 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/status.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- -- @Date : 2015-01-27 05:56 3 | -- -- @Author : Alexa (AlexaZhou@163.com) 4 | -- -- @Link : 5 | -- -- @Disc : record nginx infomation 6 | 7 | local json = require "json" 8 | 9 | local _M = {} 10 | 11 | local KEY_STATUS_INIT = "I_" 12 | 13 | local KEY_START_TIME = "G_" 14 | 15 | local KEY_TOTAL_COUNT = "F_" 16 | local KEY_TOTAL_COUNT_SUCCESS = "H_" 17 | 18 | local KEY_TRAFFIC_READ = "J_" 19 | local KEY_TRAFFIC_WRITE = "K_" 20 | 21 | local KEY_TIME_TOTAL = "L_" 22 | 23 | function _M.init() 24 | 25 | local ok, err = ngx.shared.status:add( KEY_STATUS_INIT,true ) 26 | if ok then 27 | ngx.shared.status:set( KEY_START_TIME, ngx.time() ) 28 | ngx.shared.status:set( KEY_TOTAL_COUNT, 0 ) 29 | ngx.shared.status:set( KEY_TOTAL_COUNT_SUCCESS, 0 ) 30 | 31 | ngx.shared.status:set( KEY_TRAFFIC_READ, 0 ) 32 | ngx.shared.status:set( KEY_TRAFFIC_WRITE, 0 ) 33 | 34 | ngx.shared.status:set( KEY_TIME_TOTAL, 0 ) 35 | end 36 | 37 | end 38 | 39 | --add global count info 40 | function _M.log() 41 | ngx.shared.status:incr( KEY_TOTAL_COUNT, 1 ) 42 | 43 | if tonumber(ngx.var.status) < 400 then 44 | ngx.shared.status:incr( KEY_TOTAL_COUNT_SUCCESS, 1 ) 45 | end 46 | 47 | ngx.shared.status:incr( KEY_TRAFFIC_READ, ngx.var.request_length) 48 | ngx.shared.status:incr( KEY_TRAFFIC_WRITE, ngx.var.bytes_sent ) 49 | ngx.shared.status:incr( KEY_TIME_TOTAL, ngx.var.request_time ) 50 | 51 | end 52 | 53 | function _M.report() 54 | 55 | local report = {} 56 | report['request_all_count'] = ngx.shared.status:get( KEY_TOTAL_COUNT ) 57 | report['request_success_count'] = ngx.shared.status:get( KEY_TOTAL_COUNT_SUCCESS ) 58 | report['time'] = ngx.now() 59 | report['boot_time'] = ngx.shared.status:get( KEY_START_TIME ) 60 | report['response_time_total'] = ngx.shared.status:get( KEY_TIME_TOTAL ) 61 | report['connections_active'] = ngx.var.connections_active 62 | report['connections_reading'] = ngx.var.connections_reading 63 | report['connections_writing'] = ngx.var.connections_writing 64 | report['connections_waiting'] = ngx.var.connections_waiting 65 | 66 | report['traffic_read'] = ngx.shared.status:get( KEY_TRAFFIC_READ ) 67 | report['traffic_write'] = ngx.shared.status:get( KEY_TRAFFIC_WRITE ) 68 | 69 | report['ret'] = 'success' 70 | 71 | return json.encode( report ) 72 | 73 | end 74 | 75 | return _M 76 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/summary.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- -- @Date : 2015-12-26 23:58 3 | -- -- @Author : Alexa (AlexaZhou@163.com) 4 | -- -- @Link : 5 | -- -- @Disc : summary all the request 6 | 7 | local json = require "json" 8 | local VeryNginxConfig = require "VeryNginxConfig" 9 | local util = require "util" 10 | local request_tester = require "request_tester" 11 | 12 | local _M = {} 13 | 14 | local KEY_SUMMARY_REFRESHING_FLAG = "A_" 15 | 16 | local KEY_URI_STATUS = "B_" 17 | local KEY_URI_SIZE = "C_" 18 | local KEY_URI_TIME = "D_" 19 | local KEY_URI_COUNT = "E_" 20 | 21 | local KEY_COLLECT_STATUS = "F_" 22 | local KEY_COLLECT_SIZE = "G_" 23 | local KEY_COLLECT_TIME = "H_" 24 | local KEY_COLLECT_COUNT = "I_" 25 | 26 | function _M.refresh() 27 | local period = tonumber( VeryNginxConfig.configs["summary_request_enable"] ) 28 | 29 | if period == nil or period < 10 then 30 | period = 10 31 | end 32 | 33 | ngx.timer.at( period, _M.refresh ) 34 | ngx.shared.summary_short:flush_all() 35 | --update flag timeout 36 | ngx.shared.status:set( KEY_SUMMARY_REFRESHING_FLAG, true, 120 ) 37 | end 38 | 39 | function _M.pre_run_matcher() 40 | if VeryNginxConfig.configs["summary_request_enable"] ~= true or VeryNginxConfig.configs["summary_collect_enable"] ~= true then 41 | return 42 | end 43 | 44 | local matcher_list = VeryNginxConfig.configs['matcher'] 45 | for i,rule in ipairs( VeryNginxConfig.configs["summary_collect_rule"] ) do 46 | local enable = rule['enable'] 47 | local matcher = matcher_list[ rule['matcher'] ] 48 | if enable == true and request_tester.test( matcher ) == true then 49 | ngx.ctx.log_collect_name = rule['collect_name'] 50 | break 51 | end 52 | end 53 | end 54 | 55 | function _M.log() 56 | 57 | local ok, err = ngx.shared.status:add( KEY_SUMMARY_REFRESHING_FLAG, true, 120 ) 58 | --here use set a 120s timeout for the flag key, so when the nginx worker exit( for example nginx-s reload may cause that ), 59 | --a other worker will continue to refresh the data period 60 | if ok then 61 | _M.refresh() 62 | end 63 | 64 | if VeryNginxConfig.configs["summary_request_enable"] ~= true then 65 | return 66 | end 67 | 68 | local with_host_info = VeryNginxConfig.configs["summary_with_host"] 69 | local uri = ngx.var.request_uri 70 | local status_code = ngx.var.status; 71 | local key_status = nil 72 | local key_size = nil 73 | local key_time = nil 74 | local key_count = nil 75 | local log_collect_name = ngx.ctx.log_collect_name 76 | local index = nil 77 | 78 | if log_collect_name ~= nil then 79 | key_status = KEY_COLLECT_STATUS..log_collect_name.."_"..status_code 80 | key_size = KEY_COLLECT_SIZE..log_collect_name 81 | key_time = KEY_COLLECT_TIME..log_collect_name 82 | key_count = KEY_COLLECT_COUNT..log_collect_name 83 | else 84 | if uri ~= nil then 85 | index = string.find( uri, '?' ) 86 | if index ~= nil then 87 | uri = string.sub( uri, 1 , index - 1 ) 88 | end 89 | end 90 | if with_host_info then 91 | uri = ngx.var.host..uri 92 | end 93 | key_status = KEY_URI_STATUS..(uri or '').."_"..status_code 94 | key_size = KEY_URI_SIZE..uri 95 | key_time = KEY_URI_TIME..uri 96 | key_count = KEY_URI_COUNT..uri 97 | end 98 | 99 | if VeryNginxConfig.configs["summary_group_persistent_enable"] == true then 100 | if ngx.shared.summary_long:get( key_count ) == nil then 101 | ngx.shared.summary_long:set( key_count, 0 ) 102 | end 103 | 104 | if ngx.shared.summary_long:get( key_status ) == nil then 105 | ngx.shared.summary_long:set( key_status, 0 ) 106 | end 107 | 108 | if ngx.shared.summary_long:get( key_size ) == nil then 109 | ngx.shared.summary_long:set( key_size, 0 ) 110 | end 111 | 112 | if ngx.shared.summary_long:get( key_time ) == nil then 113 | ngx.shared.summary_long:set( key_time, 0 ) 114 | end 115 | 116 | ngx.shared.summary_long:incr( key_count, 1 ) 117 | ngx.shared.summary_long:incr( key_status, 1 ) 118 | ngx.shared.summary_long:incr( key_size, ngx.var.body_bytes_sent ) 119 | ngx.shared.summary_long:incr( key_time, ngx.var.request_time ) 120 | end 121 | 122 | if VeryNginxConfig.configs["summary_group_temporary_enable"] == true then 123 | if ngx.shared.summary_short:get( key_count ) == nil then 124 | ngx.shared.summary_short:set( key_count, 0 ) 125 | end 126 | 127 | if ngx.shared.summary_short:get( key_status ) == nil then 128 | ngx.shared.summary_short:set( key_status, 0 ) 129 | end 130 | 131 | if ngx.shared.summary_short:get( key_size ) == nil then 132 | ngx.shared.summary_short:set( key_size, 0 ) 133 | end 134 | 135 | if ngx.shared.summary_short:get( key_time ) == nil then 136 | ngx.shared.summary_short:set( key_time, 0 ) 137 | end 138 | 139 | ngx.shared.summary_short:incr( key_count, 1 ) 140 | ngx.shared.summary_short:incr( key_status, 1 ) 141 | ngx.shared.summary_short:incr( key_size, ngx.var.body_bytes_sent ) 142 | ngx.shared.summary_short:incr( key_time, ngx.var.request_time ) 143 | end 144 | end 145 | 146 | function _M.report() 147 | 148 | local dict = nil 149 | local report = {} 150 | local uri_report = {} 151 | local collect_report = {} 152 | local record_key = nil 153 | local status = nil 154 | local size = nil 155 | local time = nil 156 | local count = nil 157 | 158 | report['uri'] = uri_report 159 | report['collect'] = collect_report 160 | 161 | local args = ngx.req.get_uri_args() 162 | if args['type'] == 'long' then 163 | dict = ngx.shared.summary_long 164 | elseif args['type'] == 'short' then 165 | dict = ngx.shared.summary_short 166 | else 167 | return json.encode({["ret"]="failed",["err"]="type error"}) 168 | end 169 | 170 | local keys = dict:get_keys(0) 171 | local str_sub = string.sub 172 | local str_len = string.len 173 | local str_format = string.format 174 | 175 | 176 | for k, v in pairs( keys ) do 177 | record_key = nil 178 | record_table = nil 179 | status = nil 180 | size = nil 181 | time = nil 182 | count = nil 183 | 184 | if v.find(v, KEY_URI_STATUS) == 1 then 185 | record_key = str_sub( v, str_len(KEY_URI_STATUS) + 1, -5 ) 186 | record_table = uri_report 187 | status = str_sub( v,-3 ) 188 | elseif v.find(v, KEY_URI_SIZE) == 1 then 189 | record_key = str_sub( v, str_len(KEY_URI_SIZE) + 1 ) 190 | record_table = uri_report 191 | size = dict:get( v ) 192 | elseif v.find(v, KEY_URI_TIME) == 1 then 193 | record_key = str_sub( v, str_len(KEY_URI_TIME) + 1 ) 194 | record_table = uri_report 195 | time = dict:get( v ) 196 | elseif v.find(v, KEY_URI_COUNT) == 1 then 197 | record_key = str_sub( v, str_len(KEY_URI_COUNT) + 1 ) 198 | record_table = uri_report 199 | count = dict:get( v ) 200 | elseif v.find(v, KEY_COLLECT_STATUS) == 1 then 201 | record_key = str_sub( v, str_len(KEY_COLLECT_STATUS) + 1, -5 ) 202 | record_table = collect_report 203 | status = str_sub( v,-3 ) 204 | elseif v.find(v, KEY_COLLECT_SIZE) == 1 then 205 | record_key = str_sub( v, str_len(KEY_COLLECT_SIZE) + 1 ) 206 | record_table = collect_report 207 | size = dict:get( v ) 208 | elseif v.find(v, KEY_COLLECT_TIME) == 1 then 209 | record_key = str_sub( v, str_len(KEY_COLLECT_TIME) + 1 ) 210 | record_table = collect_report 211 | time = dict:get( v ) 212 | elseif v.find(v, KEY_COLLECT_COUNT) == 1 then 213 | record_key = str_sub( v, str_len(KEY_COLLECT_COUNT) + 1 ) 214 | record_table = collect_report 215 | count = dict:get( v ) 216 | end 217 | 218 | 219 | if record_key ~= nil then 220 | if record_table[record_key] == nil then 221 | record_table[record_key] = {} 222 | record_table[record_key]["status"] = {} 223 | end 224 | 225 | if status ~= nil then 226 | record_table[record_key]["status"][status] = dict:get( v ) 227 | elseif time ~= nil then 228 | record_table[record_key]["time"] = time 229 | elseif 230 | size ~= nil then 231 | record_table[record_key]["size"] = size 232 | elseif count ~= nil then 233 | record_table[record_key]["count"] = count 234 | end 235 | end 236 | end 237 | 238 | --remove incomplete record 239 | for name,record_table in pairs( report ) do 240 | for k, v in pairs( record_table ) do 241 | if v['time'] == nil or v['count'] == nil or v['size'] == nil then 242 | record_table[k] = nil 243 | end 244 | end 245 | end 246 | 247 | return json.encode( report ) 248 | end 249 | 250 | function _M.clear() 251 | local args = util.get_request_args() 252 | local group = args['group'] 253 | 254 | if group == 'temporary' then 255 | ngx.shared.summary_short:flush_all() 256 | elseif group == 'persistent' then 257 | ngx.shared.summary_long:flush_all() 258 | elseif group == 'all' then 259 | ngx.shared.summary_short:flush_all() 260 | ngx.shared.summary_long:flush_all() 261 | end 262 | 263 | return '{}' 264 | end 265 | 266 | return _M 267 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/uri_rewrite.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- @Date : 2016-02-21 3 | -- @Author : Alexa (AlexaZhou@163.com) 4 | -- @Link : 5 | -- @Disc : rewrite uri inside nginx 6 | 7 | local _M = {} 8 | 9 | local VeryNginxConfig = require "VeryNginxConfig" 10 | local request_tester = require "request_tester" 11 | 12 | 13 | function _M.run() 14 | 15 | if VeryNginxConfig.configs["uri_rewrite_enable"] ~= true then 16 | return 17 | end 18 | 19 | local new_uri = nil 20 | local re_gsub = ngx.re.gsub 21 | local ngx_var = ngx.var 22 | local ngx_set_uri = ngx.req.set_uri 23 | local ngx_var_uri = ngx_var.uri 24 | local ngx_var_scheme = ngx_var.scheme 25 | local ngx_var_host = ngx_var.host 26 | local matcher_list = VeryNginxConfig.configs['matcher'] 27 | 28 | 29 | for i, rule in ipairs( VeryNginxConfig.configs["uri_rewrite_rule"] ) do 30 | local enable = rule['enable'] 31 | local matcher = matcher_list[ rule['matcher'] ] 32 | if enable == true and request_tester.test( matcher ) == true then 33 | replace_re = rule['replace_re'] 34 | if replace_re ~= nil and string.len( replace_re ) >0 then 35 | new_uri = re_gsub( ngx_var_uri, replace_re, rule['to_uri'] ) 36 | else 37 | new_uri = rule['to_uri'] 38 | end 39 | 40 | if new_uri ~= ngx_var_uri then 41 | ngx_set_uri( new_uri , false ) 42 | end 43 | return 44 | end 45 | end 46 | 47 | end 48 | 49 | return _M 50 | -------------------------------------------------------------------------------- /verynginx/lua_script/module/util.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- @Date : 2016-02-29 3 | -- @Author : Alexa (AlexaZhou@163.com) 4 | -- @Link : 5 | -- @Disc : some tools 6 | 7 | local json = require "json" 8 | 9 | local _M = {} 10 | 11 | 12 | function _M.string_replace(s, pattern, replace, times) 13 | local ret = nil 14 | while times >= 0 do 15 | times = times - 1 16 | local s_start,s_stop = string.find(s, pattern , 1, true ) -- 1,true means plain searches from index 1 17 | if s_start ~= nil and s_stop ~= nil then 18 | s = string.sub( s, 1, s_start-1 ) .. replace .. string.sub( s, s_stop+1 ) 19 | end 20 | end 21 | return s 22 | end 23 | 24 | function _M.existed( list, value ) 25 | for idx,item in ipairs( list ) do 26 | if item == value then 27 | return true 28 | end 29 | end 30 | return false 31 | end 32 | 33 | function _M.ngx_ctx_dump() 34 | local dump_str = json.encode( ngx.ctx ) 35 | ngx.var.vn_ctx_dump = dump_str 36 | end 37 | 38 | function _M.ngx_ctx_load() 39 | 40 | if ngx.var.vn_ctx_dump == nil then 41 | return 42 | end 43 | 44 | local dump_str = ngx.var.vn_ctx_dump 45 | if dump_str ~= '' then 46 | ngx.ctx = json.decode( dump_str ) 47 | end 48 | end 49 | 50 | function _M.get_request_args() 51 | local args = ngx.req.get_uri_args() 52 | local post_args, err = nil 53 | 54 | ngx.req.read_body() 55 | post_args, err = ngx.req.get_post_args() 56 | if post_args == nil then 57 | return args 58 | end 59 | 60 | for k,v in pairs(post_args) do 61 | args[k] = v 62 | end 63 | 64 | return args 65 | end 66 | 67 | return _M 68 | -------------------------------------------------------------------------------- /verynginx/lua_script/on_access.lua: -------------------------------------------------------------------------------- 1 | local summary = require "summary" 2 | local filter = require "filter" 3 | local browser_verify = require "browser_verify" 4 | local frequency_limit = require "frequency_limit" 5 | local router = require "router" 6 | local backend_static = require "backend_static" 7 | local backend_proxy = require "backend_proxy" 8 | 9 | if ngx.var.vn_exec_flag and ngx.var.vn_exec_flag ~= '' then 10 | return 11 | end 12 | 13 | summary.pre_run_matcher() 14 | 15 | filter.filter() 16 | browser_verify.filter() 17 | frequency_limit.filter() 18 | router.filter() 19 | 20 | 21 | backend_static.filter() 22 | backend_proxy.filter() 23 | -------------------------------------------------------------------------------- /verynginx/lua_script/on_banlance.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- @Date : 2016-04-23 3 | -- @Author : Alexa (AlexaZhou@163.com) 4 | -- @Link : 5 | -- @Disc : load balancer by lua 6 | 7 | local _M = {} 8 | 9 | local balancer = require "ngx.balancer" 10 | 11 | function _M.run() 12 | --ngx.log( ngx.ERR, ngx.var.vn_proxy_target) 13 | local ok, err = balancer.set_current_peer( ngx.var.vn_proxy_host , ngx.var.vn_proxy_port ) 14 | if not ok then 15 | ngx.log(ngx.ERR, "failed to set the current peer: ", err) 16 | return ngx.exit(500) 17 | end 18 | 19 | return 20 | end 21 | 22 | _M.run() 23 | 24 | return _M 25 | 26 | -------------------------------------------------------------------------------- /verynginx/lua_script/on_init.lua: -------------------------------------------------------------------------------- 1 | local VeryNginxConfig = require "VeryNginxConfig" 2 | 3 | local status = require "status" 4 | status.init() 5 | -------------------------------------------------------------------------------- /verynginx/lua_script/on_log.lua: -------------------------------------------------------------------------------- 1 | local status = require "status" 2 | local summary = require "summary" 3 | 4 | status.log() 5 | summary.log() 6 | -------------------------------------------------------------------------------- /verynginx/lua_script/on_rewrite.lua: -------------------------------------------------------------------------------- 1 | local util = require "util" 2 | local VeryNginxConfig = require "VeryNginxConfig" 3 | local scheme_lock = require "scheme_lock" 4 | local redirect = require "redirect" 5 | local uri_rewrite = require "uri_rewrite" 6 | 7 | if ngx.var.vn_exec_flag and ngx.var.vn_exec_flag ~= '' then 8 | util.ngx_ctx_load() 9 | return 10 | end 11 | 12 | --At first , make sure every request use latest running config 13 | VeryNginxConfig.update_config() 14 | 15 | scheme_lock.run() 16 | redirect.run() 17 | uri_rewrite.run() 18 | -------------------------------------------------------------------------------- /verynginx/nginx_conf/in_external.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexazhou/VeryNginx/b66ef2ee6a108279653316323bd8ac026dd7ccd8/verynginx/nginx_conf/in_external.conf -------------------------------------------------------------------------------- /verynginx/nginx_conf/in_http_block.conf: -------------------------------------------------------------------------------- 1 | upstream vn_upstream{ 2 | server 127.0.0.1; 3 | balancer_by_lua_file /opt/verynginx/verynginx/lua_script/on_banlance.lua; 4 | keepalive 1024; #Connection pool 5 | } 6 | 7 | lua_package_path '/opt/verynginx/verynginx/lua_script/?.lua;;/opt/verynginx/verynginx/lua_script/module/?.lua;;'; 8 | lua_package_cpath '/opt/verynginx/verynginx/lua_script/?.so;;'; 9 | lua_code_cache on; 10 | 11 | lua_shared_dict status 1m; 12 | lua_shared_dict frequency_limit 10m; 13 | lua_shared_dict summary_long 10m; 14 | lua_shared_dict summary_short 10m; 15 | 16 | init_by_lua_file /opt/verynginx/verynginx/lua_script/on_init.lua; 17 | rewrite_by_lua_file /opt/verynginx/verynginx/lua_script/on_rewrite.lua; 18 | access_by_lua_file /opt/verynginx/verynginx/lua_script/on_access.lua; 19 | log_by_lua_file /opt/verynginx/verynginx/lua_script/on_log.lua; 20 | -------------------------------------------------------------------------------- /verynginx/nginx_conf/in_server_block.conf: -------------------------------------------------------------------------------- 1 | set $vn_exec_flag ''; 2 | set $vn_ctx_dump ''; 3 | 4 | #for proxy_pass backend 5 | set $vn_proxy_scheme ''; 6 | set $vn_proxy_host ''; 7 | set $vn_proxy_port ''; 8 | set $vn_header_host ''; 9 | set $vn_static_expires 'epoch'; 10 | 11 | #for static file backend 12 | set $vn_static_root ''; 13 | 14 | location @vn_static { 15 | expires $vn_static_expires; 16 | root $vn_static_root; 17 | } 18 | 19 | location @vn_proxy { 20 | proxy_set_header Host $vn_header_host; 21 | #proxy_set_header X-Real-IP $remote_addr; 22 | #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 23 | proxy_set_header User-Agent $http_user_agent; 24 | proxy_pass $vn_proxy_scheme://vn_upstream; 25 | proxy_ssl_verify off; 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /verynginx/support/verify_javascript.html: -------------------------------------------------------------------------------- 1 | <!doctype html> 2 | <html> 3 | <head> 4 | <meta charset="utf-8"> 5 | <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"> 6 | <meta http-equiv="Pragma" content="no-cache"> 7 | <meta http-equiv="Expires" content="0"> 8 | <script type="text/javascript"> 9 | var data = { 10 | 'cookie' : "INFOCOOKIE", 11 | 'uri' : "INFOURI", 12 | }; 13 | 14 | function getCookie(c_name){ 15 | if (document.cookie.length>0){ 16 | c_start=document.cookie.indexOf(c_name + "=") 17 | if (c_start!=-1){ 18 | c_start=c_start + c_name.length+1 19 | c_end=document.cookie.indexOf(";",c_start) 20 | if (c_end==-1) c_end=document.cookie.length 21 | return unescape(document.cookie.substring(c_start,c_end)) 22 | } 23 | } 24 | return ""; 25 | } 26 | 27 | function setCookie(c_name,value,expiredays){ 28 | var exdate=new Date(); 29 | exdate.setDate(exdate.getDate()+expiredays); 30 | document.cookie=c_name+ "=" +escape(value)+((expiredays==null) ? "" : "; path=/; expires="+exdate.toGMTString()); 31 | } 32 | 33 | function jump(){ 34 | setCookie('COOKIEPREFIX_sign_javascript',data['cookie'],365); 35 | window.location = data['uri']; 36 | } 37 | </script> 38 | </head> 39 | <body onLoad="javascript:jump()"> 40 | </body> 41 | </html> 42 | --------------------------------------------------------------------------------