标签归档:nginx

Mac 开发环境搭建

进入Mac的默认Shell终端,安装Homebrew工具:

Homebrew是一个包管理器,用于在Mac上安装一些OS X没有的UNIX工具(比如著名的wget)。 Homebrew将这些工具统统安装到了/usr/local/Cellar目录并在/usr/local/bin中创建符号链接。

官方网站:
http://brew.sh/index_zh-cn.html

安装:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

#检查安装:
brew -v

Homebrew 常用命令

brew install git

brew uninstall git

brew search git

brew list

brew update

#更新某具体软件
brew upgrade git

#查看软件信息
brew [info | home] [FORMULA...]

#和upgrade一样,单个软件删除 和 所有程序删除。清理所有已安装软件包的历史老版本
brew cleanup git 
brew cleanup

#查看哪些已安装的程序需要更新
brew outdated

Homebrew 卸载

    cd `brew --prefix`
    rm -rf Cellar
    brew prune 
    rm `git ls-files` 
    rm -rf Library .git .gitignore bin/brew
    rm -rf README.md share/man/man1/brew
    rm -rf Library/Homebrew Library/Aliases 
    rm -rf Library/Formula Library/Contributions
    rm -rf ~/Library/Caches/Homebrew

一 安装PHP

#搜索,会出现几个分之,比如PHP56 PHP71
brew search php
#过滤,只要71分支,提供了非常多扩展包
brew search php71
#安装(选择需要的扩展包)
brew install homebrew/php/php71 homebrew/php/php71-apcu homebrew/php/php71-redis homebrew/php/php71-mongodb homebrew/php/php71-opcache omebrew/php/php71-swoole

