e攻城狮

  • 首页

  • 随笔

  • 分类

  • 瞎折腾

  • 搜索

通过webhook自动部署git项目

发表于 2022-11-26 分类于 服务器运维

部署脚本 ci.sh:

1
2
3
4
#!/bin/sh
cd /www/project/ #项目目录
sudo unset GIT_DIR
sudo git pull

webhook(以php为例):

1
2
3
<?php
/* check you auth */
system("sudo /xxx/ci.sh 2>&1"); # xxx表示你ci.sh文件所在目录

注意 1 :git公钥归属的问题会导致你的 git pull 命令无法执行失败,需要把当前web服务器的用户权限提高

1
2
3
4
su - # 进入超级用户模式
chmod u+w /etc/sudoers #添加文件的写权限
vim /etc/sudoers # 编辑文件, 找到这一 行:"root ALL=(ALL) ALL"在起下面添加"xxx ALL=(ALL) NOPASSWD:/usr/bin/git"(这里的xxx是你的web服务器的用户,如nginx默认的www),然后保存退出。
chmod u-w /etc/sudoers

注意 2 : 出现.git/FETCH_HEAD没有权限 需要提升.git目录权限

chmod -R 777  .git

MySQL MERGE存储引擎

发表于 2022-11-03 分类于 数据库

MERGE存储引擎把一组MyISAM数据表当做一个逻辑单元来对待,让我们可以同时对他们进行查询,是一种简单的分表方案。

例如将日志表按日期分为 log_20221103, log_20221104 两张表

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE `log_20221103` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`log` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

CREATE TABLE `log_20221104` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`log` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

分别插入数据:

log_20221103:

id log
1 1
2 2
3 3

log_20221104:

id log
1 a
2 b
3 c

创建Merge表:

1
2
3
4
5
6
7
8
9
10
11
12
CREATE TABLE `log_merge` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`log` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 INSERT_METHOD=LAST UNION=(`log_20221103`,`log_20221104`);
/**
解释:
1)ENGINE=MERGE 指明使用MERGE引擎。
2)UNION=(`log_20221103`,`log_20221104`) 指明了MERGE表中挂接了些哪表,可以通过alter table (如移除基础表log_20221104: ALTER TABLE `log_merge` UNION = (`log_20221103`);)的方式修改UNION的值,以实现增删MERGE表子表的功能。
3)INSERT_METHOD=LAST 指明往merge表插入方式,取值可以是:0 不允许插入;FIRST 插入到UNION中的第一个表; LAST 插入到UNION中的最后一个表。
4)MERGE表及构成MERGE数据表结构的各成员数据表必须具有完全一样的结构。每一个成员数据表的数据列必须按照同样的顺序定义同样的名字和类型,索引也必须按照同样的顺序和同样的方式定义。
**/

执行select * from log_merge 将会得到如下结果:

id log
1 1
2 2
3 3
1 a
2 b
3 c

从效果上看,两张表记录如同union在一起了一样, 但是需要注意的是:

1. 此表结构必须与基本表完全一致,包括列名、顺序。UNION表必须同属一个DATABASE。
2. 此表类似于SQL中的union机制。
3. 基本表类型必须是MyISAM的。
4. 可以通过修改.mrg文件来修改MERGE表,每个基本表的名字占一行。注意:修改后要通过FLUSH TABLES刷新表缓存。
5. 对基本表的更改可以直接反映在此表上。
6. INSERT_METHOD的取值可以是: 0 不允许插入 FIRST 插入到UNION中的第一个表 LAST 插入到UNION中的最后一个表。(4.0之后可用)
7. 定义在它上面的约束没有任何作用,约束是由基本表控制的,例如两个基本表中存在着同样的一个Key值,那么在MERGE表中会有两个一样的Key值。
8. 在数据量、查询量较大的情况下, 使用Merge表会很影响性能,原因参考2。
9. 查询结果及顺序与创建Merge表时联合表的顺序有关。
10. 当key一样的时候,修改Merge表内容会影响key对应的第一条数据,和9有关。
11. TRUNCATE Merge表的时候会同时truncate基础表,请慎重操作。 但是DELETE merge表的时候对基础表是没有影响的。

Navicat Premium 16无限试用方法

发表于 2022-08-23

1.前往官网下载【Navicat Premium 16】

http://www.navicat.com.cn/products

2.创建清理试用信息bat

Navicat Premium 16Crack.bat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@echo off

echo Delete HKEY_CURRENT_USER\Software\PremiumSoft\NavicatPremium\Registration[version and language]
for /f %%i in ('"REG QUERY "HKEY_CURRENT_USER\Software\PremiumSoft\NavicatPremium" /s | findstr /L Registration"') do (
reg delete %%i /va /f
)
echo.

echo Delete Info folder under HKEY_CURRENT_USER\Software\Classes\CLSID
for /f %%i in ('"REG QUERY "HKEY_CURRENT_USER\Software\Classes\CLSID" /s | findstr /E Info"') do (
reg delete %%i /va /f
)
echo.

echo Finish

pause

在每次试用到期时,执行清理,或者添加至定时程序中,每日自动清理相当于无限试用

3.说明

本项目自始至终都是免费使用,如果有你发现有人盗取牟利,请拒绝并不遗余力地在一切平台举报投诉他!


本项目只做个人学习研究之用,不得用于商业用途!
若资金允许,请点击链接购买正版,谢谢合作!

APP代理检测对抗 转

发表于 2022-06-02 分类于 安全研究

1.前言

本文将从网络通信原理浅析在android中出现的一些代理转发检测,这些功能会使我们测试app时出现抓不到包或者应用闪退等情况,针对这种场景,我搭建了测试环境,并对其场景展开分析与实施应对方案。

2.OSI 7层网络模型

