├── .gitignore ├── BENCHMARK.md ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── README_zh-TW.md ├── caddy-wphpfpm-example.md ├── conf └── conf.go ├── config-sample.json ├── go.mod ├── go.sum ├── logformat_test.go ├── main.go ├── phpfpm ├── phpfpm.go └── process.go └── server └── sever.go /.gitignore: -------------------------------------------------------------------------------- 1 | /config.json 2 | /*.exe 3 | /*.exe~ 4 | /*.log 5 | /*.gz 6 | -------------------------------------------------------------------------------- /BENCHMARK.md: -------------------------------------------------------------------------------- 1 | # WPHPFPM Benchmark # 2 | 3 | - Hardware : Intel E3-1230v2 4 | - RAM : 16GB DDR3 5 | - OS : Windows 10 6 | - Benchmark tool : apache ab 7 | 8 | ## Caddy with one php-cgi ## 9 | 10 | See https://github.com/caddyserver/examples/tree/master/winphp 11 | 12 | ~~~ 13 | Server Software: Caddy 14 | Server Hostname: localhost 15 | Server Port: 2015 16 | 17 | Document Path: /phpinfo.php 18 | Document Length: 91015 bytes 19 | 20 | Concurrency Level: 10 21 | Time taken for tests: 6.703 seconds 22 | Complete requests: 529 23 | Failed requests: 70 24 | (Connect: 0, Receive: 0, Length: 70, Exceptions: 0) 25 | Non-2xx responses: 32 26 | Total transferred: 45399235 bytes 27 | HTML transferred: 45325939 bytes 28 | Requests per second: 78.92 [#/sec] (mean) 29 | Time per request: 126.707 [ms] (mean) 30 | Time per request: 12.671 [ms] (mean, across all concurrent requests) 31 | Transfer rate: 6614.41 [Kbytes/sec] received 32 | ~~~ 33 | 34 | ## Caddy with wphpfpm MaxProcess = 4 and LogLevel = DEBUG ## 35 | 36 | see [caddy-wphpfpm-example.md](./caddy-wphpfpm-example.md) 37 | 38 | ~~~ 39 | Server Software: Caddy 40 | Server Hostname: localhost 41 | Server Port: 2015 42 | 43 | Document Path: /phpinfo.php 44 | Document Length: 90796 bytes 45 | 46 | Concurrency Level: 10 47 | Time taken for tests: 5.000 seconds 48 | Complete requests: 4954 49 | Failed requests: 491 50 | (Connect: 0, Receive: 0, Length: 491, Exceptions: 0) 51 | Total transferred: 450521646 bytes 52 | HTML transferred: 449847766 bytes 53 | Requests per second: 990.80 [#/sec] (mean) 54 | Time per request: 10.093 [ms] (mean) 55 | Time per request: 1.009 [ms] (mean, across all concurrent requests) 56 | Transfer rate: 87992.26 [Kbytes/sec] received 57 | ~~~ 58 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # wphpfpm Change log # 2 | 3 | ### v0.01 2018-9-XX ### 4 | 5 | - Implement install as windows service 6 | - implement JSON configuration 7 | - Implement Logger to file 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2013 npipe authors 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wphpfpm (PHP FastCGI Manager for windows) # 2 | 3 | wphpfpm is my first go-lang project for manage php-cgi on Windows. 4 | 5 | Since php-cgi can only serve one client at one time, unless you use apache's mod_fcgid, it's really hard to manage. 6 | 7 | So I wrote it for myself, mainly because using caddy to test php can only start one php-cgi process. It is too inhuman, and when I want to change the php settings and want to restart php-cgi, I have to manually kill php-cgi. 8 | 9 | For performance, please refer to [BENCHMARK.md](./BENCHMARK.md) 10 | 11 | 12 | 13 | ## Features ## 14 | 15 | 1. wphpfpm is a standalone service, similar to php-fpm under Linux 16 | 2. You can create multiple instances for multiple version php-cgi 17 | 3. php-cgi can set the maximum number of process 18 | 4. wphpfpm can be a windows service or running on console mode. 19 | 5. JSON format configuration file 20 | 21 | Please download GO [GO SDK](https://golang.org/) (version 1.12+) and execute the following command to get wphpfpm.exe 22 | 23 | ~~~bash 24 | go build 25 | ~~~ 26 | 27 | Or refer to [caddy-wphpfpm-example.md](./caddy-wphpfpm-example.md) for a simple setup of caddy + wphpfpm. 28 | 29 | ## Configure file ## 30 | 31 | The following is a json example. The source code [config-sample.json](./config-sample.json) can be download and modify it for your environment. 32 | 33 | ```json 34 | { 35 | "LogLevel" : "ERROR", 36 | "Logger": { 37 | "FileName": "C:\\wphpfpm\\wphpfpm.log", 38 | "MaxSize": 10, 39 | "MaxBackups": 4, 40 | "MaxAge": 7, 41 | "Compress": true, 42 | "Note": "If you don't need Logger, you can remove the entire Logger section, MaxSize is the unit of MB, MaxAge is the unit of days, this example has 7 days of content per log." 43 | }, 44 | "Instances" : [ 45 | { 46 | "Bind" : "127.0.0.1:8000", 47 | "ExecPath": "C:\\PHP7\\php-cgi.exe", 48 | "Args" : [], 49 | "Env": [ 50 | "PHPRC=C:\\PHP7", 51 | "PHP_FCGI_MAX_REQUESTS=5000" , 52 | "PHP_INI_SCAN_DIR=c:\\php7\\conf.d" 53 | ], 54 | "MaxProcesses" : 4, 55 | "MaxRequestsPerProcess": 500 56 | } , 57 | 58 | { 59 | "Bind" : "127.0.0.1:8001", 60 | "ExecPath": "C:\\PHP5\\php-cgi.exe", 61 | "Args" : [], 62 | "Env": [ 63 | "PHPRC=C:\\PHP5", 64 | "PHP_FCGI_MAX_REQUESTS=5000" , 65 | "PHP_INI_SCAN_DIR=c:\\php5\\conf.d" 66 | ], 67 | "MaxProcesses" : 2, 68 | "MaxRequestsPerProcess": 500 69 | } 70 | ] 71 | } 72 | ``` 73 | 74 | - LogLevel : According to the level, the default is ERROR 75 | * PANIC 76 | * FATAL 77 | * ERROR 78 | * WARN 79 | * INFO 80 | * DEBUG 81 | * TRACE 82 | - Logger : You can define the Log output to the file. If you don't need it, you can remove it. The output will be Console (stderr). 83 | - Instances : Define how many kinds of php-cgi to start, this can be used as multiple versions 84 | 85 | - Bind : Define what IP and Port to use for this instance. If multiple versions are required, different Instances must be used with different Ports. 86 | 87 | - ExecPath : php-cgi real path. 88 | 89 | - Args : You can add parameters for execute php-cgi.exe, note that you can't use -b parameters 90 | - Env : Additional environmental variables 91 | - MaxProcesses : This directive sets the maximum number of php-cgi processes which can be active at one time. 92 | - MaxRequestsPerProcess : Each php-cgi process trip can handle up to several requests. This value must be the same or less than Env's environment variable PHP_FCGI_MAX_REQUESTS. 93 | - Note : This field has no effect, just for comment 94 | 95 | 96 | 97 | ## Usage ## 98 | 99 | ### Run in command line mode (console mode) ### 100 | 101 | ``` 102 | wphpfpm run --conf=config.json 103 | ``` 104 | 105 | ### Install as Windows Service ### 106 | 107 | ``` 108 | wphpfpm install --conf=c:\wphpfpm\config.json 109 | ``` 110 | 111 | Note that when install as a Windows Service, you must use administrator privileges to install. 112 | 113 | ### Remove wphpfpm service ### 114 | 115 | ``` 116 | wphpfpm uninstall 117 | ``` 118 | 119 | 120 | 121 | ### Start and stop wphpfpm service ### 122 | 123 | ``` 124 | wphpfpm start 125 | wphpfpm stop 126 | ``` 127 | 128 | Alternatively, the service under Windows Control Panel\All Control Panel Items\Administrative Tools\Services can also be started or stopped PHP FastCGI Manager for windows 129 | 130 | ## Author 131 | 132 | - Pigo Chu 133 | 134 | - Web Site https://www.pigo.idv.tw 135 | 136 | ## Resouces ## 137 | 138 | - Windows Service Control : https://github.com/chai2010/winsvc 139 | - Command Line parser : https://gopkg.in/alecthomas/kingpin.v2 140 | - Windows Named Pipe : https://github.com/natefinch/npipe 141 | - Article (https://blog.csdn.net/small_qch/article/details/19562661) 142 | - Log/LogLevel : Logrus (https://github.com/sirupsen/logrus) 143 | - Log Rotate : lumberjack (https://github.com/natefinch/lumberjack) -------------------------------------------------------------------------------- /README_zh-TW.md: -------------------------------------------------------------------------------- 1 | # wphpfpm (PHP FastCGI Manager for windows) # 2 | 3 | wphpfpm 是我初次練習 Go Lang 開發用來管理 Windows 下的 php-cgi 4 | 5 | 由於 php-cgi 一次只能服務一個客戶端,除非使用 apache 的 mod_fcgid,不然還真難管理 6 | 7 | 所以我就自己寫來玩玩,主要是因為用 caddy 來測試 php 只能啟動一隻 php-cgi 實在太不人道了,而且當我修改 php 的設定值,想要重啟 php-cgi,必須要自己手動砍掉 php-cgi 行程 8 | 9 | 有關於性能,可以參考 [BENCHMARK.md](./BENCHMARK.md) 10 | 11 | 12 | 13 | 目前有的功能如下 14 | 15 | 1. wphpfpm 是獨立的服務,類似 Linux 下的 php-fpm 16 | 2. 可以建立不同版本的 php-cgi 來跑 17 | 3. php-cgi 可以設定最大啟動的數量 18 | 4. 可以安裝於 Windows Service,也可以命令列模式下跑 19 | 5. JSON 格式的設定檔 20 | 21 | 請直接下載 [GO SDK](https://golang.org/) (version 1.12+)後,執行以下命令,就可以得到 wphpfpm.exe 22 | 23 | ~~~bash 24 | go build 25 | ~~~ 26 | 27 | 或者參考 [caddy-wphpfpm-example.md](./caddy-wphpfpm-example.md) 有說明 caddy + wphpfpm 的簡單設定。 28 | 29 | ## 設定檔說明 ## 30 | 31 | 以下是 json 範例 , 原始碼中 [config-sample.json](./config-sample.json) 可以下載來修改使用 32 | 33 | ```json 34 | { 35 | "LogLevel" : "ERROR", 36 | "Logger": { 37 | "FileName": "C:\\wphpfpm\\wphpfpm.log", 38 | "MaxSize": 10, 39 | "MaxBackups": 4, 40 | "MaxAge": 7, 41 | "Compress": true, 42 | "Note": "如果不需要 Log File, 可以將 FileName 設為空字串,則所有 Log 將輸出至 Console , MaxSize 是 MB 為單位 , MaxAge 是以天為單位,本例子為每一份log有7天的內容" 43 | }, 44 | "Instances" : [ 45 | { 46 | "Bind" : "127.0.0.1:8000", 47 | "ExecPath": "C:\\PHP7\\php-cgi.exe", 48 | "Args" : [], 49 | "Env": [ 50 | "PHPRC=C:\\PHP7", 51 | "PHP_FCGI_MAX_REQUESTS=5000" , 52 | "PHP_INI_SCAN_DIR=c:\\php7\\conf.d" 53 | ], 54 | "MaxProcesses" : 4, 55 | "MaxRequestsPerProcess": 500 56 | } , 57 | 58 | { 59 | "Bind" : "127.0.0.1:8001", 60 | "ExecPath": "C:\\PHP5\\php-cgi.exe", 61 | "Args" : [], 62 | "Env": [ 63 | "PHPRC=C:\\PHP5", 64 | "PHP_FCGI_MAX_REQUESTS=5000" , 65 | "PHP_INI_SCAN_DIR=c:\\php5\\conf.d" 66 | ], 67 | "MaxProcesses" : 2, 68 | "MaxRequestsPerProcess": 500 69 | } 70 | ] 71 | } 72 | ``` 73 | 74 | - LogLevel : 依照等級有以下,預設是 ERROR 75 | * PANIC 76 | * FATAL 77 | * ERROR 78 | * WARN 79 | * INFO 80 | * DEBUG 81 | * TRACE 82 | 83 | - Logger : 可以定義 Logger 運作行為 84 | 85 | - FileName : 可以定義 Log 輸出至檔案,如果不需要,可以設定為空字串,輸出會是 Console(stderr) 86 | - MaxSize : 每一份 Log 檔案最大的 Size , 單位是 MB , 當 Log 檔案已經到達設定值時,會進行 Rotate 的動作。 87 | - MaxBackups : 最大保留幾份 Log 檔案 88 | - MaxAge : 每一份檔案保留幾天的內容,單位是天 89 | - Compress : 是否在 Rotate 之後的檔案要進行壓縮,格式是 gz 90 | 91 | - Instances : 定義有多少種 php-cgi 要啟動,這可做為多版本之用 92 | 93 | - Bind : 定義該 instance 要使用甚麼 IP 及 Port ,若針對多版本必須讓不同的 Instances 用不同的 Port 才有效 94 | 95 | - ExecPath : php-cgi 真實路徑 96 | 97 | - Args : 可以帶入 php-cgi 額外參數,**注意,不能使用 -b 的參數** 98 | 99 | - Env : 可以額外加上環境變數 100 | 101 | - MaxProcesses : 最大 php-cgi 執行數量 102 | 103 | - MaxRequestsPerProcess : 每隻 php-cgi 行程,最多能處理幾次請求 , 這個數值必須與 Env 的環境變數 PHP_FCGI_MAX_REQUESTS 一致或小於才不會出問題 104 | 105 | - Note : 此欄位並無作用,只是用來註解的 106 | 107 | 108 | 109 | ## 使用方式 ## 110 | 111 | ### 在命令列模式 (Console Mode)下執行 ### 112 | 113 | ``` 114 | wphpfpm run --conf=config.json 115 | ``` 116 | 117 | ### 安裝於 Windows Service ### 118 | 119 | ``` 120 | wphpfpm install --conf=c:\wphpfpm\config.json 121 | ``` 122 | 123 | 注意,安裝為 Windows Service 模式運作時,必須使用管理者權限才能安裝 124 | 125 | ### 移除 wphpfpm service ### 126 | 127 | ``` 128 | wphpfpm uninstall 129 | ``` 130 | 131 | 132 | 133 | ### 啟動及停止 wphpfpm service ### 134 | 135 | ``` 136 | wphpfpm start 137 | wphpfpm stop 138 | ``` 139 | 140 | 或者,在 Windows 的 **控制台\所有控制台項目\系統管理工具** 下的 **服務** 也可以進行啟動或停止 PHP FastCGI Manager for windows 141 | 142 | 143 | 144 | ## wphpfpm 運作的方式 145 | 146 | 1. wphpfpm 是採用 TCP port 方式對外服務,例如 caddy 當作 Http Server,使用 caddy fastcgi 來連接 wphpfpm 設定值 Instances>Bind 所開啟的 Port 147 | 2. wphpfpm 跟 php-cgi 之間的溝通則是採用 windows named pipe 方式溝通,我目前功力仍不夠,不知道如何讓 golang 直接對 php-cgi stdin 溝通,因為看 [xxfpm](https://github.com/78/xxfpm) 的源碼,理論上會更有效率。 148 | 149 | 150 | 151 | ## 作者 ## 152 | 153 | - Pigo Chu 154 | 155 | - Web Site https://www.pigo.idv.tw 156 | 157 | 158 | 159 | ## 資源清單 ## 160 | 161 | - Windows Service 處理 : https://github.com/chai2010/winsvc 162 | - 命令列處理 : https://gopkg.in/alecthomas/kingpin.v2 163 | - Windows Named Pipe : https://github.com/natefinch/npipe 164 | - 網路文章(https://blog.csdn.net/small_qch/article/details/19562661) 165 | - 處理 Log/LogLevel : Logrus (https://github.com/sirupsen/logrus) 166 | - Log Rotate : lumberjack (https://github.com/natefinch/lumberjack) -------------------------------------------------------------------------------- /caddy-wphpfpm-example.md: -------------------------------------------------------------------------------- 1 | # Caddy with wphpfpm setting example # 2 | 3 | 4 | 5 | ## Requirement ## 6 | 7 | 1. [GO SDK](https://golang.org/doc/install) 8 | 2. php windows version 9 | 3. caddy.exe , build from https://caddyserver.com/ 10 | 4. wphpfpm source code 11 | 12 | ## Folder struct example ## 13 | 14 | ~~~ 15 | C:/ 16 | php/ <= decompression php to here 17 | caddy/caddy.exe 18 | caddy/Caddyfile <= please create it 19 | caddy/www <= please create it 20 | wphpfpm/ <= clone from github 21 | ~~~ 22 | 23 | ### Caddyfile example ### 24 | 25 | ~~~lua 26 | :2015 { 27 | root c:\caddy\www 28 | fastcgi / 127.0.0.1:8000 php 29 | } 30 | ~~~ 31 | 32 | Save it to c:/caddy/Caddyfile , than Run the following command in console 33 | 34 | ~~~ 35 | c: 36 | cd \caddy 37 | caddy 38 | ~~~ 39 | 40 | caddy will listen on port 2015 41 | 42 | ## wphpfpm config.json example ## 43 | 44 | 1. Install GO SDK 45 | 2. CD to c:\wphpfpm folder , Run "go build" , you will get a file wphpfpm.exe 46 | 3. Copy config-sample.json to config.json 47 | 4. Modify config.json as following content 48 | 49 | ~~~json 50 | { 51 | "LogLevel" : "DEBUG", 52 | "Logger": { 53 | "FileName": "", 54 | "MaxSize": 1, 55 | "MaxBackups": 3, 56 | "MaxAge": 7, 57 | "Compress": true 58 | }, 59 | "Instances" : [ 60 | { 61 | "Bind" : "127.0.0.1:8000", 62 | "ExecPath": "C:\\PHP\\php-cgi.exe", 63 | "Args" : [], 64 | "Env": [ 65 | "PHPRC=C:\\PHP", 66 | "PHP_FCGI_MAX_REQUESTS=5000" , 67 | "PHP_INI_SCAN_DIR=c:\\php\\conf.d" 68 | ], 69 | "MaxProcesses" : 4, 70 | "MaxRequestsPerProcess": 500 71 | } 72 | ] 73 | } 74 | ~~~ 75 | 76 | 3. Run the following command in console 77 | 78 | ~~~ 79 | wphpfpm run --conf=config.json 80 | ~~~ 81 | 82 | If successful, you will see the following output. 83 | 84 | ~~~ 85 | Start in console mode , press CTRL+C to exit ... 86 | ..... 87 | 2019-09-08 14:43:58 +0800 [info]: Set LogLevel to DEBUG. 88 | 2019-09-08 14:43:58 +0800 [info]: Logger ouput set to console. 89 | 2019-09-08 14:43:58 +0800 [info]: phpfpm starting. 90 | 2019-09-08 14:43:58 +0800 [debug]: Trying to start php-cgi(C:\PHP\php-cgi.exe -> 91 | 2019-09-08 14:43:58 +0800 [info]: Service running ... 92 | 2019-09-08 14:43:58 +0800 [debug]: Server 127.0.0.1:8000 starting listener 93 | 2019-09-08 14:43:58 +0800 [info]: Server 127.0.0.1:8000 starting accept 94 | 2019-09-08 14:43:58 +0800 [debug]: Server 127.0.0.1:8001 starting listener 95 | 2019-09-08 14:43:58 +0800 [info]: Server 127.0.0.1:8001 starting accept 96 | ~~~ 97 | 98 | ## Test it ## 99 | 100 | Write a test script named phpinfo.php and save to c:\caddy\www 101 | 102 | ~~~ 103 | 104 | ~~~ 105 | 106 | Open url http://localhost:2015/phpinfo.php 107 | 108 | Done. 109 | 110 | -------------------------------------------------------------------------------- /conf/conf.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "os" 7 | ) 8 | 9 | // Conf : JSON root 10 | type Conf struct { 11 | // Instances 為陣列,包含了多個 ConfInstance 12 | Instances []Instance 13 | LogLevel string `json:"LogLevel"` 14 | Logger *Logger `json:"Logger"` 15 | } 16 | 17 | // Instance : JSON Instances 18 | type Instance struct { 19 | Bind string `json:"Bind"` 20 | ExecPath string `json:"ExecPath"` 21 | Args []string `json:"Args"` 22 | Env []string `json:"Env"` 23 | // MaxRequestsPerProcess 每個php-cgi行程最多能夠處理幾次要求 , Default 500 24 | MaxRequestsPerProcess int `json:"MaxRequestsPerProcess,500"` 25 | // MaxProcesses 定義 Instance 啟動 php-cgi 的最大數量,default 4 26 | MaxProcesses int `json:"MaxProcesses,4"` 27 | // Note 只是註解,此欄位沒有任何作用 28 | Note string `json:"-"` 29 | } 30 | 31 | // Logger : the same lumberjack.Logger 32 | // see https://github.com/natefinch/lumberjack 33 | type Logger struct { 34 | Filename string `json:"Filename"` 35 | MaxSize int `json:"MaxSize,10"` 36 | MaxAge int `json:"MaxAge,7"` 37 | MaxBackups int `json:"MaxBackups,4"` 38 | LocalTime bool `json:"LocalTime,true"` 39 | Compress bool `json:"Compress,false"` 40 | } 41 | 42 | // LoadFile 讀取 JSON 設定檔,並返回 *Conf 43 | func LoadFile(filePath string) (conf *Conf, err error) { 44 | 45 | jsonFile, err := os.Open(filePath) 46 | if err != nil { 47 | return 48 | } 49 | defer jsonFile.Close() 50 | 51 | conf = &Conf{LogLevel: "ERROR"} 52 | byteValue, _ := ioutil.ReadAll(jsonFile) 53 | err = json.Unmarshal(byteValue, &conf) 54 | return 55 | } 56 | 57 | // FileExist config file is exist 檢查檔案是否存在 58 | func FileExist(filePath string) bool { 59 | if _, err := os.Stat(filePath); err != nil { 60 | if os.IsNotExist(err) { 61 | return false 62 | } 63 | } 64 | return true 65 | } 66 | -------------------------------------------------------------------------------- /config-sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "LogLevel" : "ERROR", 3 | "Logger": { 4 | "FileName": "C:\\wphpfpm\\wphpfpm.log", 5 | "MaxSize": 10, 6 | "MaxBackups": 4, 7 | "MaxAge": 7, 8 | "Compress": true, 9 | "Note": "如果不需要 Logger, 可以拿掉整個 Logger 區段 , MaxSize 是 MB 為單位 , MaxAge 是以天為單位,本例子為每一份log有7天的內容" 10 | }, 11 | "Instances" : [ 12 | { 13 | "Bind" : "127.0.0.1:8000", 14 | "ExecPath": "C:\\PHP7\\php-cgi.exe", 15 | "Args" : [], 16 | "Env": [ 17 | "PHPRC=C:\\PHP7", 18 | "PHP_FCGI_MAX_REQUESTS=5000" , 19 | "PHP_INI_SCAN_DIR=c:\\php7\\conf.d" 20 | ], 21 | "MaxProcesses" : 4, 22 | "MaxRequestsPerProcess": 500 23 | } , 24 | 25 | { 26 | "Bind" : "127.0.0.1:8001", 27 | "ExecPath": "C:\\PHP5\\php-cgi.exe", 28 | "Args" : [], 29 | "Env": [ 30 | "PHPRC=C:\\PHP5", 31 | "PHP_FCGI_MAX_REQUESTS=5000" , 32 | "PHP_INI_SCAN_DIR=c:\\php5\\conf.d" 33 | ], 34 | "MaxProcesses" : 2, 35 | "MaxRequestsPerProcess": 500 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module wphpfpm 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/BurntSushi/toml v0.3.1 // indirect 7 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect 8 | github.com/alecthomas/units v0.0.0-20190910110746-680d30ca3117 // indirect 9 | github.com/chai2010/winsvc v0.0.0-20161110002403-fe57a9a621ec 10 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect 11 | github.com/kr/pretty v0.1.0 // indirect 12 | github.com/sirupsen/logrus v1.4.2 13 | github.com/stretchr/objx v0.2.0 // indirect 14 | golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 // indirect 15 | golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2 16 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect 17 | golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8 18 | golang.org/x/text v0.3.2 // indirect 19 | golang.org/x/tools v0.0.0-20190912152909-b0a6c2aa3ffa // indirect 20 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 21 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 22 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 23 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce 24 | ) 25 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 8 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 9 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 10 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 11 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 12 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= 13 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 14 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= 15 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 16 | github.com/alecthomas/units v0.0.0-20190910110746-680d30ca3117 h1:aUo+WrWZtRRfc6WITdEKzEczFRlEpfW15NhNeLRc17U= 17 | github.com/alecthomas/units v0.0.0-20190910110746-680d30ca3117/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 18 | github.com/antonfisher/nested-logrus-formatter v1.0.2 h1:t65eOqj0fWbOkZR2+OgmxPa0KYIwbPhKdYmseaCMIyI= 19 | github.com/antonfisher/nested-logrus-formatter v1.0.2/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA= 20 | github.com/chai2010/winsvc v0.0.0-20161110002403-fe57a9a621ec h1:zbWtLeEEAXSIpOUm1N4JCzBIcKFJ64ZczZNk/1vls38= 21 | github.com/chai2010/winsvc v0.0.0-20161110002403-fe57a9a621ec/go.mod h1:b9Xy0A0C/binZARjeVfHEr+gHzQUVztL71bTms7PRIM= 22 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 23 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 24 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 25 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 27 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 28 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 29 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 30 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 31 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 32 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 33 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 34 | github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= 35 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 36 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 37 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 38 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 39 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 40 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 41 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 42 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 43 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 44 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 45 | github.com/google/pprof v0.0.0-20190723021845-34ac40c74b70/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 46 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 47 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 48 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 49 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 50 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 51 | github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 52 | github.com/joonix/log v0.0.0-20190524090622-13fe31bbdd7a h1:LL1gwNo4Z1LG68SaaNb8bxB+YnMSilYzytRfkF3AigE= 53 | github.com/joonix/log v0.0.0-20190524090622-13fe31bbdd7a/go.mod h1:fS54ONkjDV71zS9CDx3V9K21gJg7byKSvI4ajuWFNJw= 54 | github.com/jpillora/go-tcp-proxy v0.0.0-20190818024305-f42e8ed734f6 h1:BxJI+o9hhYoDUv7GhlPxT4ABVCjZkIIUPi2TY3mmMgI= 55 | github.com/jpillora/go-tcp-proxy v0.0.0-20190818024305-f42e8ed734f6/go.mod h1:YYGX8P8Drrs+UnXo3/0XU4e0Xdjq8vvsOm89n+oJj40= 56 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 57 | github.com/kavu/go_reuseport v1.4.0 h1:YIp/96RZ3sJfn0LN+FFkkXIq3H3dfVOdRUtNejhDcxc= 58 | github.com/kavu/go_reuseport v1.4.0/go.mod h1:CG8Ee7ceMFSMnx/xr25Vm0qXaj2Z4i5PWoUx+JZ5/CU= 59 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 60 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 61 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 62 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= 63 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 64 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 65 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 66 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 67 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 68 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 69 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 70 | github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= 71 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 72 | github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= 73 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 74 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 75 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= 76 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 77 | github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= 78 | github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= 79 | github.com/natefinch/npipe v0.0.0-20160621034901-c1b8fa8bdcce h1:TqjP/BTDrwN7zP9xyXVuLsMBXYMt6LLYi55PlrIcq8U= 80 | github.com/natefinch/npipe v0.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:ifHPsLndGGzvgzcaXUvzmt6LxKT4pJ+uzEhtnMt+f7A= 81 | github.com/novalagung/golpal v1.0.0 h1:33TidKDH6Jr7wwLrq/GvuZve67q+jJkeRty5pRrlPb8= 82 | github.com/novalagung/golpal v1.0.0/go.mod h1:pOW1E6nhjAvi8l0wXkkJvKjbTKdyA29C40yjIL5XZcg= 83 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 84 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 85 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 86 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 87 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 88 | github.com/rogpeppe/go-internal v1.3.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 89 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 90 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 91 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 92 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 93 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 94 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 95 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 96 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 97 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 98 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 99 | github.com/tidwall/evio v1.0.2 h1:vhPp5nVS5PZfR0CfV5ixqDEf+BKQFcaR1W1XeLLkvRE= 100 | github.com/tidwall/evio v1.0.2/go.mod h1:cYtY49LddNrlpsOmW7qJnqM8B2gOjrFrzT8+Fnb/GKs= 101 | github.com/uber-go/zap v1.10.0 h1:4pFX6Frb+nVIH8QS73XEiyPcKrJ/C25Z2xpudBIlOnE= 102 | github.com/uber-go/zap v1.10.0/go.mod h1:GY+83l3yxBcBw2kmHu/sAWwItnTn+ynxHCRo+WiIQOY= 103 | github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= 104 | github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= 105 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 106 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 107 | go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= 108 | go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= 109 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 110 | go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= 111 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 112 | go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= 113 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 114 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 115 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 116 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 117 | golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM= 118 | golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 119 | golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 120 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 121 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 122 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 123 | golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= 124 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 125 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 126 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 127 | golang.org/x/image v0.0.0-20190902063713-cb417be4ba39/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 128 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 129 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 130 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 131 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 132 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 133 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 134 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 135 | golang.org/x/mobile v0.0.0-20190830201351-c6da95954960/go.mod h1:mJOp/i0LXPxJZ9weeIadcPqKVfS05Ai7m6/t9z1Hs/Y= 136 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 137 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 138 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 139 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 140 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 141 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 142 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 143 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 144 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 145 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 146 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 147 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 148 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= 149 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 150 | golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2 h1:4dVFTC832rPn4pomLSz1vA+are2+dU19w1H8OngV7nc= 151 | golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 152 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 153 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 154 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 155 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 156 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 157 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 158 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 159 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 160 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 161 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 162 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 163 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 164 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 165 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 166 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 167 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= 168 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 169 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 170 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 171 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 172 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 173 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= 174 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 175 | golang.org/x/sys v0.0.0-20190825160603-fb81701db80f h1:LCxigP8q3fPRGNVYndYsyHnF0zRrvcoVwZMfb8iQZe4= 176 | golang.org/x/sys v0.0.0-20190825160603-fb81701db80f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 177 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= 178 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 179 | golang.org/x/sys v0.0.0-20190904005037-43c01164e931 h1:+WYfosiOJzB4BjsISl1Rv4ZLUy+VYcF+u+0Y9jcerv8= 180 | golang.org/x/sys v0.0.0-20190904005037-43c01164e931/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 181 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0= 182 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 183 | golang.org/x/sys v0.0.0-20190907184412-d223b2b6db03 h1:b3JiLYVaG9kHjTcOQIoUh978YMCO7oVTQQBLudU47zY= 184 | golang.org/x/sys v0.0.0-20190907184412-d223b2b6db03/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 185 | golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8 h1:41hwlulw1prEMBxLQSlMSux1zxJf07B3WPsdjJlKZxE= 186 | golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 187 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 188 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 189 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 190 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 191 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 192 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 193 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 194 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 195 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 196 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 197 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 198 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 199 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 200 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 201 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 202 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 203 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 204 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 205 | golang.org/x/tools v0.0.0-20190903163617-be0da057c5e3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 206 | golang.org/x/tools v0.0.0-20190905235650-93dcc2f048f5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 207 | golang.org/x/tools v0.0.0-20190912152909-b0a6c2aa3ffa/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 208 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 209 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 210 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 211 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 212 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 213 | google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 214 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 215 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 216 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 217 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 218 | google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 219 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 220 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 221 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 222 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 223 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 224 | google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69 h1:4rNOqY4ULrKzS6twXa619uQgI7h9PaVd4ZhjFQ7C5zs= 225 | google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= 226 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 227 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 228 | google.golang.org/genproto v0.0.0-20190905072037-92dd089d5514/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 229 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 230 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 231 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 232 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 233 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= 234 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 235 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 236 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 237 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 238 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 239 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 240 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 241 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 242 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= 243 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 244 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= 245 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= 246 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 247 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 248 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 249 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 250 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 251 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 252 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 253 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 254 | -------------------------------------------------------------------------------- /logformat_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "strings" 7 | "testing" 8 | "unsafe" 9 | 10 | log "github.com/sirupsen/logrus" 11 | ) 12 | 13 | const ( 14 | testTimeFormat = "2006-01-02 15:04:05 -0700" 15 | ) 16 | 17 | type BufferFormatter struct { 18 | timeFormat string 19 | } 20 | 21 | func (f *BufferFormatter) Format(entry *log.Entry) ([]byte, error) { 22 | var b *bytes.Buffer 23 | if entry.Buffer != nil { 24 | b = entry.Buffer 25 | } else { 26 | b = &bytes.Buffer{} 27 | } 28 | b.WriteString(entry.Time.Format(f.timeFormat)) 29 | b.WriteString(" [") 30 | b.WriteString(entry.Level.String()) 31 | b.WriteString("]: ") 32 | b.WriteString(entry.Message) 33 | b.WriteByte('\n') 34 | return b.Bytes(), nil 35 | } 36 | 37 | type StringAddFormatter struct { 38 | timeFormat string 39 | } 40 | 41 | func (f *StringAddFormatter) Format(entry *log.Entry) ([]byte, error) { 42 | var b string 43 | b = entry.Time.Format(f.timeFormat) + " [" + entry.Level.String() + "]: " + entry.Message + "\n" 44 | return []byte(b), nil 45 | } 46 | 47 | type StringAddUnsafeFormatter struct { 48 | timeFormat string 49 | } 50 | 51 | func (f *StringAddUnsafeFormatter) Format(entry *log.Entry) ([]byte, error) { 52 | var s string 53 | s = entry.Time.Format(f.timeFormat) + " [" + entry.Level.String() + "]: " + entry.Message + "\n" 54 | return *(*[]byte)(unsafe.Pointer(&s)), nil 55 | } 56 | 57 | type StringBuilderFormatter struct { 58 | timeFormat string 59 | } 60 | 61 | func (f *StringBuilderFormatter) Format(entry *log.Entry) ([]byte, error) { 62 | var builder strings.Builder 63 | builder.WriteString(entry.Time.Format(f.timeFormat)) 64 | builder.WriteString(" [") 65 | builder.WriteString(entry.Level.String()) 66 | builder.WriteString("]: ") 67 | builder.WriteString(entry.Message) 68 | builder.WriteByte('\n') 69 | return []byte(builder.String()), nil 70 | } 71 | 72 | type StringBuilderUnsafeFormatter struct { 73 | timeFormat string 74 | } 75 | 76 | func (f *StringBuilderUnsafeFormatter) Format(entry *log.Entry) ([]byte, error) { 77 | var builder strings.Builder 78 | 79 | builder.WriteString(entry.Time.Format(f.timeFormat)) 80 | builder.WriteString(" [") 81 | builder.WriteString(entry.Level.String()) 82 | builder.WriteString("]: ") 83 | builder.WriteString(entry.Message) 84 | builder.WriteByte('\n') 85 | s := builder.String() 86 | return *(*[]byte)(unsafe.Pointer(&s)), nil 87 | } 88 | 89 | func BenchmarkFormatBuffer(b *testing.B) { 90 | formatter := &BufferFormatter{timeFormat: "2006-01-02 15:04:05 -0700"} 91 | log.SetFormatter(formatter) 92 | log.SetLevel(log.DebugLevel) 93 | log.SetOutput(ioutil.Discard) 94 | for i := 0; i < b.N; i++ { 95 | log.Debugf("%d This is a test message blah blah blah blah blah blah blah blah blah blah ...", i) 96 | } 97 | } 98 | 99 | func BenchmarkFormatStringAdd(b *testing.B) { 100 | formatter := &StringAddFormatter{timeFormat: "2006-01-02 15:04:05 -0700"} 101 | log.SetFormatter(formatter) 102 | log.SetLevel(log.DebugLevel) 103 | log.SetOutput(ioutil.Discard) 104 | for i := 0; i < b.N; i++ { 105 | log.Debugf("%d This is a test message blah blah blah blah blah blah blah blah blah blah ...", i) 106 | } 107 | } 108 | 109 | func BenchmarkFormatStringAddUnsafe(b *testing.B) { 110 | formatter := &StringAddUnsafeFormatter{timeFormat: "2006-01-02 15:04:05 -0700"} 111 | log.SetFormatter(formatter) 112 | log.SetLevel(log.DebugLevel) 113 | log.SetOutput(ioutil.Discard) 114 | for i := 0; i < b.N; i++ { 115 | log.Debugf("%d This is a test message blah blah blah blah blah blah blah blah blah blah ...", i) 116 | } 117 | } 118 | 119 | func BenchmarkFormatBuilder(b *testing.B) { 120 | formatter := &StringBuilderFormatter{timeFormat: "2006-01-02 15:04:05 -0700"} 121 | log.SetFormatter(formatter) 122 | log.SetLevel(log.DebugLevel) 123 | log.SetOutput(ioutil.Discard) 124 | for i := 0; i < b.N; i++ { 125 | log.Debugf("%d This is a test message blah blah blah blah blah blah blah blah blah blah ...", i) 126 | } 127 | } 128 | 129 | func BenchmarkFormatBuilderUnsafe(b *testing.B) { 130 | formatter := &StringBuilderUnsafeFormatter{timeFormat: "2006-01-02 15:04:05 -0700"} 131 | log.SetFormatter(formatter) 132 | log.SetLevel(log.DebugLevel) 133 | log.SetOutput(ioutil.Discard) 134 | for i := 0; i < b.N; i++ { 135 | log.Debugf("%d This is a test message blah blah blah blah blah blah blah blah blah blah ...", i) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "path/filepath" 9 | "strings" 10 | "sync" 11 | "wphpfpm/conf" 12 | "wphpfpm/phpfpm" 13 | "wphpfpm/server" 14 | 15 | "github.com/chai2010/winsvc" 16 | log "github.com/sirupsen/logrus" 17 | "gopkg.in/alecthomas/kingpin.v2" 18 | "gopkg.in/natefinch/lumberjack.v2" 19 | ) 20 | 21 | var ( 22 | serviceName = "wphpfpm" 23 | serviceDesc = "PHP FastCGI Manager for windows" 24 | app = kingpin.New(serviceName, serviceDesc) 25 | commandInstall *kingpin.CmdClause 26 | commandUninstall *kingpin.CmdClause 27 | commandStart *kingpin.CmdClause 28 | commandStop *kingpin.CmdClause 29 | commandRun *kingpin.CmdClause 30 | flagConfigFile *string 31 | 32 | servers []*server.Server 33 | ) 34 | 35 | func main() { 36 | if !winsvc.IsAnInteractiveSession() { 37 | // run as service 38 | flag := kingpin.Flag("conf", "Config file path , required by install or run.") 39 | flagConfigFile = flag.Required().String() 40 | kingpin.Parse() 41 | checkConfigFileExist(*flagConfigFile) 42 | 43 | fmt.Println(serviceName, "Run service") 44 | if err := winsvc.RunAsService(serviceName, startService, stopService, false); err != nil { 45 | log.Fatalf(serviceName+" run: %v\n", err) 46 | } 47 | } else { 48 | // command line mode 49 | initCommandFlag() 50 | switch kingpin.Parse() { 51 | case commandInstall.FullCommand(): 52 | checkConfigFileExist(*flagConfigFile) 53 | installService() 54 | case commandUninstall.FullCommand(): 55 | if err := winsvc.RemoveService(serviceName); err != nil { 56 | fmt.Println("Uninstall service: ", err) 57 | os.Exit(1) 58 | } 59 | fmt.Println("Uninstall service: success") 60 | case commandRun.FullCommand(): 61 | checkConfigFileExist(*flagConfigFile) 62 | startService() 63 | case commandStart.FullCommand(): 64 | if err := winsvc.StartService(serviceName); err != nil { 65 | fmt.Println("Start service:", err) 66 | os.Exit(1) 67 | } 68 | fmt.Println("Start service: success") 69 | case commandStop.FullCommand(): 70 | if err := winsvc.StopService(serviceName); err != nil { 71 | fmt.Println("Stop service:", err) 72 | os.Exit(1) 73 | } 74 | fmt.Println("Stop service: success") 75 | return 76 | } 77 | } 78 | 79 | } 80 | 81 | func initCommandFlag() { 82 | commandInstall = kingpin.Command("install", "Install as service") 83 | commandUninstall = kingpin.Command("uninstall", "Uninstall service") 84 | commandStart = kingpin.Command("start", "Start service.") 85 | commandStop = kingpin.Command("stop", "Stop service.") 86 | commandRun = kingpin.Command("run", "Run in console mode") 87 | flag := kingpin.Flag("conf", "Config file path , required by install or run.") 88 | if len(os.Args) > 1 && (os.Args[1] == "install" || os.Args[1] == "run") { 89 | flagConfigFile = flag.Required().String() 90 | } else { 91 | flagConfigFile = flag.String() 92 | } 93 | } 94 | 95 | // 安裝服務 96 | func installService() { 97 | var serviceExec string 98 | var err error 99 | 100 | if serviceExec, err = winsvc.GetAppPath(); err != nil { 101 | fmt.Println(err) 102 | os.Exit(1) 103 | } 104 | if err := os.Chdir(filepath.Dir(serviceExec)); err != nil { 105 | fmt.Println(err) 106 | os.Exit(1) 107 | } 108 | 109 | abs, err := filepath.Abs(*flagConfigFile) 110 | 111 | serviceExecFull := "\"" + serviceExec + "\"" + " --conf=" + abs 112 | args := []string{"--conf", abs} 113 | fmt.Printf("Service install name : %s , binpath : %s\n", serviceName, serviceExecFull) 114 | if err := winsvc.InstallService(serviceExec, serviceName, serviceDesc, args...); err != nil { 115 | fmt.Printf("Install service error : %s\n", err.Error()) 116 | os.Exit(1) 117 | } 118 | 119 | fmt.Println("Install service successfully.") 120 | } 121 | 122 | // 啟動服務 123 | func startService() { 124 | config, err := conf.LoadFile(*flagConfigFile) 125 | 126 | if err != nil { 127 | fmt.Printf("Config load error : %s\n", err.Error()) 128 | os.Exit(1) 129 | } 130 | 131 | fmt.Printf("Start in console mode , press CTRL+C to exit ...\r\n") 132 | initLogger(config) 133 | err = phpfpm.Start(config) 134 | if err != nil { 135 | log.Fatalf("Can not start service : %s\n", err.Error()) 136 | } 137 | 138 | var events server.Event 139 | 140 | events.OnConnect = func(c *server.Conn) (action server.Action) { 141 | 142 | p := phpfpm.GetIdleProcess(c.Server().Tag.(int)) 143 | 144 | if p == nil { 145 | if log.IsLevelEnabled(log.ErrorLevel) { 146 | log.Error("Can not get php-cgi process") 147 | } 148 | action = server.Close 149 | return 150 | } 151 | serr, terr := p.Proxy(c) // blocked 152 | if log.IsLevelEnabled(log.DebugLevel) { 153 | log.Debugf("php-cgi(%s) proxy error , serr : %s , terr : %s", p.ExecWithPippedName(), serr, terr) 154 | } 155 | 156 | phpfpm.PutIdleProcess(p) 157 | 158 | return 159 | } 160 | 161 | conf := phpfpm.Conf() 162 | 163 | var wg sync.WaitGroup 164 | 165 | wg.Add(len(conf.Instances)) 166 | 167 | servers = make([]*server.Server, len(conf.Instances)) 168 | 169 | for i := 0; i < len(conf.Instances); i++ { 170 | instance := conf.Instances[i] 171 | servers[i] = &server.Server{MaxConnections: instance.MaxProcesses, BindAddress: instance.Bind, Tag: i} 172 | 173 | log.Infof("Start server #%d on %s", i, servers[i].BindAddress) 174 | 175 | go func(s *server.Server) { 176 | err := s.Serve(events) 177 | if err != nil { 178 | log.Errorf("Service serve error : %s", err.Error()) 179 | } 180 | wg.Done() 181 | }(servers[i]) 182 | } 183 | log.Info("Service running ...") 184 | 185 | // 這段處理 CTRL + C 186 | c := make(chan os.Signal, 1) 187 | signal.Notify(c, os.Interrupt) 188 | go func() { 189 | for sig := range c { 190 | log.Infof("Service got signal: %s", sig.String()) 191 | stopService() 192 | return 193 | } 194 | }() 195 | 196 | wg.Wait() 197 | phpfpm.Stop() 198 | log.Info("Service Stopped.") 199 | } 200 | 201 | // 停止服務 202 | func stopService() { 203 | 204 | for i := 0; i < len(servers); i++ { 205 | servers[i].Shutdown() 206 | } 207 | 208 | } 209 | 210 | func checkConfigFileExist(filepath string) { 211 | exist := conf.FileExist(filepath) 212 | if !exist { 213 | fmt.Printf("Could not load config file : %s", filepath) 214 | os.Exit(1) 215 | } 216 | } 217 | 218 | func initLogger(config *conf.Conf) { 219 | 220 | formatter := &MyTextFormatter{timeFormat: "2006-01-02 15:04:05 -0700"} 221 | log.SetFormatter(formatter) 222 | 223 | // Set logger 224 | if len(config.Logger.Filename) > 0 { 225 | 226 | logDir := filepath.Dir(config.Logger.Filename) 227 | exeDir, err := filepath.Abs(filepath.Dir(os.Args[0])) // 執行檔的路徑 228 | if err != nil { 229 | log.Fatal(err) 230 | } 231 | 232 | if logDir == "." || logDir == "" { 233 | // 如果 Filename 沒指定路徑,修正為 exe 的路徑 234 | config.Logger.Filename = exeDir + "\\" + config.Logger.Filename 235 | } 236 | 237 | logger := &lumberjack.Logger{ 238 | Filename: config.Logger.Filename, 239 | MaxSize: config.Logger.MaxSize, 240 | MaxBackups: config.Logger.MaxBackups, 241 | MaxAge: config.Logger.MaxAge, 242 | Compress: config.Logger.Compress, 243 | } 244 | log.SetOutput(logger) 245 | fmt.Printf("Logger ouput set to file %s .\n", config.Logger.Filename) 246 | } else { 247 | fmt.Printf("Logger ouput set to console.\n") 248 | } 249 | 250 | if config.LogLevel == "" { 251 | config.LogLevel = "ERROR" 252 | } 253 | 254 | logLevel, err := log.ParseLevel(config.LogLevel) 255 | if err != nil { 256 | log.Fatalf("LogLevel %s can not parse.", config.LogLevel) 257 | } 258 | log.SetLevel(logLevel) 259 | log.Infof("Set LogLevel to %s.", strings.ToUpper(logLevel.String())) 260 | 261 | // Repair config 262 | for i := 0; i < len(config.Instances); i++ { 263 | if config.Instances[i].MaxRequestsPerProcess < 1 { 264 | // Repair MaxRequestsPerProcess 265 | log.Warnf("Instance #%d MaxRequestsPerProcess is less 1 , set to 500", i) 266 | config.Instances[i].MaxRequestsPerProcess = 500 267 | } 268 | 269 | if config.Instances[i].MaxProcesses < 1 { 270 | //Repair MaxProcesses 271 | log.Warnf("Instance #%d MaxProcesses is less 1 , set to 4", i) 272 | config.Instances[i].MaxProcesses = 4 273 | } 274 | } 275 | } 276 | 277 | // MyTextFormatter logrus custom formatter 278 | type MyTextFormatter struct { 279 | timeFormat string 280 | } 281 | 282 | // Format logrus custom format 283 | func (f *MyTextFormatter) Format(entry *log.Entry) ([]byte, error) { 284 | var b *bytes.Buffer 285 | if entry.Buffer != nil { 286 | b = entry.Buffer 287 | } else { 288 | b = &bytes.Buffer{} 289 | } 290 | b.WriteString(entry.Time.Format(f.timeFormat)) 291 | b.WriteString(" [") 292 | b.WriteString(entry.Level.String()) 293 | b.WriteString("]: ") 294 | b.WriteString(entry.Message) 295 | b.WriteByte('\n') 296 | return b.Bytes(), nil 297 | } 298 | -------------------------------------------------------------------------------- /phpfpm/phpfpm.go: -------------------------------------------------------------------------------- 1 | package phpfpm 2 | 3 | import ( 4 | "container/list" 5 | "sync" 6 | "wphpfpm/conf" 7 | 8 | log "github.com/sirupsen/logrus" 9 | ) 10 | 11 | // Instance : struct 12 | type Instance struct { 13 | execPath string 14 | args []string 15 | env []string 16 | processes []Process 17 | } 18 | 19 | var ( 20 | // conf 是 json 讀進來後產生的設定 21 | phpfpmConf *conf.Conf 22 | // idleProcesses php-cgi 如果沒有任何連線處理,都存在這 23 | idleProcesses []*list.List 24 | stopManage = false // 如果調用 Stop() , 這個會是 true , 同時 mon() 也不會繼續監控 25 | mutex sync.Mutex 26 | ) 27 | 28 | // Conf : get Json config 29 | func Conf() *conf.Conf { 30 | return phpfpmConf 31 | } 32 | 33 | // Start php-cgi manager 34 | func Start(conf *conf.Conf) (err error) { 35 | log.Info("phpfpm starting.") 36 | phpfpmConf = conf 37 | instanceLen := len(conf.Instances) 38 | idleProcesses = make([]*list.List, instanceLen) 39 | for i := 0; i < instanceLen; i++ { 40 | idleProcesses[i] = list.New() 41 | 42 | for j := 0; j < conf.Instances[i].MaxProcesses; j++ { 43 | instance := conf.Instances[i] 44 | p := newProcess(instance.ExecPath, instance.Args, instance.Env) 45 | p.instanceIndex = i 46 | err := p.TryStart() 47 | if err == nil { 48 | mutex.Lock() 49 | p.mapElement = idleProcesses[i].PushBack(p) 50 | mutex.Unlock() 51 | go monProcess(p) 52 | } else { 53 | Stop() 54 | return err 55 | } 56 | } 57 | } 58 | log.Info("phpfpm is in loop.") 59 | return 60 | } 61 | 62 | // monProcess 監控 php-cgi 狀態是否跳出 63 | func monProcess(p *Process) { 64 | log.Infof("Starting monitor php-cgi(%s)", p.ExecWithPippedName()) 65 | defer log.Infof("Stopped monitor php-cgi(%s)", p.ExecWithPippedName()) 66 | for { 67 | err := p.cmd.Wait() 68 | if p.requestCount >= phpfpmConf.Instances[p.instanceIndex].MaxRequestsPerProcess { 69 | err = p.TryStart() 70 | if err != nil { 71 | p.restartChan <- false 72 | } else { 73 | p.restartChan <- true 74 | } 75 | 76 | continue 77 | } 78 | if err != nil { 79 | log.Errorf("php-cgi(%s) exit error, because %s", p.ExecWithPippedName(), err.Error()) 80 | } 81 | 82 | mutex.Lock() 83 | 84 | if stopManage { 85 | // 執行 phpfpm.Stop() 代表不需要再監控了 86 | mutex.Unlock() 87 | return 88 | } 89 | 90 | idleProcesses[p.instanceIndex].Remove(p.mapElement) 91 | err = p.TryStart() 92 | 93 | if err != nil { 94 | // 退出監控 95 | log.Errorf("php-cgi(%s) restart error, because %s", p.ExecWithPippedName(), err.Error()) 96 | mutex.Unlock() 97 | return 98 | } 99 | // 啟動成功 100 | p.mapElement = idleProcesses[p.instanceIndex].PushBack(p) 101 | mutex.Unlock() 102 | if log.IsLevelEnabled(log.InfoLevel) { 103 | log.Infof("php-cgi(%s) restart successfully.", p.ExecWithPippedName()) 104 | } 105 | } 106 | } 107 | 108 | // Stop php-cgi manager , 所有的 process kill 109 | func Stop() { 110 | log.Info("phpfpm stoping.") 111 | stopManage = true 112 | mutex.Lock() 113 | defer mutex.Unlock() 114 | 115 | var next *list.Element 116 | 117 | for _, v := range idleProcesses { 118 | for e := v.Front(); e != nil; e = next { 119 | next = e.Next() 120 | p := e.Value.(*Process) 121 | p.Kill() 122 | v.Remove(e) 123 | } 124 | 125 | } 126 | log.Info("phpfpm stopped.") 127 | } 128 | 129 | // GetIdleProcess : 取得任何一個 Idle 的 Process , 並且移除 Idle 列表 130 | func GetIdleProcess(instanceIndex int) (p *Process) { 131 | mutex.Lock() 132 | defer mutex.Unlock() 133 | e := idleProcesses[instanceIndex].Front() 134 | if e != nil { 135 | p = idleProcesses[instanceIndex].Remove(e).(*Process) 136 | p.mapElement = nil 137 | } 138 | return 139 | } 140 | 141 | // PutIdleProcess : 設定 Process 為 idle 142 | func PutIdleProcess(p *Process) (err error) { 143 | 144 | mutex.Lock() 145 | defer mutex.Unlock() 146 | if p.pipe != nil { 147 | err = p.pipe.Close() 148 | p.pipe = nil 149 | } 150 | 151 | if p.requestCount >= phpfpmConf.Instances[p.instanceIndex].MaxRequestsPerProcess { 152 | log.Warnf("php-cgi(%s) handled %d requests , need restart.", p.execWithPippedName, p.requestCount) 153 | p.Kill() 154 | if true == <-p.restartChan { 155 | p.mapElement = idleProcesses[p.instanceIndex].PushBack(p) 156 | } else { 157 | log.Errorf("php-cgi(%s) restart faild.", p.execWithPippedName) 158 | } 159 | } else { 160 | p.mapElement = idleProcesses[p.instanceIndex].PushBack(p) 161 | if log.IsLevelEnabled(log.DebugLevel) { 162 | log.Debugf("php-cgi(%s) is idle , requests count : %d", p.execWithPippedName, p.requestCount) 163 | } 164 | } 165 | return 166 | } 167 | -------------------------------------------------------------------------------- /phpfpm/process.go: -------------------------------------------------------------------------------- 1 | package phpfpm 2 | 3 | import ( 4 | "container/list" 5 | "io" 6 | "net" 7 | "os" 8 | "os/exec" 9 | "strconv" 10 | "sync" 11 | "time" 12 | 13 | log "github.com/sirupsen/logrus" 14 | "gopkg.in/natefinch/npipe.v2" 15 | ) 16 | 17 | // Process : struct 18 | type Process struct { 19 | execPath string 20 | args []string 21 | env []string 22 | cmd *exec.Cmd 23 | instanceIndex int // 這個是在 phpfpm.go 中的 idleprocess 連結用的 , 代表這個 Process 是屬於那個 Instance 24 | mapElement *list.Element 25 | pipe *npipe.PipeConn 26 | pippedName string // php-cgi 執行時指定的 pipped name 27 | 28 | requestCount int // 紀錄當前執行中的 php-cgi 已經接受幾次要求了 29 | 30 | restartChan chan bool 31 | 32 | copyRbuf []byte 33 | copyWbuf []byte 34 | execWithPippedName string 35 | 36 | wg sync.WaitGroup 37 | } 38 | 39 | var ( 40 | namedPipeNumber = time.Now().Unix() 41 | namedPipeNumberMutex sync.Mutex 42 | ) 43 | 44 | // newProcess : Create new Process 45 | // 建立一個新的 Process 46 | func newProcess(execPath string, args []string, env []string) *Process { 47 | p := new(Process) 48 | p.execPath = execPath 49 | p.args = args 50 | p.env = env 51 | p.restartChan = make(chan bool) 52 | p.copyRbuf = make([]byte, 4096) 53 | p.copyWbuf = make([]byte, 16384) 54 | return p 55 | } 56 | 57 | // TryStart will execute php-cgi twince 58 | func (p *Process) TryStart() (err error) { 59 | // pippedName 是啟動 php-cgi 時候指定 -b pipename 使用的 60 | namedPipeNumberMutex.Lock() 61 | namedPipeNumber++ 62 | namedPipeNumberMutex.Unlock() 63 | p.pippedName = `\\.\pipe\wphpfpm\wphpfpm.` + strconv.FormatInt(namedPipeNumber, 10) 64 | p.requestCount = 0 65 | p.execWithPippedName = p.execPath + " -> " + p.pippedName 66 | 67 | log.Debugf("Trying to start php-cgi(%s).", p.execWithPippedName) 68 | for i := 0; i < 2; i++ { 69 | args := append(p.args, "-b", p.pippedName) 70 | p.cmd = nil 71 | p.cmd = exec.Command(p.execPath, args...) 72 | p.cmd.Env = os.Environ() 73 | p.cmd.Env = append(p.cmd.Env, p.env...) 74 | err = p.cmd.Start() 75 | if err == nil { 76 | i = 3 77 | if log.IsLevelEnabled(log.DebugLevel) { 78 | log.Debugf("php-cgi(%s) executing now.", p.execWithPippedName) 79 | } 80 | } 81 | } 82 | 83 | if err != nil { 84 | log.Errorf("php-cgi(%s) can not start , because %s", p.execWithPippedName, err.Error()) 85 | } 86 | return 87 | } 88 | 89 | // connectPipe will connect to php-cgi named pipe 90 | func (p *Process) connectPipe() error { 91 | var err error 92 | //if p.pipe != nil { 93 | // p.pipe.Close() 94 | //} 95 | 96 | p.pipe, err = npipe.Dial(p.pippedName) 97 | if err != nil { 98 | log.Errorf("Connect to php-cgi(%s) error , because %s", p.execWithPippedName, err.Error()) 99 | return err 100 | } 101 | p.requestCount++ 102 | if log.IsLevelEnabled(log.DebugLevel) { 103 | log.Debugf("Connect to php-cgi(%s) successfully.", p.execWithPippedName) 104 | } 105 | 106 | return nil 107 | } 108 | 109 | // Proxy net.Conn <> Windows-named-pipe 110 | // Proxy 將 tcp 來源跟 windows named pipe 直接做讀寫 111 | // 返回值 serr 代表由 http server 讀取資料寫至 php-cgi 的錯誤 112 | // 返回值 terr 代表由 php-cgi 讀取資料寫至 http server 的錯誤 113 | func (p *Process) Proxy(conn net.Conn) (serr error, terr error) { 114 | 115 | terr = p.connectPipe() 116 | if terr != nil { 117 | return 118 | } 119 | 120 | p.wg.Add(2) 121 | go func() { 122 | // read from web server , write to php-cgi 123 | _, serr = io.CopyBuffer(p.pipe, conn, p.copyRbuf) 124 | p.wg.Done() 125 | }() 126 | go func() { 127 | // read from php-cgi , write to web server 128 | _, terr = io.CopyBuffer(conn, p.pipe, p.copyWbuf) 129 | p.wg.Done() 130 | }() 131 | 132 | p.wg.Wait() 133 | p.pipe.Close() 134 | p.pipe = nil 135 | return 136 | } 137 | 138 | // Kill php-cgi process 139 | func (p *Process) Kill() (err error) { 140 | err = p.cmd.Process.Kill() 141 | 142 | if err != nil { 143 | if log.IsLevelEnabled(log.ErrorLevel) { 144 | log.Errorf("Kill php-cgi(%s) error , because %s", p.execWithPippedName, err.Error()) 145 | } 146 | } else { 147 | if log.IsLevelEnabled(log.DebugLevel) { 148 | log.Debugf("Kill php-cgi(%s) successfully.", p.execWithPippedName) 149 | } 150 | } 151 | 152 | return 153 | } 154 | 155 | // ExecWithPippedName ... 156 | func (p *Process) ExecWithPippedName() string { 157 | return p.execWithPippedName 158 | } 159 | -------------------------------------------------------------------------------- /server/sever.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net" 5 | 6 | log "github.com/sirupsen/logrus" 7 | "golang.org/x/net/netutil" 8 | ) 9 | 10 | // Server 定義 Server 的一些參數 11 | type Server struct { 12 | // 自定義 Tag 13 | Tag interface{} 14 | // MaxConnections 定義最大連接數量,必須大於 0 , 否則無上限 15 | MaxConnections int 16 | // BindAddress 定義要 listen 的 Address 及 Port , 如 127.0.0.1:8000 17 | BindAddress string 18 | listener net.Listener 19 | 20 | shutdownChan chan bool // 此值如果為 true , 代表 Server 必須停止,所有工作都需要關閉 21 | shutdown bool 22 | } 23 | 24 | // Conn 是當 Accept 後產生的連線物件 25 | type Conn struct { 26 | net.Conn // 繼承原本的 net.Conn 27 | ctx interface{} 28 | server *Server // Server 29 | } 30 | 31 | // SetContext 設定任意型態的關聯資源 , 可藉由 Context() 取得 32 | func (c *Conn) SetContext(ctx interface{}) { 33 | c.ctx = ctx 34 | } 35 | 36 | // Context 取得關聯資源 , 可藉由 SetContext() 設定 37 | func (c *Conn) Context() interface{} { 38 | return c.ctx 39 | } 40 | 41 | // Server ... 42 | func (c *Conn) Server() *Server { return c.server } 43 | 44 | // Serve ... 45 | func (s *Server) Serve(event Event) error { 46 | var err error 47 | s.shutdown = false 48 | s.listener, err = net.Listen("tcp", s.BindAddress) 49 | 50 | if err != nil { 51 | return err 52 | } 53 | log.Debugf("Server %s starting listener", s.BindAddress) 54 | 55 | s.listener = netutil.LimitListener(s.listener, s.MaxConnections) 56 | var nextAction Action 57 | nextAction = None 58 | 59 | if event.OnStartup != nil { 60 | // 如果有實作 Startup 事件,就呼叫 61 | nextAction = event.OnStartup(s) 62 | } 63 | 64 | if &nextAction == nil || nextAction == None { 65 | return s.loopAccept(event) 66 | } 67 | 68 | return nil 69 | } 70 | 71 | // loopAccept 開始接受外部連線 72 | func (s *Server) loopAccept(event Event) error { 73 | 74 | log.Infof("Server %s starting accept", s.BindAddress) 75 | s.shutdownChan = make(chan bool, 1) 76 | 77 | for { 78 | 79 | select { 80 | default: 81 | case <-s.shutdownChan: 82 | s.triggerOnShutdown(event) 83 | return nil 84 | } 85 | 86 | netconn, err := s.listener.Accept() 87 | 88 | if err == nil { 89 | conn := &Conn{netconn, nil, s} 90 | if log.IsLevelEnabled(log.DebugLevel) { 91 | log.Debugf("Accept %s to %s", conn.RemoteAddr().String(), conn.LocalAddr().String()) 92 | } 93 | go func(c *Conn) { 94 | 95 | nextAction := s.triggerOnConnect(event, c) 96 | switch nextAction { 97 | case Close: 98 | s.triggerOnDisconnect(event, c) 99 | case Shutdown: 100 | s.Shutdown() 101 | 102 | } 103 | 104 | }(conn) 105 | 106 | } else { 107 | if netconn != nil { 108 | netconn.Close() 109 | } 110 | 111 | if s.shutdown == true { 112 | err = nil 113 | } 114 | 115 | return err 116 | } 117 | 118 | } 119 | 120 | } 121 | 122 | func (s *Server) triggerOnConnect(event Event, c *Conn) Action { 123 | if log.IsLevelEnabled(log.DebugLevel) { 124 | log.Debugf("Client connect %s to %s", c.RemoteAddr().String(), c.LocalAddr().String()) 125 | } 126 | nextAction := Close 127 | if event.OnConnect != nil { 128 | nextAction := event.OnConnect(c) 129 | if &nextAction == nil { 130 | nextAction = Close 131 | } 132 | } 133 | return nextAction 134 | } 135 | 136 | func (s *Server) triggerOnDisconnect(event Event, c *Conn) Action { 137 | if log.IsLevelEnabled(log.DebugLevel) { 138 | log.Debugf("Client disconnect %s to %s", c.RemoteAddr().String(), c.LocalAddr().String()) 139 | } 140 | nextAction := None 141 | c.Close() // 關閉連線 142 | if event.OnDisconnect != nil { 143 | nextAction = event.OnDisconnect(c) 144 | if &nextAction == nil { 145 | nextAction = None 146 | } 147 | } 148 | return nextAction 149 | } 150 | 151 | func (s *Server) triggerOnShutdown(event Event) { 152 | if event.OnShutdown != nil { 153 | event.OnShutdown(s) 154 | } 155 | } 156 | 157 | // Shutdown 停止服務 158 | func (s *Server) Shutdown() { 159 | s.shutdown = true 160 | close(s.shutdownChan) 161 | s.listener.Close() 162 | log.Debugf("Server %s shutdown", s.BindAddress) 163 | } 164 | 165 | // Action 定義 Server 接下來的動作 166 | type Action int 167 | 168 | const ( 169 | // None action , next default event will be trigger 170 | None Action = iota 171 | // Close client connection 172 | Close 173 | // Shutdown server 174 | Shutdown 175 | ) 176 | 177 | // Event 啟動 Server 時必須實作以下事件 178 | type Event struct { 179 | // Startup 代表 Server 啟動事件 180 | OnStartup func(*Server) (action Action) 181 | // OnConnect 當有 Client 連線成功時觸發 , 所有邏輯可以寫在這 182 | OnConnect func(*Conn) (action Action) 183 | // OnDisconnec Client 斷線時觸發 184 | OnDisconnect func(*Conn) (action Action) 185 | // OnShutdown Server 要停止前觸發 186 | OnShutdown func(*Server) 187 | } 188 | --------------------------------------------------------------------------------