大部分PHP的模块,都包含在了homebrew/php/php71中,是编译到内核的(非动态模块),上面的apcu,redis,mogondb,swoole是动态模块,模块安装位置:/usr/local/opt/,比如:/usr/local/opt/php71-apcu/apcu.so。配置文件自然是/usr/local/etc/php/7.1/php.ini,扩展的配置放在/usr/local/etc/php/7.1/conf.d/*.ini。

php -v
php -m

编译到内核的模块确实是大而全,然后还需要调整一下php.ini的配置(才能符合开发环境要求):

#设置时区
date.timezone = Asia/Shanghai
 
#CGI相关参数,实际上建议修改的是force_redirect,其它均保留默认值
cgi.force_redirect = 0   #默认为1,改为0
cgi.fix_pathinfo = 1     #默认是1,保留
fastcgi.impersonate = 1  #默认是1,保留
cgi.rfc2616_headers = 0  #默认是0,保留

#其它参数调整,根据实际情况调整
upload_max_filesize = 64M
max_execution_time = 1200
max_input_time = 600
max_input_nesting_level = 128
max_input_vars = 2048
memory_limit = 1024M
 
post_max_size = 64M

如果要启动PHPFPM,FPM主配置文件/usr/local/etc/php/7.1/php-fpm.conf,池配置在/usr/local/etc/php/7.1/php-fpm.d中,需要注意的是,池配置中,默认的运行用户是和用户组均为_www,所以需要检查文件的权限,保证对_www具有读和执行(默认是符合的),如果要写入,那么还需要保证对应的文件夹有被写入的权限。

启动PHPFPM,由于php-fpm这个命令放入到了/usr/local/sbin中,默认shell并不搜索这个路径,所以要想添加环境变量:

#设置环境变量
export PATH="/usr/local/sbin:$PATH"  
echo 'export PATH="/usr/local/sbin:$PATH"' >> ~/.bash_profile

#确认命令能找到
which php-fpm
which php71-fpm

#手动启动,PHPFPM可以不使用root身份启动(user和group指令无用),会使用当前用户运行
sudo php71-fpm start
sudo php71-fpm stop

对于开发环境,PHPFPM可以不用启动,直接使用PHP内置的HTTP服务器也可以。

二 安装Nginx

brew install --with-http2 nginx  

如果要绑定到80端口,那么Nginx就必须以root身份运行。默认的server配置位于(可改):/usr/local/etc/nginx/servers。可以往里面方式配置:

server {
    listen 80;
    #listen 443 ssl http2;
    server_name test.app;
    root "/Users/xx/www/test/public";
 
    index index.html index.htm index.php;
 
    charset utf-8;
 
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
 
    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }
 
    access_log off;
 
    sendfile off;
 
    location ~ \.php$ {
        client_max_body_size 64M;
        fastcgi_intercept_errors off;
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;
        fastcgi_buffer_size 32k;
        fastcgi_buffers 64 32k;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
 
    location ~ /\.ht {
        deny all;
    }
 
    #ssl_certificate     /etc/nginx/ssl/test.app.crt;
    #ssl_certificate_key /etc/nginx/ssl/test.app.key;
}

Nginx的主配置文件设置的启动user一般应该和PHPFPM相同,或者需要保证Nginx对文件具备读和执行的权限。如果是文件上传,还需要确保Nginx对临时中间文件夹具备写入权限。

启动关闭等:

sudo nginx -t
sudo nginx -s start
sudo nginx -s stop

三 安装MySQL

#安装最新版本(5.7.xx)
brew install mysql

#确定搜索路径:
which mysqld
mysqld —verbose —help | grep -A 1 ‘Default options’

/etc/my.cnf  /etc/mysql/my.cnf  /usr/local/etc/my.cnf  ~/.my.cnf

#
mysql.server start
mysql_secure_installation

# 停止
mysql.server stop

MySQL不需要以root身份启动。

四、安装Tomcat

brew search tomcat
==> Searching local taps...
tomcat ✔            tomcat-native       tomcat@6            tomcat@7            tomcat@8
==> Searching taps on GitHub...
==> Searching blacklisted, migrated and deleted formulae...

# 安装最新版本
brew install tomcat

#安装指定版本
brew install tomcat@8

启动关闭:

catalina --help
Using CATALINA_BASE:   /usr/local/Cellar/tomcat/9.0.6/libexec
Using CATALINA_HOME:   /usr/local/Cellar/tomcat/9.0.6/libexec
Using CATALINA_TMPDIR: /usr/local/Cellar/tomcat/9.0.6/libexec/temp
Using JRE_HOME:        /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home
Using CLASSPATH:       /usr/local/Cellar/tomcat/9.0.6/libexec/bin/bootstrap.jar:/usr/local/Cellar/tomcat/9.0.6/libexec/bin/tomcat-juli.jar
Usage: catalina.sh ( commands ... )
commands:
  debug             Start Catalina in a debugger
  debug -security   Debug Catalina with a security manager
  jpda start        Start Catalina under JPDA debugger
  run               Start Catalina in the current window
  run -security     Start in the current window with security manager
  start             Start Catalina in a separate window
  start -security   Start in a separate window with security manager
  stop              Stop Catalina, waiting up to 5 seconds for the process to end
  stop n            Stop Catalina, waiting up to n seconds for the process to end
  stop -force       Stop Catalina, wait up to 5 seconds and then use kill -KILL if still running
  stop n -force     Stop Catalina, wait up to n seconds and then use kill -KILL if still running
  configtest        Run a basic syntax check on server.xml - check exit code for result
  version           What version of tomcat are you running?
Note: Waiting for the process to end and use of the -force option require that $CATALINA_PID is defined

关于启动问题:
在Mac下,如果要开机启动,可以参考如下配置(一般不需要):

#Nginx
cp /usr/local/opt/nginx/homebrew.mxcl.nginx.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist  

# PHP-FPM
cp /usr/local/opt/php70/homebrew.mxcl.php71.plist ~/Library/LaunchAgents/  
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.php71.plist  

# MySQL
cp /usr/local/opt/mysql/homebrew.mxcl.mysql.plist ~/Library/LaunchAgents/  
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist

## 卸载
launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist  
launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.php71.plist  
launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist  
rm ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist  
rm ~/Library/LaunchAgents/homebrew.mxcl.php71.plist  
rm ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist

更加实际的方法:进入操作系统后,启动Nginx和PHPFPM(因为要sudo,需要输入密码),MySQL则在需要时启动,比如本地一般链接远程数据库。所以可以这个别名:(往.bash_profile中写入)

alias servers.start='sudo nginx && php-fpm --fpm-config /usr/local/etc/php/7.1/php-fpm.conf -D'
alias servers.stop='sudo bash -c "killall -9 php-fpm && nginx -s stop"'                       
alias nginx.logs='tail -f /usr/local/opt/nginx/access.log'
alias nginx.errors='tail -f /usr/local/opt/nginx/error.log'

遇到问题:
1 Nginx启动提示

nginx: [emerg] getgrnam("

提示大体就是找不到用户组的意思。在Nginx配置中,user如果只指定了用户名,默认会去寻找同名的用户组,在Mac中,用户不一定对应一个同名的用户组,所以出现这种情况就是需要明确指定存在的用户组,可以通过如下方式来确定用户和用户组:

#当前登录的用户名
whoami
www

#确认用户组(可见www的uid是502,对应的组id是20,名称是staff)
id
uid=502(www) gid=20(staff) groups=20(staff),12(everyone)

把www和staff对应填入,错误提示消失。

Nginx配置应用实例汇总

防盗链
Nginx中防盗链是依靠valid_referers指令来完成的,一般是放在server或location段中:

location ~ .*\.(wma|wmv|asf|mp3|mmf|zip|rar|jpg|gif|png|swf|flv)$ {
     valid_referers none blocked *.ifeeline.com;
     if ($invalid_referer) {
      		return 403;
     }
}

其中的none表示空的来路,也就是直接访问,blocked表示被防火墙标记过的来路。

在正常访问中,HTTP的请求头都会返回一个referfer的参数,它标记当前的请求的来源,如果为空表示直接访问。

如果当前请求是http://blog.ifeeline.com/refer.php,里面嵌入了很多图片,图片会发起新的请求,那么这些图片的请求中就有一个referer的参数,它的值就是前面这个URL。另外,如果从一个连接点击跳转到另一个连接,那么后一个连接的referer的值就是前一个的连接。所以,当别人的网页盗用你的图片地址时,referer记录的就是盗用你图片的域。

注意:referer的值是否发送,完全是有浏览器决定的,目前大部分浏览器都主动发送这个referer,但是也有浏览器发送的referer不准确。

对于一般访问,这种防盗链的方法还是有效的,但是想对付程序采集就有点难度了。因为referer可以被伪造:

function GrabImage($url, $filename = "",$referer = ""){
	if($url == ""){return false;}
	$extt = strrchr($url, ".");
	$ext = strtolower($extt);
	if($ext != ".gif" && $ext != ".jpg" && $ext != ".png" && $ext != ".bmp"){echo $url."-格式不支持!";return false;}
	
	if($filename == ""){ $filename = time()."$extt"; }//以时间戳另起名
	
	ob_start();
	
	//-----------
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	if($referer != ""){
		curl_setopt($ch, CURLOPT_REFERER, $referer);
	}
	curl_exec($ch);
	curl_close($ch);
	//----------
	
	$img = ob_get_contents();
	ob_end_clean();
	
	$size = strlen($img);
	$fp = fopen($filename , "a");
	fwrite($fp, $img);
	fclose($fp);
	
	return $filename;
}

可见,可以通过PHP的curl模块发送连接(伪造一个合法的referer),这样反盗链的设置就被跳过了。

Magento2.x Nginx + SSL 配置

Window 7 下部署Nginx PHP7环境

在Window下做开发,一般会选择使用集成安装包来安装环境,比如wamp, xamp。 由于仅仅是一个开发环境,又希望和生产环境保持一致(Nginx+PHP),在Windows下可以手动配置,实际上不会比一键安装包来得的更复杂。

#下载Nginx  http://nginx.org/
#下载PHP http://windows.php.net/download

如果下载的是PHP 7.x,需要安装VC14,下载地址:http://www.microsoft.com/zh-CN/download/details.aspx?id=48145。注意:这里提供的两个包,分别针对32为和64位的机器。

把下载的Nginx和PHP压缩释放到一个目录(E:/wnmp):

E:\wnmp\nginx
E:\wnmp\php

Nginx配置:

# 1
E:\wnmp\nginx\conf\nginx.conf

#user  nobody;
worker_processes  2;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    ##配置一个默认虚拟主机
    server {
        listen       80;
        server_name  localhost;

	root E:/var/www/default;
    	index index.html index.htm index.php;

        location ~ \.php$ {
            root           E:/var/www/default;
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include        fastcgi_params;
        }
    }

    ##把其它的虚拟主机放入conf/vhosts中
    include vhosts/*.conf;
}


###############################################
#vhosts/*.conf模子
server {
    listen 80;

    server_name php.dev;

    root D:\wamp\www\php.dev\public;
    index index.html index.htm index.php;

    if (!-e $request_filename) {
        rewrite ^/(.+)$ /index.php last;
        break;
    }

    location / {
	root D:\wamp\www\php.dev\public;
	try_files $uri $uri/ /index.php?$query_string;
    }

    location ~* ^.+\.(js|css|jpeg|jpg|gif|png|ico|eot|ttf|woff|svg)$ {
        expires 5d;
    }

    location ~ \.php$ {
	client_max_body_size 	100M;
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 	300;
        fastcgi_read_timeout 	300;
        fastcgi_buffer_size 	32k;
        fastcgi_buffers 	256 32k;

        fastcgi_pass   		127.0.0.1:9000;
        fastcgi_index  		index.php;

        fastcgi_param  		HTTPS $https if_not_empty;

        fastcgi_param  		SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        		fastcgi_params;
   }
}
###############################################


# 2 测试配置文件正确
E:\wnmp\nginx\nginx.exe -t

# 3 启动
E:\wnmp\nginx\nginx.exe

Nginx启动关闭脚本:

# 启动
nginx_start.bat

@echo off
set NGINX_HOME=E:\wnmp\nginx
start /D %NGINX_HOME%\ %NGINX_HOME%\nginx.exe
pause

# 关闭
nginx_stop.bat

@echo off
set NGINX_HOME=E:\wnmp\nginx
cd %NGINX_HOME%
nginx.exe -s quit
pause

PHP配置:

# 对php.ini文件做一些修改
# 拷贝E:\wnmp\php\php.ini-development为php.ini
# 修改或确认如下参数:

#设置时区
date.timezone = Asia/Shanghai

#允许用户在运行时加载PHP扩展
enable_dl = On           #默认为Off 

#CGI相关参数,实际上建议修改的是force_redirect,其它均保留默认值
cgi.force_redirect = 0   #默认为1,改为0
cgi.fix_pathinfo = 1     #默认是1,保留
fastcgi.impersonate = 1  #默认是1,保留
cgi.rfc2616_headers = 0  #默认是0,保留

#扩展目录
extension_dir = "ext"

#加载扩展 根据需要调整
extension=php_bz2.dll
extension=php_curl.dll
extension=php_fileinfo.dll
extension=php_gd2.dll
extension=php_gettext.dll
extension=php_intl.dll
extension=php_mbstring.dll
extension=php_mysqli.dll
extension=php_openssl.dll
extension=php_pdo_mysql.dll

#扩展opcache是否加载,开发环境可以忽略
[opcache]
zend_extension=php_opcache.dll
opcache.enable=1
opcache.enable_cli=1

#其它参数调整,根据实际情况调整
upload_max_filesize = 64M
max_execution_time = 1200
max_input_time = 600
max_input_nesting_level = 128
max_input_vars = 2048
memory_limit = 1024M

post_max_size = 64M

PHP启动关闭脚本:

#启动x个php-cgi进行(注意,这里没有PHP-FPM管理器,无法动态管理CGI进程,开发环境就无所谓了)
php_start.vbs

createobject("wscript.shell").run "E:\wnmp\php\php-cgi.exe -b 127.0.0.1:9000",0,false
createobject("wscript.shell").run "E:\wnmp\php\php-cgi.exe -b 127.0.0.1:9000",0,false
createobject("wscript.shell").run "E:\wnmp\php\php-cgi.exe -b 127.0.0.1:9000",0,false

#关闭php-cgi进行
php_stop.bat

@echo off
taskkill /fi "imagename eq php-cgi.exe"
pause

启动时,点击nginx_start.bat和php_start.vbs,关闭时点击nginx_stop.bat和php_stop.bat。如果要切换到不同版本的PHP,很简单,直接替换就可以,或者建立多个启动脚本,比如:

start_php5.5.vbs
start_php5.6.vbs
start_php7.0.vbs
start_php7.1.vbs

关闭脚本就不需要建立多个了。

然后修改一下path环境变量:
winpath

phpenv

windowphp

在Linux下,PHP的fpm模块是可用的,但是在Windows下不支持,看起来也没有支持的计划。目前在Windows下也看到有类似实现的,不过一般是监控php-cgi进程数量,死掉重启,如此而已,一般认为没有什么价值。开发环境,直接运行一个脚本,启动几个php-cgi进程即可。

在widows下,可以配置为开启运行这些脚本,避免每次都要点击:
cmd-gpedit-msc

gpedit-window

这样设置就可以开机启动了。

另外,在开发环境下,如果觉得引入Nginx太麻烦,还有更加简便的方法,PHP 5.4以后,引入了一个内置的CLI Http Server,启动方式:

D:\wnmp\php-7.0.11\php.exe -S 127.0.0.1:6060 -t D:\var\www\xx\public

可以监听同一个端口,根据不同的名称来区分不同的项目。也使用IP地址,使用不同端口来区分不同的项目。(相同端口和相同主机名,可以启动多次的)
[sehll]
#基于端口,IP一样,端口不一样
D:\wnmp\php-7.0.11\php.exe -S 127.0.0.1:6060 -t D:\var\www\xx\public
D:\wnmp\php-7.0.11\php.exe -S 127.0.0.1:6061 -t D:\var\www\yy\public

#基于主机名(先做hosts绑定),端口一样,主机名不一样
D:\wnmp\php-7.0.11\php.exe -S vfeelit.local:6060 -t D:\var\www\xx\public
D:\wnmp\php-7.0.11\php.exe -S ifeeline.local:6060 -t D:\var\www\yy\public
[/shell]

为了方便,可以对不同项目编写独立的VBS文件,需要哪个点哪个,这也是一个不错的办法:

createobject("wscript.shell").run "D:\wnmp\php-7.0.11\php.exe -S ifeeline.local:6060 -t D:\var\www\yy\public",0,false

CGI – FastCGI – FPM PHP-FPM

CGI – Common Gateway Interface的简写,一般叫做通用网关接口。CGI程序就是指可以使用某某解释器解释的脚本程序,比如Perl、PHP、Shell脚本等。工作流程大体是这样:HTTP服务器根据请求调用相应的CGI脚本,这个时候会从HTTP服务器fork一个进程,在这个进程内用脚本声明的解释器执行脚本,HTTP服务器接收标准输出,然后CGI进程退出。当有新的请求过来,继续这样的流程。在Apache Httpd服务器中,提供了CGI模块,为了加深认识,这里配置一下:

yum install httpd

vi /etc/httpd/conf/httpd.conf
#修改下端口,避免冲突
Listen *:8080
#明确指定服务器名称
ServerName localhost:8080

#修改cgi-bin目录下的文件都是cgi脚本,都可以执行
<Directory "/var/www/cgi-bin">
    AllowOverride All
    Options +ExecCGI
    #AddHandler cgi-script .cgi
    SetHandler cgi-script
    Require all granted
</Directory>

#由于是演示,少开几个进程
<IfModule mpm_prefork_module>
    StartServers 2
    MinSpareServers 2
    MaxSpareServers 6
    ServerLimit 200
    MaxClients 150
    MaxRequestsPerChild 30
</IfModule>

#启动服务器器
systemctl start httpd.service
# - or -
/usr/sbin/httpd

然后分别建立三种类型的脚本(shell, perl, php)

cd /var/www/cgi-bin

## 1 Shell 
vi cgi.sh
 
#!/bin/sh
set -f
echo "Content-type: text/plain; charset=utf-8"
echo
echo CGI Shell Test----------------------------:
echo
echo argc is $#. argv is "$*".
echo
echo SERVER_SOFTWARE = $SERVER_SOFTWARE
echo SERVER_NAME = $SERVER_NAME
echo GATEWAY_INTERFACE = $GATEWAY_INTERFACE
echo SERVER_PROTOCOL = $SERVER_PROTOCOL
echo SERVER_PORT = $SERVER_PORT
echo REQUEST_METHOD = $REQUEST_METHOD
echo HTTP_ACCEPT = "$HTTP_ACCEPT"
echo PATH_INFO = "$PATH_INFO"
echo PATH_TRANSLATED = "$PATH_TRANSLATED"
echo SCRIPT_NAME = "$SCRIPT_NAME"
echo QUERY_STRING = "$QUERY_STRING"
echo REMOTE_HOST = $REMOTE_HOST
echo REMOTE_ADDR = $REMOTE_ADDR
echo REMOTE_USER = $REMOTE_USER
echo AUTH_TYPE = $AUTH_TYPE
echo CONTENT_TYPE = $CONTENT_TYPE
echo CONTENT_LENGTH = $CONTENT_LENGTH

## 2 Perl
vi cgi.perl

#!/usr/bin/perl

print "Content-type: text/plain; charset=utf-8\n\n";

foreach $var (sort(keys(%ENV))) {
    $val = $ENV{$var};
    $val =~ s|\n|\\n|g;
    $val =~ s|"|\\"|g;
    print "${var}=\"${val}\"\n";
}

## 3 PHP
#!/usr/bin/php
<?php
echo "Content-type: text/plain; charset=utf-8\n\n";

print_r($_SERVER);

确保这些程序是可以执行的(chmod 777 *), 然后通过浏览器访问:
1 Shell的输出
cgi-sh

2 Perl的输出
cgi-perl

3 PHP的输出
cig-php

看到以上的输出,应该对CGI会有一个比较直观的了解。 然后把cgi.php修改一下:

#!/usr/bin/php
<?php
echo "Content-type: text/plain; charset=utf-8\n\n";

$i = 20;
while($i > 0) {
	echo $i." sleep...<br />";
	sleep(2);
	$i--;
}

在进程没有退出前,查看进程:
cgi-ps
很明显,从httpd派生了一个进程,然后把脚本传递给php解释器。

HTTP服务器会把环境变量(比如请求参数等)传递给CGI解释器,比如PHP解释器的$_SERVER输出就能取到传递过来的参数。而CGI程序需要输出一个MIME类型的头(”Content-type: text/plain; charset=utf-8\n\n”),之后跟一个空行(Shell中无法解释转义,需要明确输出空行),这些几乎就是大部分内容了。

FastCGI是为了解决CGI的缺陷而引入的。CGI的缺陷是非常明显的,每一个请求都启动一个进程,用完了后退出。FastCGI要解决的问题是进程启动,转入解释器,解释脚本,完成后不退出,继续等待其它脚本的投递。比如启动10个FastCGI进程,形成一个进程池。PHP中提供了php-cgi(php-cgi.exe),可以非常便利实现这个进程池:

#启动三个php-cgi
D:\wnmp\php-7.0.11\php-cgi.exe -b 127.0.0.1:9000
D:\wnmp\php-7.0.11\php-cgi.exe -b 127.0.0.1:9000
D:\wnmp\php-7.0.11\php-cgi.exe -b 127.0.0.1:9000

这样在Nginx中,可以直接使用:

    location ~ \.php$ {
        client_max_body_size    100M;
        
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout    300;
        fastcgi_read_timeout    300;
        fastcgi_buffer_size     32k;
        fastcgi_buffers     256 32k;
 
        fastcgi_pass        127.0.0.1:9000;
        fastcgi_index       index.php;
 
        fastcgi_param       HTTPS $https if_not_empty;
 
        fastcgi_param       SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include             fastcgi_params;
   }

指令fastcgi_param定义的参数,会传递给FastCGI进程。在Window下为了使用Nginx+PHP组合,就必须这么做了。

现在这个模式解决了CGI资源消耗问题,但是又有了新的问题:一次性启动的进程,如果因为某种原因异常退出了,如何重启未解决;多个进程监听同一个端口,那么Nginx投递请求过来时,必定引起进程争抢请求现象(这个是资源浪费);如果请求量加大,无法动态加大进程池的数量(PHP是单进程堵塞模式,每次只能处理一个请求)。

看起来,这个进程池里面的进程,需要一个主管。它负责把请求交给谁处理,监控进程池的进程是否异常退出然后重启,根据请求情况动态增加进程池进程数量等等。这个就是进程管理器的概念。一般称为Fast CGI Process Manager,简称就是FPM,而PHP实现的FPM就叫PHP-FPM。PHP中的PHP-FPM主进程目前应该使用的是Reactor模型,它负责hold住请求,socket监听绑定(没有FPM时是各个进程同时监听和绑定,被唤醒时存在争抢),FPM可以均衡控制子进程处理的请求数量,在到达一定数量后,可以重启该进程(防止内存泄露问题),监控进程异常退出,动态加大进程池等。总之,PHP-FPM是一个非常不错的实现(Window下没有这个FPM)。

接下来具体看看PHP-FPM模型:

安装了php-fpm模块后,会对应一个服务启动脚本:

cat /usr/lib/systemd/system/php-fpm.service
[Unit]
Description=The PHP FastCGI Process Manager
After=syslog.target network.target

[Service]
Type=notify
PIDFile=/var/run/php-fpm/php-fpm.pid
EnvironmentFile=/etc/sysconfig/php-fpm
ExecStart=/usr/sbin/php-fpm --nodaemonize --fpm-config /etc/php-fpm.conf
ExecReload=/bin/kill -USR2 $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target

可以看到,/usr/sbin/php-fpm就是进程管理器,它接收配置文件,根据配置启动进程池。进程池里面的进程不是调用php-cgi而来的,而是从php-fpm fork出来的,php-fpm进程是常驻内存的,解释器(也可以认为是zend引擎)在每个进程中都独立存在一份,它的所谓opcode缓存也是在进程没有退出前有效的,由于PHP的特点,脚本执行完毕,脚本相关的一切变量都会被销毁(可以看做是在一个沙箱中执行代码,完了后把整个沙箱干掉),基于这样的事实,什么垃圾回收之类的都是浮云,垃圾回收也只能在脚本运行中回收。另外,当脚本中大量include文件进来时,这些文件编译后可以被缓存起来(opcode),但是缓存也是有大小限制的,当新的请求进来时,如果include了相同的文件,那么就可以从缓存中取出来执行,如果没有就从文件系统读入文件,编译成字节码,放入缓存,执行字节码。

注:这里无法确定的是当有缓存时,是否需要搬动。

nginx-php-fpm

root       25711     1  nginx: master process /usr/sbin/nginx
www      25712 25711  nginx: worker process
www      25713 25711  nginx: worker process

root       25726     1  php-fpm: master process (/etc/php-fpm.conf)
www      25727 25726  php-fpm: pool www
www      25728 25726  php-fpm: pool www

/etc/php-fpm.conf

#################################
#全局配置
#################################
[global]
pid = /var/run/php-fpm/php-fpm.pid
error_log = /var/log/php-fpm/error.log
;syslog.facility = daemon
;syslog.ident = php-fpm
;log_level = notice
;emergency_restart_threshold = 0
;emergency_restart_interval = 0
;process_control_timeout = 0
;process.max = 128
;process.priority = -19
daemonize = yes
;rlimit_files = 1024
;rlimit_core = 0
;events.mechanism = epoll
;systemd_interval = 10
include=/etc/php-fpm.d/*.conf

/etc/php-fpm.d/www.conf

#################################
#针对池的配置
#################################
[www]
listen = 127.0.0.1:9000
;listen.backlog = -1
listen.allowed_clients = 127.0.0.1

;listen.owner = nobody
;listen.group = nobody
;listen.mode = 0666

user = www
group = www

pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
;pm.max_requests = 500

;pm.status_path = /status
;ping.path = /ping
;ping.response = pong
;request_terminate_timeout = 0
;request_slowlog_timeout = 0
slowlog = /var/log/php-fpm/www-slow.log
 
rlimit_files = 10240
;rlimit_core = 0
;chroot = 
;chdir = /var/www
;catch_workers_output = yes
;security.limit_extensions = .php .php3 .php4 .php5

;env[HOSTNAME] = $HOSTNAME
;env[PATH] = /usr/local/bin:/usr/bin:/bin
;env[TMP] = /tmp
;env[TMPDIR] = /tmp
;env[TEMP] = /tmp

;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
;php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 10240M

; Set session path to a directory owned by process user
php_value[session.save_handler] = files
php_value[session.save_path]    = /var/lib/php/session
php_value[soap.wsdl_cache_dir]  = /var/lib/php/wsdlcache

当配置了多个池时,如果日志都写入/var/log/php-fpm/,那么需要确保不同池的用户都可以往这个文件夹写入内容。同样的/var/lib/php也是如此。

错误日志:
如果应用程序由捕捉异常,相关的日志信息由程序的自定义程序处理。如果没有就会上交到PHP,如果PHP中配置了记录日志的位置,那么日志就会被记录到这个文件中,如果没有就会上交给HTTP服务器,比如Nginx,在HTTP服务器这个级别,如果对不同域做了配置,那么就记录到这个域下的配置中,如果没有则统一记录到error_log指令指向的文件。

参考:http://blog.ifeeline.com/1901.html

Nginx Location配置 – 被坑记

server {
    listen       80;
    server_name  xxx.com;
    root /mnt/xxx/public;
    index index.php;

    error_page 404 /index.php;
    
    if (!-e $request_filename) {
        rewrite ^/(.+)$ /index.php last;
        break;
    }

    location / {
	root /var/xxx/public;
        #尝试实际文件,然后判断是不是目录,在然后就是到首页(实际是被重新,重新发起location匹配)
	try_files $uri $uri/ /index.php?$query_string; 
    }

    location ~* ^.+\.(css|js|jpeg|jpg|gif|png|ico|eot|ttf|woff|svg) {
        expires 30d;
    }

    location ~* \.(eot|ttf|woff|svg|html)$ {
        add_header Access-Control-Allow-Origin *;
    }

    location ~ \.php$ {
	client_max_body_size 500M;
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;
        fastcgi_buffer_size 32k;
        fastcgi_buffers 256 32k;

        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
        #fastcgi_param APPLICATION_ENV testing;
   }

首先,第一个if语句是判断对应的rui不存在(非目录和文件),那么就重写到index.php,我们知道last重写相当于continue语句,由于这个if在server块中,所以它不会再次发起请求了,故而下面的break是多余的,写上也没有问题。

还是再次复习location指令的用法吧:
location指令
语法:location [=|~|~*|^~] /uri {…} 注意看明白格式****中括号表示可以省略
使用环境:server

该指令运行对不同的URI进行不同的配置,即可以使用字符串,也可以使用正则表达式。使用正则表达式,须使用以下前缀。
1 ~* 表示不区分大小写的匹配
2 ~ 表示区分大小写的匹配
(意思是以这两个字符开头的,就是一定是正则匹配)
在匹配过程中,Nginx将首先匹配字符串,然后再匹配正则表达式。匹配到第一个正则表达式后,会停止搜索(搜索顺序时先出现的优先)。如果匹配到正则表达式,则使用正则表达式匹配,如果没有匹配到正则表达式,则使用字符串的搜索结果。

特殊用法:
可以使用前缀^~来禁止匹配到字符串后,再去检查正则表达式。
使用前缀=可以进行精确的URI匹配,如果找到匹配的URI,则停止查询。

注意,Nginx中的变量$uri由于都是以/开头的,当访问首页是其实访问的就是/,浏览器总是把斜杠添加上然后才发出请求,就算没有加上斜杠发出来了请求,服务器也会进行一个加了斜杠的重定向,如果访问一个目录时,请求到达服务器后如何确认它是一个目录,服务器会进行一个加了斜杠的重定向。

另外,location指令中能使用的前缀只有上面列出的四个,而那些在if中合法的!~ 和 !~*在location中是不合法的,这个稍不注意就会让人困惑。

location的正则匹配是一旦匹配就停止,所以那些允许的匹配应该写在前,不允许的应该写在后,具体的写在最前面。

所有的location匹配放入一个循环,字符串优先匹配(最先的先匹配,后面的后匹配),然后匹配正则,正则一旦匹配,就使用这个正则匹配(触发被重写,会重新发起一次对locaiton的匹配),否则就使用最后的字符匹配,总体上满足先具体,后一般的原则。

回到配置,第一个字符匹配,基本上,如果后面的没有匹配,就都是它了。后面的是三个正则匹配,一旦匹配,就用它。现在假如URI是get.js.php 和 get.css.php,这个时候它匹配了第二个正则而不是第三个正则,所以被坑就在这里了。导致明明要执行PHP脚本的,生硬的把get.js.php源码返回。争取的写法应该是在最后加上$字符。

Nginx php-fpm日志记录 与 配置参考

Nginx的日志配置:

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

可以针对具体的域做具体配置。

PHP的php.ini中针对日志配置:

//错误报告级别
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
//是否显示错误
display_errors = On
//是否记录日志
log_errors = On
//日志记录到哪里
error_log = php_errors.log

在PHP php-fpm.conf关于日志的配置:

;;;;;;;;;;;;;;;;;;
; Global Options ;
;;;;;;;;;;;;;;;;;;
[global]
error_log = /var/log/php-fpm/error.log

; Log level
; Possible Values: alert, error, warning, notice, debug
; Default Value: notice
;log_level = notice

这个是针对php-fpm进行的日志配置,那么,它自然只应该记录和php-fpm相关的日志(cat /var/log/php-fpm/error.log):

[02-Aug-2015 21:40:54] WARNING: [pool www] child 4062, script '/data/web/nginx/timeout.php' (request: "GET /timeout.php") executing too slow (2.565468 sec), logging
[02-Aug-2015 21:40:54] NOTICE: child 4062 stopped for tracing
[02-Aug-2015 21:40:54] NOTICE: about to trace 4062
[02-Aug-2015 21:40:54] NOTICE: finished trace of 4062
[02-Aug-2015 22:15:29] NOTICE: Terminating ...
[02-Aug-2015 22:15:29] NOTICE: exiting, bye-bye!
[02-Aug-2015 22:15:30] NOTICE: fpm is running, pid 15707
[02-Aug-2015 22:15:30] NOTICE: ready to handle connections
[02-Aug-2015 22:45:56] NOTICE: Terminating ...
[02-Aug-2015 22:45:56] NOTICE: exiting, bye-bye!
[02-Aug-2015 22:50:41] NOTICE: fpm is running, pid 1243
[02-Aug-2015 22:50:41] NOTICE: ready to handle connections

就记录了进程启用终止等之类的信息,fpm有问题时,这个日志是很有用的信息。

如果PHP作为Apache的模块运行,当PHP脚本发生错误时,如果在PHP的php.ini中配置了log_error,那么错误就记录到这个指定文件,否则错误会上交到Apache来处理。这个过程一直被认为当PHP以fpm运行时是不正确的。不过我这里拿PHP 5.5以FPM运行时,它的过程跟PHP作为Apache的模块运行时的过程是一致的。这个应该是早期的PHP版本FPM运行时,没有向上报告导致的。比如当我去掉log_error配置项(php.ini和php-fpm.conf中都去掉):

tail -f /usr/local/nginx/logs/error.log
2015/08/02 23:30:11 [error] 1254#0: *12 FastCGI sent in stderr: "PHP message: PHP Parse error:  syntax error, unexpected 'echo' (T_ECHO) in /data/web/nginx/err.php on line 4" while reading response header from upstream, client: 192.168.1.100, server: nginx.ifeeline.com, request: "GET /err.php HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "nginx.ifeeline.com"

这里Nginx成功记录了PHP的语法错误。而当我恢复php.ini中的log_error配置时,错误就记录到了配置指定的文件,Nginx也没有重复记录这个错误。

PHP-FPM中,还可以配置request_slowlog,要找出执行时间超标的脚本,这个设置比较有用,以下是PHP-FPM配置参考:

#################################
#全局配置
#################################

#包含文件,一般,池的配置可以放入单独的文件
#比如www.conf,表示www池
include=/etc/php-fpm.d/*.conf

#进程ID号存放的文件,默认为none
pid = /var/run/php-fpm/php-fpm.pid

#错误日志,可以设置为syslog,让其把日志发送到syslog
#默认值/var/log/php-fpm.log
error_log = /var/log/php-fpm/error.log
;syslog.facility = daemon
;syslog.ident = php-fpm

#日志级别,可用值alert, error, warning, notice, debug
#默认值为notice
log_level = notice

#紧急重启阀值和区间,在循环的区间(emergency_restart_interval)
#如果子进程退出的次数等于emergency_restart_threshold的设置
#FPM优雅重启,默认值都为0
;emergency_restart_threshold = 0
;emergency_restart_interval = 0

#子进程等待一个来自主进程的可重用信号的限制时间
#默认为0,默认单位为s(秒)
;process_control_timeout = 0

#FPM将可fork的最大值。当在大量的池中使用动态PM时,这个值被设置来控制全局的进程数量
;process.max = 128
;process.priority = -19

#后台或前提运行
daemonize = yes

#设置主进程可打开的文件描述符数量. 默认值: 系统定义值默认可打开句柄是1024,可使用 ulimit -n查看,ulimit -n 2048修改。
;rlimit_files = 1024

#设置主进程最大的核心数
;rlimit_core = 0

#指定FPM将使用的事件机制
;events.mechanism = epoll

#当FPM和systemd,指定一个时间区间,用来向systemd报告健康通知
;systemd_interval = 10

#################################
#以下是针对池的配置
#################################

#FPM监听端口,即nginx中php处理的地址。可用格式为: 'ip:port', 'port', '/path/to/unix/socket'. 每个进程池都需要设置。
listen = 127.0.0.1:9000

#backlog数,-1表示无限制,由操作系统决定
;listen.backlog = -1

#允许访问FastCGI进程的IP,设置any为不限制IP,如果要设置其他主机的nginx也能访问这台FPM进程,listen处要设置成本地可被访问的IP。默认值是any。每个地址是用逗号分隔. 如果没有设置或者为空,则允许任何服务器请求连接
listen.allowed_clients = 127.0.0.1

#unix socket设置选项,如果使用tcp方式访问,可忽略。
;listen.owner = www
;listen.group = www
;listen.mode = 0660

#启动进程的帐户和组
user = www
group = www

#如何控制子进程,选项有static和dynamic。
pm = dynamic
#如果选择static,则由pm.max_children指定固定的子进程数。如果选择dynamic,则由下开参数决定:
pm.max_children 	#,子进程最大数
pm.start_servers 	#,启动时的进程数
pm.min_spare_servers 	#,保证空闲进程数最小值,如果空闲进程小于此值,则创建新的子进程
pm.max_spare_servers 	#,保证空闲进程数最大值,如果空闲进程大于此值,此进行清理

#设置每个子进程重生之前服务的请求数(服务这么多的请求自杀重启). 对于可能存在内存泄漏的第三方模块来说是非常有用的
pm.max_requests = 500

#FPM状态页面的网址. 如果没有设置, 则无法访问状态页面。默认值为没有设置。
;pm.status_path = /status

#FPM监控页面的ping网址. 如果没有设置, 则无法访问ping页面. 该页面用于外部检测FPM是否存活并且可以响应请求. 请注意必须以斜线开头 (/)。
ping.path = /ping

#用于定义ping请求的返回相应. 返回为 HTTP 200 的 text/plain 格式文本. 默认值: pong.
;ping.response = pong

#设置单个请求的超时中止时间
request_terminate_timeout = 0

#当一个请求到达设置的超时时间后,就会将对应的PHP调用堆栈信息完整写入到慢日志中. 设置为 '0' 表示 'Off'
request_slowlog_timeout = 10s

#慢请求的记录日志,配合request_slowlog_timeout使用
slowlog = log/$pool.log.slow

;rlimit_files = 1024
;rlimit_core = 0

#启动时的Chroot目录. 所定义的目录需要是绝对路径. 如果没有设置, 则chroot不被使用.
chroot =

#设置启动目录,启动时会自动Chdir到该目录. 所定义的目录需要是绝对路径. 默认值: 当前目录,或者/目录(chroot时)
chdir =

#重定向运行过程中的stdout和stderr到主要的错误日志文件中. 如果没有设置, stdout 和 stderr 将会根据FastCGI的规则被重定向到 /dev/null . 默认值: 空.
catch_workers_output = yes

#限制后缀,默认为php
;security.limit_extensions = .php .php3 .php4 .php5

#设置环境变量
;env[HOSTNAME] = $HOSTNAME
;env[PATH] = /usr/local/bin:/usr/bin:/bin
;env[TMP] = /tmp
;env[TMPDIR] = /tmp
;env[TEMP] = /tmp

#修改PHP配置
;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
;php_flag[display_errors] = off
;php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
;php_admin_value[memory_limit] = 128M

; Set session path to a directory owned by process user
php_value[session.save_handler] = files
php_value[session.save_path] = /var/lib/php/session

TFS的Nginx扩展nginx_tfs 与 PHP包装器

Taobao官方有提供一个针对TFS的Nginx扩展,地址https://github.com/alibaba/nginx-tfs,设计成REST风格API,它就是一个适配器,任何的编程语言执行简单发送指令就可以操作TFS。

官方描述的安装:
1 TFS模块使用了一个开源的JSON库来支持JSON,请先安装yajl-2.0.1(http://lloyd.github.io/yajl/)。
2 下载nginx或tengine。
3 ./configure –add-module=/path/to/nginx-tfs
4 make && make install

实际操作过程:

#先安装Nginx需要的包
yum -y install zlib zlib-devel openssl openssl-devel pcre pcre-devel

#获取yajl
wget http://github.com/lloyd/yajl/zipball/2.1.0
mv 2.1.0 yajl-2.0.1.zip
unzip yajl-2.0.1.zip
cd yajl-2.0.1
##默认安装到/usr/local,库放入了/usr/local/lib中
./configure 
##这个玩意使用了cmake      
yum install cmake 
make && make install
#避免后面找不到这个库,玛尼,这里拷贝一下
cp -a /usr/local/libyajl* /lib64/

#获取nginx-tfs扩展,解压一下就好了
wget https://github.com/alibaba/nginx-tfs/archive/master.zip
unzip master.zip

#Ngingx-1.8.0无法适配这个扩展,所以找一个低一点的版本
wget http://nginx.org/download/nginx-1.2.9.tar.gz
tar zxvf nginx-1.2.9.tar.gz
cd nginx-1.2.9
useradd www
./configure --user=www --group=www --prefix=/usr/local/nginx-1.2.9 --add-module=/root/nginx-tfs-master
make && make install

配置:

vi nginx.conf 添加:
tfs_upstream tfs_ns {
        server 192.168.1.102:8000;
        type ns;
    }

server {
	listen       80;
	server_name  192.168.1.102;

        tfs_keepalive max_cached=10 bucket_count=2;

        location / {
              tfs_pass tfs://tfs_ns;
        }
}

我这里不使用rcServer,所以配置异常简单。具体配置参考:https://github.com/alibaba/nginx-tfs/blob/master/ReadMe.markdown

然后用如下程序POST一张图片:

/////////////////////////////////////////////////////
$url = 'http://192.168.1.102/v1/tfs?suffix=.jpg&simple_name=0';

$postBinary = file_get_contents('D:\v.jpg');

//初始化
$ch = curl_init();
curl_setopt($ch, CURLOPT_HEADER, '');
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
//
curl_setopt($ch, CURLOPT_POST, false);
//
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS,$postBinary);
//
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
//
curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
curl_setopt($ch,CURLOPT_FOLLOWLOCATION,true);
curl_setopt($ch,CURLOPT_MAXREDIRS,10);
//
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0");

$result = curl_exec($ch);
$errn = curl_errno($ch);
curl_close($ch);

print_r($result);

######################################
输出:
{ "TFS_FILE_NAME": "T1IRETByxT1RXrhCrK" } 

说明:这个POST跟一般常见的POST是不一样的(RESTful风格),首先,参数作为URL的一部分,然后就是POST的数据不需要对应一个名称,直接是原生数据。

直接访问这个链接:
tfs_output

其它的API参考:https://github.com/alibaba/nginx-tfs/blob/master/TFS_RESTful_API.markdown,这些API的调用可以通过CURL来非常容易的使用。

针对一个nginx_tfs的这个扩展,在Github有一个针对它的PHP包装器:https://github.com/ifa6/php_tfs_client
这个包里面提供的HttpClient.php是对Curl的封装,HttpResponse.php是响应对象的封装,HttpClient.php提供了RESTfull风格的几个方法:

    public function get($url)
    {
        return $this->prepareCurlHandler(array(
            CURLOPT_URL => $this->prepareUrl($url),
            CURLOPT_CUSTOMREQUEST => 'GET',
        ))->send();
    }

    public function put($url, $content=null)
    {
        return $this->prepareCurlHandler(array(
            CURLOPT_URL => $this->prepareUrl($url),
            CURLOPT_CUSTOMREQUEST => 'PUT',
            CURLOPT_POSTFIELDS => $content,
        ))->send();
    }

    public function post($url, $content=null)
    {
        return $this->prepareCurlHandler(array(
            CURLOPT_URL => $this->prepareUrl($url),
            CURLOPT_CUSTOMREQUEST => 'POST',
            CURLOPT_POSTFIELDS => $content,
            CURLOPT_POST => true
        ))->send();
    }

    public function delete($url)
    {
        return $this->prepareCurlHandler(array(
            CURLOPT_URL => $this->prepareUrl($url),
            CURLOPT_CUSTOMREQUEST => 'DELETE',
        ))->send();
    }

    public function head($url)
    {
        return $this->prepareCurlHandler(array(
            CURLOPT_URL => $this->prepareUrl($url),
            CURLOPT_CUSTOMREQUEST => 'HEAD',
            CURLOPT_NOBODY => true
        ))->send();
    }

除了这些方法,还有一系列的set方法,比如设置请求URL,CURL用的头信息等:

$httpClient = new HttpClient();
$reponse = $httpClient->setBaseUrl('http://blog.ifeeline.com')->get('/xxx/api.php');
if($reponse->isOk()){
    $content = $response->getContent();
    // ......
}

这个包下面Client.php是利用HttpClient对nginx_tfs提供的API进行了二次封装,不过我觉得使用HttpClient已经足够好了。