网络通信嘛,首先得知道什么是OSI 7层模型。下面是百度的解释:

为了使不同计算机厂家生产的计算机能够相互通信,以便在更大的范围内建立计算机网络,国际标准化组织(ISO)在1978年提出了“开放系统互联参考模型”,即著名的OSI/RM模型(Open System Interconnection/Reference Model)。

它将计算机网络体系结构的通信协议划分为七层,自下而上依次为:

物理层(Physics Layer)、数据链路层(Data Link Layer)、网络层(Network Layer)、传输层(Transport Layer)、会话层(Session Layer)、表示层(Presentation Layer)、应用层(Application Layer)

使用网络数据的传输离不开网络协议七层模型,通过理解每一层协议的分工,也就能对网络故障逐一排查,这样的思维逻辑在安卓应用中也同样适用。

OSI 7层模型各层功能及对应的协议、设备如下表所示:

OSI对应的层 功能 TCP/IP对应的协议 设备
应用层 文件传输,电子邮件,文件服务,虚拟终端 TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet /
表示层 数据格式化,代码转换,数据加密 / /
会话层 解除或建立与别的接点的联系 / /
传输层 提供端对端的接口 TCP,UDP 四层交换机和四层路由
网络层 为数据包选择路由 IP,ICMP,RIP,OSPF,BGP,IGMP 三层交换机和路由
数据链路层 传输有地址的帧以及错误检测功能 ARP,RARP,MTU,SLIP,CSLIP,PPP 网桥、交换机、网卡
物理层 以二进制数据形式在物理媒体上传输数据 ISO2110,IEEE802,IEEE802.2 中继器、集线器、双绞线

知识点:HTTPS协议是HTTP+SSL

根据上表可知,SSL做数据加密是在表示层,也就是说,HTTPS实际上是建立在SSL之上的HTTP协议,而普通的HTTP协议是建立在TCP协议之上的。所以,当HTTPS访问URL时,由于URL在网络传送过程中最后是处于HTTP协议数据报头中,而HTTP协议位于SSL的上层,所以凡是HTTP协议所负责传输的数据就全部被加密了;但是IP地址并没加密,因为处理IP地址的协议(网络层)位于处理SSL协议(表示层)的下方。

额,说了这么多,就是要告诉你一个重要的关键点:数据的封装是自下而上的 !在网络数据处理方面,如果是上层做了检测处理,则需要在同层或下层进行逻辑绕过,这就是攻与防的关键了,偷家(底层)才是硬道理。

接下来,我们再理解一下代理与VPN。

3.代理与VPN

3.1、代理

代理(proxy) 也称网络代理,是一种特殊的网络服务,允许一个终端(一般为客户端)通过这个服务与另外一个终端(一般为服务器)进行非直接的连接。

一个完整的代理请求过程为:客户端首先根据代理服务器所使用的代理协议,与代理服务器创建连接,接着按照协议请求对目标服务器创建连接、或者获得目标服务器的指定资源。

3.2、VPN

VPN(virtual private network)(虚拟专用网络 )是常用于连接中、大型企业或团体间私人网络的通讯方法。它利用隧道协议(Tunneling Protocol)来达到发送端认证、消息保密与准确性等功能。

3.3、代理和VPN的区别

从各自的定义,我们就能看出VPN的特点是采取隧道协议进行数据传输和保护;而代理使用的则是对应的代理协议。

下面是VPN和代理的常用协议:

协议名称
VPN OpvenVPN、IPsec、IKEv2、PPTP、L2TP、WireGuard等
代理 HTTP、HTTPS、SOCKS、FTP、RTSP等

VPN 协议大多是作用在 OSI 的第二层和第三层之间,所以使用 VPN 时,几乎能转发所有的流量。

而代理协议多作用在应用层,最高层。

4.安卓代理检测

知道了代理与VPN的作用后,在APP中,如果开发人员在代码中添加了一些网络层的检测机制,而这些机制恰恰又是针对工作层协议进行的检测,那么只要分析出工作在IOS的哪一层,抢先一步在下层做出应对,那APP在上层无论怎么检测,都没有用。下面将对测试场景进行详细分析。

抓包的步骤:

1.在客户端(手机)中设置代理服务器的地址

2.开启代理服务器(burp)的代理功能

如果在客户端对代理服务进行过滤,禁止客户端通过代理服务器进行访问Internet,添加如下代码:

1
connection = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);

官方对于Proxy.NO_PROXY的描述如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
 * A proxy setting that represents a {@code DIRECT} connection,
* basically telling the protocol handler not to use any proxying.
* Used, for instance, to create sockets bypassing any other global
* proxy settings (like SOCKS):
* <P>
* {@code Socket s = new Socket(Proxy.NO_PROXY);}
*
*/public final static Proxy NO_PROXY = new Proxy();

// Creates the proxy that represents a {@code DIRECT} connection.private Proxy() {
type = Type.DIRECT;
sa = null;
}

NO_PROXY实际上就是type属性为DIRECT的一个Proxy对象,这个type有三种:

  • DIRECT
  • HTTP
  • SOCKS

所以,Proxy.NO_PROXY的意思是connection的请求是直连。

此时若通过系统进行代理,app对外请求会失效,也就是视觉上看到的卡死状态,就是不让走系统代理。

安卓手机上设置系统代理即是在【设置】-【WLAN】-【修改网络】手动设置代理。

针对不走系统代理的情况有如下两种应对:

1、使用基于VPN模式的Postern

2、使用基于iptables的ProxyDroid

对此,我做出了如下一些测试:

4.1、使用系统代理

APP关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void sendRequestWithHttpURLConnection(){
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try{
URL url = new URL("http://www.baidu.com");
connection = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
connection.setRequestMethod("GET");
InputStream in = connection.getInputStream();

reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null){
response.append(line);
}

showResponse(response.toString());


} catch (Exception e){
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e){
e.printStackTrace();
}
}
if (connection != null){
connection.disconnect();
}
}
}
}).start();
}

