部署脚本 ci.sh:
1 |
|
webhook(以php为例):
1 |
|
注意 1 :git公钥归属的问题会导致你的
git pull
命令无法执行,需要把当前web服务器的用户权限提高
1 | su - # 进入超级用户模式 |
注意 2 : 出现
.git/FETCH_HEAD没有权限
需要提升.git目录权限
chmod -R 777 .git
部署脚本 ci.sh:
1 | #!/bin/sh |
webhook(以php为例):
1 | <?php |
注意 1 :git公钥归属的问题会导致你的
git pull
命令无法执行,需要把当前web服务器的用户权限提高
1 | su - # 进入超级用户模式 |
注意 2 : 出现
.git/FETCH_HEAD没有权限
需要提升.git目录权限
chmod -R 777 .git
MERGE存储引擎把一组MyISAM数据表当做一个逻辑单元来对待,让我们可以同时对他们进行查询,是一种简单的分表方案。
例如将日志表按日期分为 log_20221103, log_20221104 两张表
1 | CREATE TABLE `log_20221103` ( |
分别插入数据:
log_20221103:
id | log |
---|---|
1 | 1 |
2 | 2 |
3 | 3 |
log_20221104:
id | log |
---|---|
1 | a |
2 | b |
3 | c |
创建Merge表:
1 | CREATE TABLE `log_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表的时候对基础表是没有影响的。
http://www.navicat.com.cn/products
Navicat Premium 16Crack.bat
1 | @echo off |
在每次试用到期时,执行清理,或者添加至定时程序中,每日自动清理相当于无限试用
本项目自始至终都是免费使用,如果有你发现有人盗取牟利,请拒绝并不遗余力地在一切平台举报投诉他!
本项目只做个人学习研究之用,不得用于商业用途!
若资金允许,请点击链接购买正版,谢谢合作!
本文将从网络通信原理浅析在android中出现的一些代理转发检测,这些功能会使我们测试app时出现抓不到包或者应用闪退等情况,针对这种场景,我搭建了测试环境,并对其场景展开分析与实施应对方案。
网络通信嘛,首先得知道什么是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。
代理(proxy) 也称网络代理,是一种特殊的网络服务,允许一个终端(一般为客户端)通过这个服务与另外一个终端(一般为服务器)进行非直接的连接。
一个完整的代理请求过程
为:客户端首先根据代理服务器所使用的代理协议,与代理服务器创建连接,接着按照协议请求对目标服务器创建连接、或者获得目标服务器的指定资源。
VPN(virtual private network)(虚拟专用网络 )是常用于连接中、大型企业或团体间私人网络的通讯方法。它利用隧道协议(Tunneling Protocol)来达到发送端认证、消息保密与准确性等功能。
从各自的定义,我们就能看出VPN的特点是采取隧道协议进行数据传输和保护;而代理使用的则是对应的代理协议。
下面是VPN和代理的常用协议:
协议名称 | |
---|---|
VPN | OpvenVPN、IPsec、IKEv2、PPTP、L2TP、WireGuard等 |
代理 | HTTP、HTTPS、SOCKS、FTP、RTSP等 |
VPN 协议大多是作用在 OSI 的第二层和第三层之间,所以使用 VPN 时,几乎能转发所有的流量。
而代理协议多作用在应用层,最高层。
知道了代理与VPN的作用后,在APP中,如果开发人员在代码中添加了一些网络层的检测机制,而这些机制恰恰又是针对工作层协议进行的检测,那么只要分析出工作在IOS的哪一层,抢先一步在下层做出应对,那APP在上层无论怎么检测,都没有用。下面将对测试场景进行详细分析。
抓包的步骤:
1.在客户端(手机)中设置代理服务器的地址
2.开启代理服务器(burp)的代理功能
如果在客户端对代理服务进行过滤,禁止客户端通过代理服务器进行访问Internet,添加如下代码:
1 | connection = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); |
官方对于Proxy.NO_PROXY的描述如下:
1 | * A proxy setting that represents a {@code DIRECT} connection, |
NO_PROXY实际上就是type属性为DIRECT的一个Proxy对象,这个type有三种:
所以,Proxy.NO_PROXY的意思是connection的请求是直连。
此时若通过系统进行代理,app对外请求会失效,也就是视觉上看到的卡死状态,就是不让走系统代理。
安卓手机上设置系统代理即是在【设置】-【WLAN】-【修改网络】手动设置代理。
针对不走系统代理的情况有如下两种应对:
1、使用基于VPN
模式的Postern
2、使用基于iptables
的ProxyDroid
对此,我做出了如下一些测试:
APP关键代码如下:
1 | private void sendRequestWithHttpURLConnection(){ |
针对Proxy.NO_PROXY,先测试一下,系统代理是否真的不能抓包。
如下图先设置系统代理,burp监听8888,此时打开APP,点击发送请求无任何反应,burp中也抓不到包,说明系统代理被禁了。
用过这款软件的都知道,当开启代理服务后状态栏会有个钥匙
的标志,这可能也是基于VPN模式工作的特征
同样的APP,点击请求,此时成功绕过了Proxy.NO_PROXY检测!也说明了VPN协议在HTTP协议的下层。
VPN也是代理的一种,但是由于通讯协议的差异,所以检测代码也不一样。
当客户端运行VPN虚拟隧道协议
时,会在当前节点创建
基于eth之上的tun0
接口或ppp0
接口,所以一旦出现带有明显特征的网络接口名称,就可以认定是使用了VPN协议进行通信。
下面这段代码的检测方式:出现特征tun0或者ppp0则退出应用,也就是我们看到的闪退效果。
1 | private void isDeviceInVPN() { |
在点击监听中放置isDeviceInVPN()功能,点击即触发,如果检测到了使用了VPN则直接退出。
1 | @Override |
当前场景:APP同时开启了代理检测以及VPN检测
这时使用iptables进行数据转发的软件 ProxyDroid 进行测试,配置如下图所示:
开启之后,系统状态栏不会出现钥匙的形状,这时再次进行抓包测试。
burp成功获取到了请求,至此代理与VPN的应对方法均已实现。所以,iptables 竟然能从OSI的 2、3层下面走吗,下面我们继续分析。
我们都知道安卓使用的是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进行“透明代理”,网上的教程错综复杂,亲测使用过程如下。
感觉不到代理的存在
,用户不需要在浏览器中设置任何代理,只需设置缺省网关即可。在访问外部网络时,客户端的数据包被发送到缺省网关,通过缺省网关的路由,最终到达代理服务器,最后代理服务器运行代理进程,数据实际被重定向到代理服务器的代理端口,即由本地代理服务器向外请求所需数据然后拷贝给客户端。接下来我将尝试:结合安卓端的透明代理技术与burp存在的invisible模式
(1)安卓端设置
首先在设备上手动进行设置:将所以请求80、443端口的tcp流量进行nat转发到192.168.50.177(burp的监听地址)的对应端口上
1 | adb shell |
查看当前规则是否成功添加
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官方文档说明为每个端口号设置监听器会更加稳定,也就是要设置两个代理监听。
根据不同的代码检测,也会有不同的应对方法,所以,遇到APP出现抓包闪退等问题,先逆向,查看源码,在通信处仔细进行分析,再针对检测代码进行绕过,才是正解。本文提到的并不是固定的处理方法,如果文章有叙述不当,尽请矫正。
事务的隔离级别分为:未提交读(READ UNCOMMITTED)、已提交读(READ COMMITTED)、可重复读(REPEATABLE READ)、串行化(SERIALIZABLE)。
mysql数据库,当且仅当引擎是InnoDB,才支持事务
A事务已执行,但未提交;B事务查询到A事务的更新后数据;A事务回滚;—出现脏数据(脏读)
A事务执行更新;B事务查询;A事务又执行更新;B事务再次查询时,前后两次数据不一致;—不可重复读,可以解决脏读
A事务无论执行多少次,只要不提交,B事务查询值都不变;B事务仅查询B事务开始时那一瞬间的数据快照;—mysql默认的,可以解决脏读 和 不可重复读
不允许读写并发操作,写执行时,读必须等待。—相当于锁表,可以解决 脏读 不可重复读 和 虚读
全局事务级别:
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 | [mysqld] |
最近在做3D模型下载时发现本地可以运行的代码放到服务器就跑不动了,抓包发现是服务器端无法访问三方接口,所以想到使用代理访问,同时记录下整个代理搭建过程。
1 | tar zxvf ss5-3.8.9-8.tar.gz |
默认安装目录在:/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 | auth 0.0.0.0/0 - u |
/etc/opt/ss5/ss5.passwd中添加用户名与密码:
1 | ss5 pass |
默认情况ss5文件没有执行权限,如果觉得使用sh来启动麻烦,那么按如下方法:
1 | chmod u+x /etc/rc.d/init.d/ss5 |
注意:如果你服务器开了防火墙不要忘了关掉,或者iptables里做下策略
通用规则: curl -x [schema]://[user]:[pass]@[host]:[post] [URI] (-v 查询执行过程)
1 | curl -x socks5h://localhost:10800 http://www.google.com/ |
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 | function http_post($sUrl, $aData, $aHeader = null, $proxy = false) |
1.虚拟机设置 -> 选项 -> 共享文件夹 -> 总是启用 -> 选择宿主机要映射的目录
注意,虚拟机如果没有安装vmware-tools, 文件夹共享 将不可点击,需要先安装vmware-tools后才可以选择启用文件共享。安装方法
接下来切换到linux
1 | yum install -y open-vm-tools open-vm-tools-desktop |
安装完后执行
1 | vmware-hgfsclient |
我们先创建一个文件夹,再映射vmware共享文件夹到这个目录下
1 | mkdir -p /data/ |
其中 /data/vm/ 就是linux和主机共享的目录,vm就是我们第一步文件共享时的建立的名称
这时候我们就可以像正常linux下文件一样去操作 /data/vm 下的文件
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 | ln -s /data/vm/ /data/wwwroot |
然后创建站点,解析hosts
说明Laravel生命周期前,先简单回忆一下PHP的生命周期。
当我们请求一个PHP文件时,PHP为了完成这次请求,会发生5个阶段的生命周期切换。
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 | /** |
过程如下:
程序入口在 index.php 中
1 | require __DIR__.'/../vendor/autoload.php'; |
创建服务容器实例
服务容器的创建在 bootstrap\app.php
中进行.
1 | $app = new Illuminate\Foundation\Application( |
容器 Application
的构造函数:
1 | public function __construct($basePath = null) |
构造函数 主要完成以下基本配置:
1 | public function setBasePath($basePath) |
1 | protected function registerBaseBindings() |
1 | protected function registerBaseServiceProviders() |
多个接口名 对应一个简短别名, 后续在注册服务时只需绑定到别名上即可 (而不必绑定到具体接口名)
1 | public function registerCoreContainerAliases() |
1 | $app->singleton( |
绑定重要接口:
1 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); |
Http 核心类的构造函数
1 | public function __construct(Application $app, Router $router) |
上述过程主要做的事是将中间件赋值给路由
核心类 app/Http/Kernel.php
1 | <?php |
以处理 Http 请求为例
index.php
入口文件
1 | $response = $kernel->handle( |
请求是通过 Illuminate\Http\Request::capture()
实例化的, 主要是将请求信息以对象形式表现出来
入口文件:
1 | $response = $kernel->handle( |
$kernel->handle(...)
处理请求过程
Illuminate\Foundation\Http\Kernel
1 | public function handle($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 类
核心类的初始化 bootstrap()
1 | protected $bootstrappers = [ |
在服务容器 Application
类中
1 | public function bootstrapWith(array $bootstrappers) |
该步骤主要是主要是对核心类中定义的 $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 | 'aliases' => [ |
使用 spl_autoload_register(...)
处理类加载, 配合 class_alias()
提供类的别名调用
Facade外观类基类依赖
__callStatic` 调用方法( 使用服务容器实例化对应类)
\Illuminate\Foundation\Bootstrap\RegisterProviders::class
从 app.providers
中读取所有服务提供者
1 | 'providers' => [ |
服务提供者经过解析后分为 3 种类型的服务提供者:
- eager 类型
马上调用 register
注册
- deferred 类型
记录下来, 当服务容器解析对应服务时, 才注册对应的服务提供者
- when 类型
记录下来, 当对应 event 触发时在注册对应服务提供者
\Illuminate\Foundation\Bootstrap\BootProviders::class
调用服务容器的 boot()
方法, 依次调用在服务容器中 register
的所有服务提供者的 boot()
方法
在内核处理请求, 将请求实例通过中间件处理后, 将请求的处理交给路由 Router 进行控制器的分发.
Http Kernel
1 | protected function dispatchToRouter() |
路由表存储结构说明
Illuminate\Routing\Route
存储单条路由
Illuminate\Routing\RouteCollection
保存所有 Route
实例, 形成路由表
Illuminate\Routing\Router
类实例持有 RouteCollection
路由表实例.
即, 一个 Router
持有一个 RouteCollection
, 而 RouteCollection
拥有 N 个 Route
在 Router
中对请求的处理同样经过一系列的 路由中间件
1 | # 路由处理请求的入库 |
route
的 run()
方法最终将请求转给 Illuminate\Routing\ControllerDispatcher::dispatch
处理
1 | public function dispatch(Route $route, $controller, $method) |
剩下的事情就是 Controller控制器 的事了.
在 Router
中有一个方法, 用于对返回的 $response
进行处理
1 | public function prepareResponse($request, $response) |
上述过程中, 在返回 $response
之前进行了最后的处理 $response->prepare($request)
该过程是在 Symfony\Component\HttpFoundation\Response::prepare()
中进行
对响应的封装是通过
Illuminate\Http\Response
类完成, 该类底层是 Symfony 框架的 Response 类
即, Symfony\Component\HttpFoundation\Response
1 | public function prepare(Request $request) |
在 index.php
入口文件的最后是将响应返回给客户端
$response->send();
1 | Symfony\Component\HttpFoundation\Response |
在 index.php
入口文件的最后:
\$kernel->terminate( \$request, \$response );
依旧以 Http Kernel 为例:
1 | public function terminate($request, $response) |
此处的中间件指的是定义在 Kernel 中的 $middleware
中间件数组列表, 不包含 路由中间件.
Laravel 5.1 注: 默认只有会话中间件包含 terminate() 函数
Application
服务容器的中止处理函数
1 | public function terminate() |
做接口认证的时候,我们常会采用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 | <VirtualHost *:80> |
rewrite_module
模块,需要在入口处添加.htaccess
文件,内容如下:1 | Options +FollowSymlinks -Multiviews |
定义: spl_autoload_register – 注册给定的函数作为 __autoload 的实现 【官方文档】
简单说就是当我们使用未引用的类时会触发这个函数执行,例如:
根据这个特性,我们可以实现自动的类加载功能,例如:
一切看起来都是如此美好,但是问题来了,在测试中我发现如下问题:
虽然成功加载了目标类,但是spl_autoload_register被触发了两次,外部实例化类的地方只有一处,显然这是不正常的(红框内的类按照处理逻辑是不应该被加载的)。
经查发现出问题的函数是 class_exists:
它的第二个参数 autoload
默认是 true
, 这就导致如果指定的类 $class_name
不存在就会触发__autoload, 而spl_autoload_register是__autoload的实现,所以spl_autoload_register就被触发了。
解决方法也简单,就是在指定参数autoload为false即可:
注意此处的Demo是放在test目录下面的,和上面示例12Demo放同级目录不同
类似的常用方法还有: class_alias
, trait_exists
,interface_exists
等。
细心的大佬可能会发现在官方文档里面已经有人提出这个问题了:
明人不说暗话,这个坑出现在我项目 QQ空间自动导出 里,项目不大所以没有使用三方框架,自己写了一个简单的类自动加载,替代繁琐的require过程。 项目测试运行正常,但是调试的时候发现,接口调用的同时自动模型构建代码被触发执行了,原因是class_exists触发了自动加载,导致substr截取的所有字符串都当做了类来处理,结果碰巧有一个字符串就是我的自动代码构建类,这就很有意思了。
来个简单安全Case:
nice~