分类目录归档: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 配置

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