针对Proxy.NO_PROXY,先测试一下,系统代理是否真的不能抓包。

如下图先设置系统代理,burp监听8888,此时打开APP,点击发送请求无任何反应,burp中也抓不到包,说明系统代理被禁了。

4.2、使用Postern代理

用过这款软件的都知道,当开启代理服务后状态栏会有个钥匙的标志,这可能也是基于VPN模式工作的特征

同样的APP,点击请求,此时成功绕过了Proxy.NO_PROXY检测!也说明了VPN协议在HTTP协议的下层。

5.安卓VPN检测

VPN也是代理的一种,但是由于通讯协议的差异,所以检测代码也不一样。

当客户端运行VPN虚拟隧道协议时,会在当前节点创建基于eth之上的tun0接口或ppp0接口,所以一旦出现带有明显特征的网络接口名称,就可以认定是使用了VPN协议进行通信。

下面这段代码的检测方式:出现特征tun0或者ppp0则退出应用,也就是我们看到的闪退效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
private void isDeviceInVPN() {
try {
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
String name = networkInterfaces.nextElement().getName();
if (name.equals("tun0") || name.equals("ppp0")) {
stop();
}
}
} catch (SocketException e) {
e.printStackTrace();
}
}

在点击监听中放置isDeviceInVPN()功能,点击即触发,如果检测到了使用了VPN则直接退出。

1
2
3
4
5
6
7
8
@Override
public void onClick(View view){
if (view.getId() == R.id.send_request){
isDeviceInVPN();
sendRequestWithHttpURLConnection();

}
}

5.1、使用ProxyDroid代理

当前场景:APP同时开启了代理检测以及VPN检测

这时使用iptables进行数据转发的软件 ProxyDroid 进行测试,配置如下图所示:

开启之后,系统状态栏不会出现钥匙的形状,这时再次进行抓包测试。

burp成功获取到了请求,至此代理与VPN的应对方法均已实现。所以,iptables 竟然能从OSI的 2、3层下面走吗,下面我们继续分析。

6.iptables原理

我们都知道安卓使用的是linux内核,而linux内核提供的防火墙工具是Netfilter/Iptables。

Netfilter是由linux内核集成的IP数据包过滤系统,其工作在内核内部,而Iptables则是让用户定义规则集的表结构。

也就是,iptables是一个命令行工具,位于用户空间,它真正操作的框架实现在内核当中。

Netfilter是一个数据包处理模块,它具有网络地址转换、数据包内容修改、数据包过滤等功能。 要使netfilter能够工作,就需要将所有的规则读入内存中。netfilter自己维护一个内存块,在此内存块中有4个表:filter表、NAT表、mangle表和raw表。在每个表中有相应的链,链中存放的是一条条的规则,规则就是过滤防火的语句或者其他功能的语句。也就是说表是链的容器,链是规则的容器。实际上,每个链都只是一个hook函数(钩子函数)而已。

Iptables主要工作在OSI七层的2.3.4层,好像也没比VPN的工作协议低,反而还有高的,但是测试结果证明,是我想错了,iptables不是由于协议低,而是没有出现tun0或者ppp0这两个关键的网卡特征,所以成功绕过了VPN的检测。

基于iptables这个流量转发,我还发现了一个新的名词,叫做“透明代理”,iptables的转发模式就是这种。

由此,延伸了一个新的代理模式,通过burp进行“透明代理”,网上的教程错综复杂,亲测使用过程如下。

7.透明代理

  • 原理:透明代理技术可以让客户端感觉不到代理的存在,用户不需要在浏览器中设置任何代理,只需设置缺省网关即可。在访问外部网络时,客户端的数据包被发送到缺省网关,通过缺省网关的路由,最终到达代理服务器,最后代理服务器运行代理进程,数据实际被重定向到代理服务器的代理端口,即由本地代理服务器向外请求所需数据然后拷贝给客户端。

接下来我将尝试:结合安卓端的透明代理技术与burp存在的invisible模式

7.1、使用Burp透明代理

(1)安卓端设置

首先在设备上手动进行设置:将所以请求80、443端口的tcp流量进行nat转发到192.168.50.177(burp的监听地址)的对应端口上

1
2
3
4
adb shell
su
iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to 192.168.50.177:80
iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to 192.168.50.177:443

查看当前规则是否成功添加

1
iptables -t nat -L

(2)代理服务器端设置

添加80和443的端口监听

在【Binding】中设置端口,选中 【All interfaces】

并对【Request handing】做出如下设置

Redirect to port - 如果配置了这个选项,Burp会在每次请求转发到指定的端口,而不必受限于浏览器所请求的目标。

Force use of SSL - 如果配置了这个选项,Burp会使用HTTPS在所有向外的连接,即使传入的请求中使用普通的HTTP。您可以使用此选项,在与SSL相关的响应修改选项结合,开展sslstrip般的攻击使用Burp,其中,强制执行HTTPS的应用程序可以降级为普通的HTTP的受害用户的流量在不知不觉中通过BurpProxy代理。

设置之后,Proxy状态如下

此时burp就可对转发到这里的80和443端口的流量进行透明代理

注意:如果出现443端口被占用,查找进程kill掉即可。

以管理员身份运行 cmd 执行如下代码

经过测试,burp成功抓取到了请求包。

这里不禁思考,如果是基于iptables进行的数据转发,那么刚才的ProxyDroid是否也内置了一些路由规则呢?

查看一下开启ProxyDroid时iptables当下的规则

从图中可以看到共有六条策略,其中最后两条就是我们刚才手动添加的,但并没有看到burp监听的8888端口,8123、8124一定是软件内置的代理转发端口,想要知道具体原理还需要详细分析ProxyDroid的源码。

血泪避坑:网上出现了很多教程,最关键的iptables规则写法不一,导致多次测试结果并不成功,如果将安卓终端的80和443端口同时转发到burp上监听的唯一一个端口则会出现连接错误。根据burp官方文档说明为每个端口号设置监听器会更加稳定,也就是要设置两个代理监听。

8.总结

根据不同的代码检测,也会有不同的应对方法,所以,遇到APP出现抓包闪退等问题,先逆向,查看源码,在通信处仔细进行分析,再针对检测代码进行绕过,才是正解。本文提到的并不是固定的处理方法,如果文章有叙述不当,尽请矫正。

9.参考链接

burp invisible官方文档

代理与VPN

iptables的内核原理

MYSQL事务隔离级别

发表于 2021-11-29 分类于 数据库

事务的隔离级别分为:未提交读(READ UNCOMMITTED)、已提交读(READ COMMITTED)、可重复读(REPEATABLE READ)、串行化(SERIALIZABLE)。

mysql数据库,当且仅当引擎是InnoDB,才支持事务

  • 未提交读 (READ UNCOMMITTED)

A事务已执行,但未提交;B事务查询到A事务的更新后数据;A事务回滚;—出现脏数据(脏读)

  • 已提交读 (READ COMMITTED)

A事务执行更新;B事务查询;A事务又执行更新;B事务再次查询时,前后两次数据不一致;—不可重复读,可以解决脏读

  • 可重复读 (REPEATABLE READ)

A事务无论执行多少次,只要不提交,B事务查询值都不变;B事务仅查询B事务开始时那一瞬间的数据快照;—mysql默认的,可以解决脏读 和 不可重复读

  • 串行化 (SERIALIZABLE)

不允许读写并发操作,写执行时,读必须等待。—相当于锁表,可以解决 脏读 不可重复读 和 虚读

查看隔离级别

全局事务级别:

1
SELECT @@GLOBAL.tx_isolation  或 SELECT @@GLOBAL.transaction_isolation

会话事务级别:

1
SELECT @@SESSION.tx_isolation 或 SELECT @@SESSION.transaction_isolation

或者

1
show variables like '%iso%';

设置隔离级别

临时有效

1
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}

持久有效

1
2
3
[mysqld]
transaction-isolation = REPEATABLE-READ
transaction-read-only = OFF

搭建Socks5代理

发表于 2021-06-05 分类于 服务器运维

最近在做3D模型下载时发现本地可以运行的代码放到服务器就跑不动了,抓包发现是服务器端无法访问三方接口,所以想到使用代理访问,同时记录下整个代理搭建过程。

安装

  • 下载: http://sourceforge.net/projects/ss5/files/
    1
    2
    3
    4
    5
    tar zxvf ss5-3.8.9-8.tar.gz
    cd ss5-3.8.9-8
    ./configure //默认是1080端口,如果想改端口的话,./configure --with-defaultport=10900
    make
    make install

默认安装目录在:/etc/opt/ss5

若出现报错:

configure: error: * Some of the headers weren’t found *

则 需要安装:

1
yum -y install pam-devel

如果出现报错:

SS5OpenLdap.c:29:18: fatal error: ldap.h: No such file or directory

安装

1
yum -y install openldap-devel
  • 修改配置

/etc/opt/ss5/ss5.conf内容全部删除,仅添加内容:

1
2
auth 0.0.0.0/0 - u
permit u 0.0.0.0/0 - 0.0.0.0/0 - - - - -
  • 添加账户

/etc/opt/ss5/ss5.passwd中添加用户名与密码:

1
ss5 pass
  • 启动socks5

默认情况ss5文件没有执行权限,如果觉得使用sh来启动麻烦,那么按如下方法:

1
2
3
4
chmod u+x /etc/rc.d/init.d/ss5
chkconfig --add ss5 //可选
chkconfig ss5 on //可选
service ss5 start

注意:如果你服务器开了防火墙不要忘了关掉,或者iptables里做下策略

使用

通用规则: curl -x [schema]://[user]:[pass]@[host]:[post] [URI] (-v 查询执行过程)

  • crul 版本 >= 7.21.7 时使用命令:
1
curl -x socks5h://localhost:10800 http://www.google.com/
  • crul 版本 >= 7.18.0 时使用命令:
1
curl --socks5-hostname localhost:10800 http://www.google.com/

许多工具在内部使用libcurl,或者在安装程序脚本中使用curl命令。如果很难修改命令行本身,可以使用环境变量设置代理。

1
env ALL_PROXY=socks5h://localhost:10800 PROGRAM [OPTION]...

如果你想覆盖系统代理设置,你可能还需要设置两个额外的变量:

1
env http_proxy=socks5h://localhost:10800 HTTPS_PROXY=socks5h://localhost:10800 ALL_PROXY=socks5h://localhost:10800 PROGRAM [OPTION]...

注意: http_proxy是小写的,其他两个是大写的。

在代码中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function http_post($sUrl, $aData, $aHeader = null, $proxy = false)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, $sUrl);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
if ($proxy) {
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); // Sockes5代理
curl_setopt($ch, CURLOPT_PROXY, HOST); // HOST:代理服务器
curl_setopt($ch, CURLOPT_PROXYPORT, PORT); // PORT: 代理端口
curl_setopt($ch, CURLOPT_PROXYUSERPWD, 'USER:PASS'); // USER: 账号 PASS:密码 (注意账号密码之间有一个 ':' )
}
!is_null($aHeader) && curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $aData);
$sResult = curl_exec($ch);
if ($sError = curl_error($ch)) {
response(curl_errno($ch), $sError);
}
curl_close($ch);
return $sResult;
}

vmware文件夹共享

发表于 2021-02-20 分类于 服务器运维

1.虚拟机设置 -> 选项 -> 共享文件夹 -> 总是启用 -> 选择宿主机要映射的目录

注意,虚拟机如果没有安装vmware-tools, 文件夹共享 将不可点击,需要先安装vmware-tools后才可以选择启用文件共享。安装方法

接下来切换到linux

  1. 安装vm-tools
1
yum install -y open-vm-tools open-vm-tools-desktop

安装完后执行

1
vmware-hgfsclient

我们先创建一个文件夹,再映射vmware共享文件夹到这个目录下

1
2
mkdir -p /data/
vmhgfs-fuse .host:/ /data/ -o allow_other -o nonempty

其中 /data/vm/ 就是linux和主机共享的目录,vm就是我们第一步文件共享时的建立的名称

这时候我们就可以像正常linux下文件一样去操作 /data/vm 下的文件

  1. 重启失效问题

linux重启后会导致挂载失效,需要重新挂载 所以我们写到开机自启脚本里

给执行权限 centos7 默认 /etc/rc.d/rc.local 无执行权限

1
chmod +x /etc/rc.d/rc.local

打开/etc/rc.d/rc.local 在最下面加入挂载命令

自动挂载vmware宿主机的共享文件夹

1
vmhgfs-fuse .host:/ /data/ -o allow_other -o nonempty
  1. 将目录软连到web目录 (可选)
1
ln -s /data/vm/ /data/wwwroot

然后创建站点,解析hosts

Laravel 启动流程

发表于 2021-02-03 分类于 PHP

0. Laravel生命周期

说明Laravel生命周期前,先简单回忆一下PHP的生命周期。

当我们请求一个PHP文件时,PHP为了完成这次请求,会发生5个阶段的生命周期切换。

PHP生命周期

MINIT: 模块初始化,即调用 php.ini 中指明的扩展的初始化函数进行初始化工作,如 mysql 扩展。
RINIT: 请求初始化,即初始化为执行本次脚本所需要的变量名称和变量值内容的符号表,如 $_SESSION变量。
HANDLE: 执行该PHP脚本。
RSHUTDOWN: 请求处理完成(Request Shutdown),按顺序调用各个模块的 RSHUTDOWN 方法,对每个变量调用 unset函数,如 unset $_SESSION 变量。
MSHUTDOWN: 关闭模块(Module Shutdown),PHP调用每个扩展的 MSHUTDOWN 方法,这是各个模块最后一次释放内存的机会, 这意味着没有下一个请求了。

小结: PHP是一种脚本语言,所有的变量只会在这一次请求中生效,下次请求之时已被重置,而不像Java静态变量拥有全局作用。

Laravel 的生命周期从public\index.php开始,从public\index.php结束。具体来讲可以分成4个步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 1. 文件载入composer生成的自动加载设置,包括所有你 composer require的依赖。
**/
require __DIR__.'/../vendor/autoload.php';

/**
* 2. 生成容器Container,Application实例,并向容器注册核心组件(HttpKernel,ConsoleKernel ,ExceptionHandler)
**/
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

/**
* 3.处理请求,生成并发送响应(毫不夸张的说,你99%的代码都运行在这个小小的handle 方法里面)
**/
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();

/**
* 4.请求结束,进行回调(还记得可终止中间件吗?没错,就是在这里回调的)
**/
$kernel->terminate($request, $response);

过程如下:

Laravel生命周期

1. 程序启动准备

程序入口在 index.php 中

1
2
3
4
5
6
7
8
9
10
11
12
13
require __DIR__.'/../vendor/autoload.php';

$app = require_once __DIR__.'/../bootstrap/app.php'; # 获取服务容器实例

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

$response->send();

$kernel->terminate($request, $response);

创建服务容器实例

服务容器的创建在 bootstrap\app.php 中进行.

1
2
3
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);

1.1 容器基础配置

容器 Application 的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}

$this->registerBaseBindings();

$this->registerBaseServiceProviders();

$this->registerCoreContainerAliases();
}

构造函数 主要完成以下基本配置:

  • 目录路径(绑定到容器中, 并提供类方法获取子目录)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public function setBasePath($basePath)
{
$this->basePath = rtrim($basePath, '\/');

$this->bindPathsInContainer();

return $this;
}

protected function bindPathsInContainer()
{
$this->instance('path', $this->path());
$this->instance('path.base', $this->basePath());
$this->instance('path.lang', $this->langPath());
$this->instance('path.config', $this->configPath());
$this->instance('path.public', $this->publicPath());
$this->instance('path.storage', $this->storagePath());
$this->instance('path.database', $this->databasePath());
$this->instance('path.resources', $this->resourcePath());
$this->instance('path.bootstrap', $this->bootstrapPath());
}
  • 绑定容器自身
1
2
3
4
5
6
7
8
9
10
11
12
protected function registerBaseBindings()
{
static::setInstance($this);

$this->instance('app', $this);

$this->instance(Container::class, $this);

$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
}
  • 基础服务注册( Event, Log, Route)
1
2
3
4
5
6
7
8
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));

$this->register(new LogServiceProvider($this));

$this->register(new RoutingServiceProvider($this));
}
  • 别名注册

多个接口名 对应一个简短别名, 后续在注册服务时只需绑定到别名上即可 (而不必绑定到具体接口名)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public function registerCoreContainerAliases()
{
foreach ([
'app' => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class],
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
'cookie' => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
'db' => [\Illuminate\Database\DatabaseManager::class],
'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
'files' => [\Illuminate\Filesystem\Filesystem::class],
'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class],
'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class],
'hash' => [\Illuminate\Contracts\Hashing\Hasher::class],
'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
'log' => [\Illuminate\Log\Writer::class, \Illuminate\Contracts\Logging\Log::class, \Psr\Log\LoggerInterface::class],
'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
'auth.password' => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
'queue.connection' => [\Illuminate\Contracts\Queue\Queue::class],
'queue.failer' => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
'redirect' => [\Illuminate\Routing\Redirector::class],
'redis' => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
'request' => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
'router' => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
'session' => [\Illuminate\Session\SessionManager::class],
'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
'url' => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}

1.2 核心类绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);

$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);

$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);

绑定重要接口:

  • Http 核心类
  • 命令行 核心类
  • 异常处理类

1.3 实例化Http核心类

1
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

Http 核心类的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function __construct(Application $app, Router $router)
{
$this->app = $app;
$this->router = $router;

$router->middlewarePriority = $this->middlewarePriority;

foreach ($this->middlewareGroups as $key => $middleware) {
$router->middlewareGroup($key, $middleware);
}

foreach ($this->routeMiddleware as $key => $middleware) {
$router->aliasMiddleware($key, $middleware);
}
}

上述过程主要做的事是将中间件赋值给路由

  • 中间件顺序优先级列表
  • 中间件组
  • 中间件别名

核心类 app/Http/Kernel.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
// 全局中间件,最先调用
protected $middleware = [

// 检测是否应用是否进入『维护模式』
// 见:https://d.laravel-china.org/docs/5.5/configuration#maintenance-mode
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,

// 检测请求的数据是否过大
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,

// 对提交的请求参数进行 PHP 函数 `trim()` 处理
\App\Http\Middleware\TrimStrings::class,

// 将提交请求参数中空子串转换为 null
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,

// 修正代理服务器后的服务器参数
\App\Http\Middleware\TrustProxies::class,
];

// 定义中间件组
protected $middlewareGroups = [

// Web 中间件组,应用于 routes/web.php 路由文件
'web' => [
// Cookie 加密解密
\App\Http\Middleware\EncryptCookies::class,

// 将 Cookie 添加到响应中
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,

// 开启会话
\Illuminate\Session\Middleware\StartSession::class,

// 认证用户,此中间件以后 Auth 类才能生效
// 见:https://d.laravel-china.org/docs/5.5/authentication
\Illuminate\Session\Middleware\AuthenticateSession::class,

// 将系统的错误数据注入到视图变量 $errors 中
\Illuminate\View\Middleware\ShareErrorsFromSession::class,

// 检验 CSRF ,防止跨站请求伪造的安全威胁
// 见:https://d.laravel-china.org/docs/5.5/csrf
\App\Http\Middleware\VerifyCsrfToken::class,

// 处理路由绑定
// 见:https://d.laravel-china.org/docs/5.5/routing#route-model-binding
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],

// API 中间件组,应用于 routes/api.php 路由文件
'api' => [
// 使用别名来调用中间件
// 请见:https://d.laravel-china.org/docs/5.5/middleware#为路由分配中间件
'throttle:60,1',
'bindings',
],
];

// 中间件别名设置,允许你使用别名调用中间件,例如上面的 api 中间件组调用
protected $routeMiddleware = [

// 只有登录用户才能访问,我们在控制器的构造方法中大量使用
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,

// HTTP Basic Auth 认证
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,

// 处理路由绑定
// 见:https://d.laravel-china.org/docs/5.5/routing#route-model-binding
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,

// 用户授权功能
'can' => \Illuminate\Auth\Middleware\Authorize::class,

// 只有游客才能访问,在 register 和 login 请求中使用,只有未登录用户才能访问这些页面
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,

// 访问节流,类似于 『1 分钟只能请求 10 次』的需求,一般在 API 中使用
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];
}

2. 请求实例化

以处理 Http 请求为例

index.php 入口文件

1
2
3
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

请求是通过 Illuminate\Http\Request::capture() 实例化的, 主要是将请求信息以对象形式表现出来

3.请求处理

入口文件:

1
2
3
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

$kernel->handle(...) 处理请求过程

Illuminate\Foundation\Http\Kernel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();

$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
$this->reportException($e);

$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));

$response = $this->renderException($request, $e);
}

$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);

return $response;
}


protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);

Facade::clearResolvedInstance('request');

$this->bootstrap(); # 核心类初始化

return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}


protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);

return $this->router->dispatch($request);
};
}

实际处理请求逻辑主要在 sendRequestThroughRouter 方法中, 它主要做了:

  • 核心类的初始化

  • 经由中间件过滤后将请求最终交由 Router 处理

对于 Http 请求处理, 中间件包括:

1
2
3
4
5
6
7
8
9
10
11
12
13
protected $middleware = [

\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,

\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,

\App\Http\Middleware\TrimStrings::class,

\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,

\App\Http\Middleware\TrustProxies::class,

];

该中间件数组定义在 Http 核心类中, 同时在核心类的构造函数中传递给 Router 类

3.1 请求处理环境初始化

核心类的初始化 bootstrap()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];

# 初始化
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}

protected function bootstrappers()
{
return $this->bootstrappers;
}

在服务容器 Application 类中

1
2
3
4
5
6
7
8
9
10
11
12
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;

foreach ($bootstrappers as $bootstrapper) {
$this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);

$this->make($bootstrapper)->bootstrap($this);

$this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
}
}

该步骤主要是主要是对核心类中定义的 $bootstrappers 数组元素(引导类)初始化.

bootstrap 过程具体是在服务容器来中进行, 由核心类调用并传入待初始化的类

Http 核心类默认包含以下 6 个启动服务:

  • 环境监测 \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class

从 .env 文件中解析环境变量到 getevn(), $_ENV, $_SERVER, 依赖 vlucas/phpdotenv 扩展包

  • 配置加载 \Illuminate\Foundation\Bootstrap\LoadConfiguration::class

载入 config 目录下所有 php 配置文件, 并将生成的配置存储类绑定到服务容器 $app['config']

同时配置时区及 多字节格式(utf8)

  • 异常处理 \Illuminate\Foundation\Bootstrap\HandleExceptions::class

报告所有错误 error_report(E_ALL)

提供对未捕获的异常, 错误的全局处理 set_error_handler, set_exception_handler, register_shutdown_function

  • 外观注册 \Illuminate\Foundation\Bootstrap\RegisterFacades::class

从 app.aliases 中读取外观配置数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
'aliases' => [

'App' => Illuminate\Support\Facades\App::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,

],

使用 spl_autoload_register(...) 处理类加载, 配合 class_alias() 提供类的别名调用

Facade外观类基类依赖__callStatic` 调用方法( 使用服务容器实例化对应类)

  • 服务提供者注册 \Illuminate\Foundation\Bootstrap\RegisterProviders::class

从 app.providers 中读取所有服务提供者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
'providers' => [

/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,

/*
* Package Service Providers...
*/

/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class, # 路由表生成
],

服务提供者经过解析后分为 3 种类型的服务提供者:

- eager 类型

马上调用 register 注册

- deferred 类型

记录下来, 当服务容器解析对应服务时, 才注册对应的服务提供者

- when 类型

记录下来, 当对应 event 触发时在注册对应服务提供者

  • 启动提供者 \Illuminate\Foundation\Bootstrap\BootProviders::class

调用服务容器的 boot() 方法, 依次调用在服务容器中 register 的所有服务提供者的 boot() 方法

3.2 路由处理请求

在内核处理请求, 将请求实例通过中间件处理后, 将请求的处理交给路由 Router 进行控制器的分发.

Http Kernel

1
2
3
4
5
6
7
8
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);

return $this->router->dispatch($request);
};
}

路由表存储结构说明

Illuminate\Routing\Route 存储单条路由

Illuminate\Routing\RouteCollection 保存所有 Route 实例, 形成路由表

Illuminate\Routing\Router 类实例持有 RouteCollection 路由表实例.

即, 一个 Router 持有一个 RouteCollection, 而 RouteCollection 拥有 N 个 Route

在 Router 中对请求的处理同样经过一系列的 路由中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 路由处理请求的入库
public function dispatchToRoute(Request $request)
{
return $this->runRoute($request, $this->findRoute($request));
}

# 根据请求的 url 和 method 查找对应的 route
protected function findRoute($request)
{
$this->current = $route = $this->routes->match($request);

$this->container->instance(Route::class, $route);

return $route;
}

# 根据对应的请求和路由条目, 返回相应的 $response
protected function runRoute(Request $request, Route $route)
{
$request->setRouteResolver(function () use ($route) {
return $route;
});

$this->events->dispatch(new Events\RouteMatched($route, $request));

return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}

# 请求经过路由中间件过滤后, 交由 route 的 run() 方法处理
protected function runRouteWithinStack(Route $route, Request $request)
{
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;

$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);

return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}

route 的 run() 方法最终将请求转给 Illuminate\Routing\ControllerDispatcher::dispatch 处理

1
2
3
4
5
6
7
8
9
10
11
12
public function dispatch(Route $route, $controller, $method)
{
$parameters = $this->resolveClassMethodDependencies(
$route->parametersWithoutNulls(), $controller, $method
);

if (method_exists($controller, 'callAction')) {
return $controller->callAction($method, $parameters);
}

return $controller->{$method}(...array_values($parameters));
}

剩下的事情就是 Controller控制器 的事了.

3.3 处理返回的 Response

在 Router 中有一个方法, 用于对返回的 $response 进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public function prepareResponse($request, $response)
{
return static::toResponse($request, $response);
}

/**
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
public static function toResponse($request, $response)
{
if ($response instanceof Responsable) {
$response = $response->toResponse($request);
}

if ($response instanceof PsrResponseInterface) {
$response = (new HttpFoundationFactory)->createResponse($response);
} elseif (! $response instanceof SymfonyResponse &&
($response instanceof Arrayable ||
$response instanceof Jsonable ||
$response instanceof ArrayObject ||
$response instanceof JsonSerializable ||
is_array($response))) {
$response = new JsonResponse($response);
} elseif (! $response instanceof SymfonyResponse) {
$response = new Response($response);
}

if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
$response->setNotModified();
}

return $response->prepare($request); # 最后的处理
}

上述过程中, 在返回 $response 之前进行了最后的处理 $response->prepare($request)

该过程是在 Symfony\Component\HttpFoundation\Response::prepare() 中进行

对响应的封装是通过 Illuminate\Http\Response 类完成, 该类底层是 Symfony 框架的 Response 类

即, Symfony\Component\HttpFoundation\Response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public function prepare(Request $request)
{
$headers = $this->headers;

if ($this->isInformational() || $this->isEmpty()) {
$this->setContent(null);
$headers->remove('Content-Type');
$headers->remove('Content-Length');
} else {
// Content-type based on the Request
if (!$headers->has('Content-Type')) {
$format = $request->getRequestFormat();
if (null !== $format && $mimeType = $request->getMimeType($format)) {
$headers->set('Content-Type', $mimeType);
}
}

// Fix Content-Type
$charset = $this->charset ?: 'UTF-8';
if (!$headers->has('Content-Type')) {
$headers->set('Content-Type', 'text/html; charset='.$charset);
} elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) {
// add the charset
$headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset);
}

// Fix Content-Length
if ($headers->has('Transfer-Encoding')) {
$headers->remove('Content-Length');
}

if ($request->isMethod('HEAD')) {
// cf. RFC2616 14.13
$length = $headers->get('Content-Length');
$this->setContent(null);
if ($length) {
$headers->set('Content-Length', $length);
}
}
}

// Fix protocol
if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) {
$this->setProtocolVersion('1.1');
}

// Check if we need to send extra expire info headers
if ('1.0' == $this->getProtocolVersion() && false !== strpos($this->headers->get('Cache-Control'), 'no-cache')) {
$this->headers->set('pragma', 'no-cache');
$this->headers->set('expires', -1);
}

$this->ensureIEOverSSLCompatibility($request);

return $this;
}

4. 响应发送和程序终止

4.1 响应的发送

在 index.php 入口文件的最后是将响应返回给客户端

$response->send();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Symfony\Component\HttpFoundation\Response

public function send()
{
$this->sendHeaders();
$this->sendContent();

if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (!\in_array(PHP_SAPI, array('cli', 'phpdbg'), true)) {
static::closeOutputBuffers(0, true);
}

return $this;
}

public function sendHeaders()
{
// headers have already been sent by the developer
if (headers_sent()) {
return $this;
}

// headers
foreach ($this->headers->allPreserveCase() as $name => $values) {
foreach ($values as $value) {
header($name.': '.$value, false, $this->statusCode);
}
}

// status
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);

return $this;
}

public function sendContent()
{
echo $this->content;

return $this;
}

4.2 请求中止

在 index.php 入口文件的最后:

\$kernel->terminate( \$request, \$response );

依旧以 Http Kernel 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public function terminate($request, $response)
{
$this->terminateMiddleware($request, $response); # 中间件中止处理

$this->app->terminate(); # 服务容器的中止处理函数
}

protected function terminateMiddleware($request, $response)
{
$middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
$this->gatherRouteMiddleware($request),
$this->middleware
);

foreach ($middlewares as $middleware) {
if (! is_string($middleware)) {
continue;
}

list($name) = $this->parseMiddleware($middleware);

$instance = $this->app->make($name);

if (method_exists($instance, 'terminate')) {
$instance->terminate($request, $response);
}
}
}

此处的中间件指的是定义在 Kernel 中的 $middleware 中间件数组列表, 不包含 路由中间件.

Laravel 5.1 注: 默认只有会话中间件包含 terminate() 函数

Application 服务容器的中止处理函数

1
2
3
4
5
6
public function terminate()
{
foreach ($this->terminatingCallbacks as $terminating) {
$this->call($terminating);
}
}

HTTP_AUTHORIZATION

发表于 2021-01-25 分类于 PHP

做接口认证的时候,我们常会采用Http BearerAuth认证方式,即请求时在Header带上Authorization参数:

Authorization: Bearer your_token

我们都知道php的自定义头信息都可以使用$SERVER['HTTP*']来获取, 如 “Cookie: BAIDUID=B86A8A0FF:”, 获取的时候,我们可以使用$_SERVER['HTTP_COOKIE']来获取。

但Authorization是个例外,在Apache服务器下会出现$_SERVER['HTTP_AUTHORIZATION'] 获取不到值的问题.

解决方法如下:

  • 如果已经开启rewrite_module模块,需要在httpd-vhosts.conf模块下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<VirtualHost *:80>
ServerName test.com
DocumentRoot "/data/www/"
<Directory "/data/www/">
# Laravel配置
Options Indexes FollowSymLinks
AllowOverride All
Order Deny,Allow
Require all granted
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
# HTTP 基础认证需要添加下面两行
RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
</Directory>
</VirtualHost>
  • 如果没有开启rewrite_module模块,需要在入口处添加.htaccess文件,内容如下:
1
2
3
4
5
Options +FollowSymlinks -Multiviews
RewriteEngine On
#Authorization Headers
RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

记一次spl_autoload_register踩坑

发表于 2020-11-16 分类于 PHP

定义: spl_autoload_register – 注册给定的函数作为 __autoload 的实现 【官方文档】

简单说就是当我们使用未引用的类时会触发这个函数执行,例如:

示例1

根据这个特性,我们可以实现自动的类加载功能,例如:

示例2

一切看起来都是如此美好,但是问题来了,在测试中我发现如下问题:

坑

虽然成功加载了目标类,但是spl_autoload_register被触发了两次,外部实例化类的地方只有一处,显然这是不正常的(红框内的类按照处理逻辑是不应该被加载的)。

经查发现出问题的函数是 class_exists:

class_exists

它的第二个参数 autoload 默认是 true, 这就导致如果指定的类 $class_name 不存在就会触发__autoload, 而spl_autoload_register是__autoload的实现,所以spl_autoload_register就被触发了。

解决方法也简单,就是在指定参数autoload为false即可:

示例3

注意此处的Demo是放在test目录下面的,和上面示例12Demo放同级目录不同

类似的常用方法还有: class_alias, trait_exists,interface_exists 等。

细心的大佬可能会发现在官方文档里面已经有人提出这个问题了:

明人不说暗话,这个坑出现在我项目 QQ空间自动导出 里,项目不大所以没有使用三方框架,自己写了一个简单的类自动加载,替代繁琐的require过程。 项目测试运行正常,但是调试的时候发现,接口调用的同时自动模型构建代码被触发执行了,原因是class_exists触发了自动加载,导致substr截取的所有字符串都当做了类来处理,结果碰巧有一个字符串就是我的自动代码构建类,这就很有意思了。

来个简单安全Case:

payload

nice~

12…16
Mr.Gou

Mr.Gou

155 日志
11 分类
38 标签
RSS
GitHub E-Mail Weibo
Links
  • 阮一峰的网络日志
  • 离别歌 - 代码审计漏洞挖掘pythonc++
  • 一只猿 - 前端攻城尸
  • 雨了个雨's blog
  • nMask's Blog - 风陵渡口
  • 区块链技术导航
© 2022 蜀ICP备2022014529号