首页
关于
Search
1
给你10个市场数据调研报告的免费下载网站!以后竞品数据就从这里找!
183 阅读
2
php接口优化 使用curl_multi_init批量请求
144 阅读
3
《从菜鸟到大师之路 ElasticSearch 篇》
107 阅读
4
2024年备考系统架构设计师
104 阅读
5
PHP 文件I/O
92 阅读
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
登录
Search
标签搜索
php函数
php语法
性能优化
安全
错误和异常处理
问题
vue
Composer
Session
缓存
框架
Swoole
api
并发
异步
正则表达式
php-fpm
mysql 索引
开发规范
协程
dafenqi
累计撰写
786
篇文章
累计收到
31
条评论
首页
栏目
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
副业
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
页面
关于
搜索到
786
篇与
的结果
2023-08-04
九个PHP很有用的功能
九个PHP很有用的功能下面是九个PHP中很有用的功能,不知道你用过了吗?1. 具有任意参数数量的函数您可能已经知道 PHP 允许您使用可选参数定义函数。但是还有一种方法可以允许完全任意数量的函数参数。首先,下面是一个只有可选参数的示例:// function with 2 optional arguments function foo($arg1 = '', $arg2 = '') {echo "arg1: $arg1\n"; echo "arg2: $arg2\n";}foo('hello','world');/* prints: arg1: hello arg2: world */foo();/* prints: arg1: arg2: */现在,让我们看看如何构建一个接受任意数量参数的函数。这次我们将利用func_get_args():// yes, the argument list can be empty function foo() {// returns an array of all passed arguments $args = func_get_args(); foreach ($args as $k => $v) { echo "arg".($k+1).": $v\n"; }}foo();/ prints nothing /foo('hello');/* prints arg1: hello */foo('hello', 'world', 'again');/* prints arg1: hello arg2: world arg3: again */2. 使用 glob() 查找文件许多 PHP 函数都有很长的描述性名称。但是,可能很难说出名为glob()的函数的作用,除非您已经从其他地方熟悉该术语。把它想象成 scandir() 函数的更强大的版本。它可以让您使用模式搜索文件。// get all php files $files = glob('*.php');print_r($files);/* output looks like: Array ( [0] => phptest.php [1] => pi.php [2] => post_output.php [3] => test.php ) */您可以获取多种文件类型,如下所示:// get all php files AND txt files $files = glob('*.{php,txt}', GLOB_BRACE);print_r($files);/* output looks like: Array ( [0] => phptest.php [1] => pi.php [2] => post_output.php [3] => test.php [4] => log.txt [5] => test.txt ) */请注意,文件实际上可以使用路径返回,具体取决于您的查询:$files = glob('../images/a*.jpg');print_r($files);/* output looks like: Array ( [0] => ../images/apple.jpg [1] => ../images/art.jpg ) */如果你想获取每个文件的完整路径,你可以对返回的值调用 realpath() 函数:$files = glob('../images/a*.jpg');// applies the function to each array element $files = array_map('realpath',$files);print_r($files);/* output looks like: Array ( [0] => C:\wamp\www\images\apple.jpg [1] => C:\wamp\www\images\art.jpg ) */3. 内存使用信息通过观察脚本的内存使用情况,您可以更好地优化代码。PHP有一个垃圾收集器和一个相当复杂的内存管理器。脚本使用的内存量。可以在脚本执行期间上下移动。要获取当前的内存使用情况,我们可以使用 memory_get_usage() 函数,并且要获得在任何时候使用的最高内存量,我们可以使用 memory_get_peak_usage() 函数。echo "Initial: ".memory_get_usage()." bytes \n";/* prints Initial: 361400 bytes */// let's use up some memory for ($i = 0; $i < 100000; $i++) {$array []= md5($i);}// let's remove half of the array for ($i = 0; $i < 100000; $i++) {unset($array[$i]);}echo "Final: ".memory_get_usage()." bytes \n";/* prints Final: 885912 bytes */echo "Peak: ".memory_get_peak_usage()." bytes \n";/* prints Peak: 13687072 bytes */4. CPU 使用率信息为此,我们将使用 getrusage() 函数。请记住,这在 Windows 平台上不可用。print_r(getrusage());/* prints Array ( [ru_oublock] => 0 [ru_inblock] => 0 [ru_msgsnd] => 2 [ru_msgrcv] => 3 [ru_maxrss] => 12692 [ru_ixrss] => 764 [ru_idrss] => 3864 [ru_minflt] => 94 [ru_majflt] => 0 [ru_nsignals] => 1 [ru_nvcsw] => 67 [ru_nivcsw] => 4 [ru_nswap] => 0 [ru_utime.tv_usec] => 0 [ru_utime.tv_sec] => 0 [ru_stime.tv_usec] => 6269 [ru_stime.tv_sec] => 0 ) */除非您已经拥有系统管理背景,否则这可能看起来有点神秘。以下是每个值的解释(您无需记住这些值):ru_oublock:块输出操作ru_inblock:块输入操作ru_msgsnd:发送的消息ru_msgrcv:收到的消息ru_maxrss:最大驻留集大小ru_ixrss:整体共享内存大小ru_idrss:整数非共享数据大小ru_minflt:页面回收ru_majflt:页面错误ru_nsignals:接收到的信号ru_nvcsw:自愿上下文切换ru_nivcsw:非自愿上下文切换ru_nswap:掉期ru_utime.tv_usec:用户使用时间(微秒)ru_utime.tv_sec:用户使用时间(秒)ru_stime.tv_usec:系统使用时间(微秒)ru_stime.tv_sec:系统使用时间(秒)要查看脚本消耗了多少 CPU 功率,我们需要查看“用户时间”和“系统时间”值。默认情况下,秒和微秒部分是单独提供的。您可以将微秒值除以 1 万,然后将其添加到秒值中,以十进制数的形式获得总秒数。让我们看一个例子:// sleep for 3 seconds (non-busy) sleep(3);$data = getrusage();echo "User time: ".($data['ru_utime.tv_sec'] + $data['ru_utime.tv_usec'] / 1000000);echo "System time: ".($data['ru_stime.tv_sec'] + $data['ru_stime.tv_usec'] / 1000000);/* prints User time: 0.011552 System time: 0 */尽管脚本运行大约需要 3 秒,但 CPU 使用率非常低。因为在睡眠操作期间,脚本实际上并不消耗 CPU 资源。还有许多其他任务可能需要实时时间,但可能不会占用 CPU 时间,例如等待磁盘操作。如您所见,CPU 使用率和运行时的实际长度并不总是相同的。这是另一个例子:// loop 10 million times (busy) for($i=0;$i<10000000;$i++) {}$data = getrusage();echo "User time: ".($data['ru_utime.tv_sec'] + $data['ru_utime.tv_usec'] / 1000000);echo "System time: ".($data['ru_stime.tv_sec'] + $data['ru_stime.tv_usec'] / 1000000);/* prints User time: 1.424592 System time: 0.004204 */这花费了大约 1.4 秒的 CPU 时间,几乎所有时间都是用户时间,因为没有系统调用。系统时间是 CPU 代表程序对内核执行系统调用所花费的时间。下面是一个示例:$start = microtime(true);// keep calling microtime for about 3 seconds while(microtime(true) - $start < 3) {}$data = getrusage();echo "User time: ".($data['ru_utime.tv_sec'] + $data['ru_utime.tv_usec'] / 1000000);echo "System time: ".($data['ru_stime.tv_sec'] + $data['ru_stime.tv_usec'] / 1000000);/* prints User time: 1.088171 System time: 1.675315 */现在我们有相当多的系统时间使用。这是因为脚本多次调用 microtime() 函数,该函数通过操作系统执行请求以获取时间。此外,您可能会注意到这些数字加起来不超过 3 秒。这是因为服务器上可能还有其他进程,并且脚本在 100 秒的整个持续时间内没有使用 3% CPU。5. 魔术常数PHP 提供了有用的魔术常量,用于获取当前行号 ()、文件路径 ()、目录路径 ()、函数名称 ()、类名 ()、方法名 (__METHOD__) 和命名空间 ()。__LINE____FILE____DIR____FUNCTION____CLASS____NAMESPACE__我们不会在本文中逐一介绍其中的每一个,但我会向您展示一些用例。当包含其他脚本时,最好使用常量(或者,从 PHP 5.3 开始):__FILE____DIR__// this is relative to the loaded script's path // it may cause problems when running scripts from different directories require_once('config/database.php');// this is always relative to this file's path // no matter where it was included from require_once(dirname(__FILE__) . '/config/database.php');使用使调试更容易。您可以跟踪行号:__LINE__// some code // ... my_debug("some debug message", __LINE__);/* prints Line 4: some debug message */// some more code // ... my_debug("another debug message", __LINE__);/* prints Line 11: another debug message */function my_debug($msg, $line) {echo "Line $line: $msg\n";}6. 生成唯一 ID在某些情况下,可能需要生成唯一的字符串。我见过很多人为此使用该功能,即使它并不完全是为此目的:md5()// generate unique string echo md5(time() . mt_rand(1,1000000));实际上有一个名为uniqid()的PHP函数用于此目的。// generate unique string echo uniqid();/* prints 4bd67c947233e */// generate another unique string echo uniqid();/* prints 4bd67c9472340 */您可能会注意到,即使字符串是唯一的,它们对于前几个字符看起来也很相似。这是因为生成的字符串与服务器时间相关。这实际上有一个很好的副作用,因为每个新生成的 id 都按字母顺序稍后出现,因此可以对它们进行排序。为了减少获得重复项的机会,您可以传递前缀或第二个参数来增加熵:// with prefix echo uniqid('foo_');/* prints foo_4bd67d6cd8b8f */// with more entropy echo uniqid('',true);/* prints 4bd67d6cd8b926.12135106 */// both echo uniqid('bar_',true);/* prints bar_4bd67da367b650.43684647 */此函数将生成比 更短的字符串,这也将为您节省一些空间。md5()7. 序列化您是否曾经需要在数据库或文本文件中存储复杂变量?您不必想出一个花哨的解决方案来将数组或对象转换为格式化字符串,因为 PHP 已经具有用于此目的的函数。序列化变量有两种常用方法。下面是一个使用 serialize() 和 unserialize() 的示例:// a complex array $myvar = array('hello', 42, array(1,'two'), 'apple');// convert to a string $string = serialize($myvar);echo $string;/* prints a:4:{i:0;s:5:"hello";i:1;i:42;i:2;a:2:{i:0;i:1;i:1;s:3:"two";}i:3;s:5:"apple";} */// you can reproduce the original variable $newvar = unserialize($string);print_r($newvar);/* prints Array ( [0] => hello [1] => 42 [2] => Array ( [0] => 1 [1] => two ) [3] => apple ) */这是本机 PHP 序列化方法。但是,由于JSON近年来变得如此流行,他们决定在PHP 5.2中添加对它的支持。现在你也可以使用 和 函数:json_encode()json_decode()// a complex array $myvar = array('hello', 42, array(1,'two'), 'apple');// convert to a string $string = json_encode($myvar);echo $string;/* prints ["hello",42,[1,"two"],"apple"] */// you can reproduce the original variable $newvar = json_decode($string);print_r($newvar);/* prints Array ( [0] => hello [1] => 42 [2] => Array ( [0] => 1 [1] => two ) [3] => apple ) */它更紧凑,最重要的是,与javascript和许多其他语言兼容。但是,对于复杂对象,某些信息可能会丢失。8. 压缩字符串在谈论压缩时,我们通常会考虑文件,例如 ZIP 存档。可以在PHP中压缩长字符串,而不涉及任何存档文件。在下面的示例中,我们将使用 gzcompress() 和 gzuncompress() 函数:$string ="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc ut elit id mi ultricies adipiscing. Nulla facilisi. Praesent pulvinar, sapien vel feugiat vestibulum, nulla dui pretium orci, non ultricies elit lacus quis ante. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam pretium ullamcorper urna quis iaculis. Etiam ac massa sed turpis tempor luctus. Curabitur sed nibh eu elit mollis congue. Praesent ipsum diam, consectetur vitae ornare a, aliquam a nunc. In id magna pellentesque tellus posuere adipiscing. Sed non mi metus, at lacinia augue. Sed magna nisi, ornare in mollis in, mollis sed nunc. Etiam at justo in leo congue mollis. Nullam in neque eget metus hendrerit scelerisque eu non enim. Ut malesuada lacus eu nulla bibendum id euismod urna sodales. ";$compressed = gzcompress($string);echo "Original size: ". strlen($string)."\n";/* prints Original size: 800 */echo "Compressed size: ". strlen($compressed)."\n";/* prints Compressed size: 418 */// getting it back $original = gzuncompress($compressed);我们能够实现近 50% 的尺寸减小。此外,函数 gzencode() 和 gzdecode() 通过使用不同的压缩算法获得了类似的结果。9. 寄存器关断功能有一个名为 register_shutdown_function() 的函数,它可以让您在脚本完成运行之前立即执行一些代码。假设您希望在脚本执行结束时捕获一些基准统计信息,例如运行所需的时间:// capture the start time $start_time = microtime(true);// do some stuff // ... // display how long the script took echo "execution took: ". (microtime(true) - $start_time). " seconds.";乍一看,这似乎微不足道。您只需将代码添加到脚本的最底部,它就会在完成之前运行。但是,如果您调用 exit() 函数,该代码将永远不会运行。此外,如果出现致命错误,或者脚本被用户终止(通过按浏览器中的“停止”按钮),它可能不会再次运行。使用 register_shutdown_function() 时,无论脚本停止运行的原因,代码都将执行:$start_time = microtime(true);register_shutdown_function('my_shutdown');// do some stuff // ... function my_shutdown() {global $start_time; echo "execution took: ". (microtime(true) - $start_time). " seconds.";}
2023年08月04日
17 阅读
0 评论
0 点赞
2023-08-04
PHP语法小记(一)
1,花括号“{}”可以像“[]”操作数组一样操作字符串,来获得指定位置的字符。2,PHP标签“<?php ?>”在独立PHP脚本内可以不写结束标签,这是为了避免意外的空格导致输出而报错。可以用注释来标明脚本结束。3,echo是语法结构,不是函数。后面跟多个字符串时用逗号“,”效率更好。4,数组中,1、'1'、true为索引的时候都会强制转换为1。而'01'不会进行转换,会按照字符串处理。5,将一个类的代码写在不同PHP标签内是不合法的,会报语法错误。而函数则没问题。6,session与cookie的区别与关系。session保存在服务器上,cookie保存在客户浏览器上;session保存可以是硬盘上的文件、数据库、memcached,cookie可以保存到硬盘(持久cookie)和内存里(会话cookie);session_id传递方式有两种,一是cookie,二是get方式(可以通过 session.name 配置项来指定保存session_id的变量名称)。7,获得当前时间戳用$_SERVER['REQUEST_TIME']代替time(),可以减少一次函数调用,效率更高。8,检查字符串是否超过某长度可以用isset($str{n})的语法代替strlen()函数,例如:判断$a的长度是否超过5,可以用isset($a{5})来判断。这样效率更高。9,header()函数过后要exit,否则后面代码还会执行。10,大数组用引用传递,减少内存占用,用完就unset()。11,数据库连接在使用的时候才建立,完全用完了记得关闭连接。12,set_time_limit()的局限性。只能限制脚本本身的运行时间,对于外部执行的时间无法控制,例如:system()函数,流操作,数据库查询等。13,abstract和interface的区别:abstract可以有非抽象方法,interface不行;abstract对方法的访问控制可以有protected,而interface必须为public;abstract只能被继承,当然一个类只能继承一个类,而一个类则可以实现多个interface。14,echo,print,print_r,var_dump,var_export的区别:echo,print是语法结构,不是函数,而且只能显示基本类型,不能显示数组和对象,其他都是函数,可以显示数组和对象;echo 可以显示多个变量,用逗号隔开;print_r第二个参数可以决定是输出变量,还是将变量作为返回值;var_dump会打印变量的详细信息,例如长度和类型,而且可以传递多个变量作为参数;var_export返回的是合法PHP代码格式。15,验证邮箱: filter_var($email, FILTER_VALIDATE_EMAIL);16,获取文件扩展名的方法:一,pathinfo($filename),取extension的值。二,end(explode('.',$filename))。17,文件锁定函数flock——的常量参数。共享锁(读取操作)——LOCK_SH独占锁(写入操作)——LOCK_EX释放锁(无论共享还是独占)——LOCK_UN防堵塞——LOCK_NB可以通过fclose()函数释放锁定操作。18,验证字符串是否是合法IP:不用正则,直接用ip2long(),合法则返回数字,不合法则返回false。19,PHP 5.3开始,可以使用__DIR__来获得当前脚本所在目录,不用再realpath(__FILE__)了。
2023年08月04日
15 阅读
0 评论
0 点赞
2023-08-04
php 利用socket发送GET,POST请求
php 利用socket发送GET,POST请求作为php程序员一定会接触http协议,也只有深入了解http协议,编程水平才会更进一步。最近我一直在学习php的关于http的编程,许多东西恍然大悟,受益匪浅。希望分享给大家。本文需要有一定http基础的开发者阅读。今天给大家带来的是如何利用socket发送GET,POST请求。我借用燕十八老师封装好的一个Http类给进行说明。在日常编程中相信很多人和我一样大部分时间是利用浏览器向服务器提出GET,POST请求,那么可否利用其它方式提出GET,POST请求呢?答案必然是肯定的。了解过HTTP协议的人知道,浏览器提交请求的实质是向服务器发送一个请求信息,这个请求信息有请求行,请求头,请求体(非必须)构成。服务器根据请求信息返回一个响应信息。连接断开。HTTP请求的格式如下所示:1 <request-line> 2 <headers> 3 <blank line> 4 [<request-body>]HTTP响应的格式与请求的格式十分相似:<status-line> <headers> <blank line> [<response-body>]我们可以利用HTTP发送请求的原理,可以重新考虑利用socket发送HTTP请求。Socket的英文原义是“孔”或“插座”。通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。如此看来,其实利用socket操作远程文件和读写本地的文件一样容易,把本地文件看成通过硬件传输,远程文件通过网线传输就行了。因而可以将发送请求的考虑成 建立连接->打开socket接口(fsockopen())->写入请求(fwrite())->读出响应(fread()->关闭文件(fclose())。话不多说,直接上代码: <?php interface Proto { // 连接url function conn($url); //发送get查询 function get(); // 发送post查询 function post(); // 关闭连接 function close(); } class Http implements Proto { const CRLF = "\r\n"; protected $errno = -1; protected $errstr = ''; protected $response = ''; protected $url = null; protected $version = 'HTTP/1.1'; protected $fh = null; protected $line = array(); protected $header = array(); protected $body = array(); public function __construct($url) { $this->conn($url); $this->setHeader('Host: ' . $this->url['host']); } // 此方法负责写请求行 protected function setLine($method) { $this->line[0] = $method . ' ' . $this->url['path'] . '?' .$this->url['query'] . ' '. $this->version; } // 此方法负责写头信息 public function setHeader($headerline) { $this->header[] = $headerline; } // 此方法负责写主体信息 protected function setBody($body) { $this->body[] = http_build_query($body); } // 连接url public function conn($url) { $this->url = parse_url($url); // 判断端口 if(!isset($this->url['port'])) { $this->url['port'] = 80; } // 判断query if(!isset($this->url['query'])) { $this->url['query'] = ''; } $this->fh = fsockopen($this->url['host'],$this->url['port'],$this->errno,$this->errstr,3); } //构造get请求的数据 public function get() { $this->setLine('GET'); $this->request(); return $this->response; } // 构造post查询的数据 public function post($body = array()) { $this->setLine('POST'); // 设计content-type $this->setHeader('Content-type: application/x-www-form-urlencoded'); // 设计主体信息,比GET不一样的地方 $this->setBody($body); // 计算content-length $this->setHeader('Content-length: ' . strlen($this->body[0])); $this->request(); return $this->response; } // 真正请求 public function request() { // 把请求行,头信息,实体信息 放在一个数组里,便于拼接 $req = array_merge($this->line,$this->header,array(''),$this->body,array('')); //print_r($req); $req = implode(self::CRLF,$req); //echo $req; exit; fwrite($this->fh,$req); while(!feof($this->fh)) { $this->response .= fread($this->fh,1024); } $this->close(); // 关闭连接 } // 关闭连接 public function close() { fclose($this->fh); } }利用此类发送一个简单的GET请求:<?php //记得引用Http类 $url="http://home.cnblogs.com/u/DeanChopper/"; $http=new Http($url); $response=$http->get(); print_r($response);返回值为信息,可以对响应信息进行进一步处理,得到自己想得到的内容。
2023年08月04日
14 阅读
0 评论
0 点赞
2023-08-04
浅谈php web安全
浅谈php web安全前言首先,不是web安全的专家,所以这不是web安全方面专家级文章,而是学习笔记、细心总结文章,里面有些是我们phper不易发现或者说不重视的东西。所以笔者写下来方便以后查阅。在大公司肯定有专门的web安全测试员,安全方面不是phper考虑的范围。但是作为一个phper对于安全知识是:“知道有这么一回事,编程时自然有所注意”。概要1、php一些安全配置(1)关闭php提示错误功能(2)关闭一些“坏功能”(3)严格配置文件权限。2、严格的数据验证,你的用户不全是“好”人2.1为了确保程序的安全性,健壮性,数据验证应该包括内容。2.2程序员容易漏掉point或者说需要注意的事项3、防注入3.1简单判断是否有注入漏洞以及原理3.2常见的mysql注入语句(1)不用用户名和密码(2)在不输入密码的情况下,利用某用户(3)猜解某用户密码(4)插入数据时提权(5)更新提权和插入提权同理(6)恶意更新和删除(7)union、join等(8)通配符号%、_(9)还有很多猜测表信息的注入sql33防注入的一些方法2.3.1 php可用于防注入的一些函数和注意事项。2.3.2防注入字符优先级。2.3.3防注入代码(1)参数是数字直接用intval()函数(2)对于非文本参数的过滤(3)文本数据防注入代码。(4)当然还有其他与addslashes、mysql_escape_string结合的代码。4、防止xss攻击4.1Xss攻击过程4.2常见xss攻击地方4.3防XSS方法5、CSRF5.1简单说明CSRF原理5.2防范方法6、防盗链7、防拒CC攻击1、php一些安全配置(1)关闭php提示错误功能在php.ini 中把display_errors改成display_errors = OFF或在php文件前加入error_reporting(0)1)使用error_reporting(0);失败的例子:A文件代码:<? error_reporting(0); echo 555 echo 444; ?>错误:Parse error: parse error, expecting `','' or `';'' in E:\webphp\2.php on line 42)使用error_reporting(0);成功的例子:a文件代码:<?php error_reporting(0); include("b.php"); ?>b文件代码:<?php echo 555 echo 444; ?>这是很多phper说用error_reporting(0)不起作用。第一个例子A.php里面有致命错误,导致不能执行,不能执行服务器则不知有这个功能,所以一样报错。第二个例子中a.php成功执行,那么服务器知道有抑制错误功能,所以就算b.php有错误也抑制了。ps:抑制不了mysql错误。(2)关闭一些“坏功能”1)关闭magic quotes功能在php.ini 把magic_quotes_gpc = OFF避免和addslashes等重复转义2)关闭register_globals = Off在php.ini 把register_globals = OFF在register_globals = ON的情况下地址栏目:http:www.phpben.com?bloger=benwin<?php //$bloger = $_GET['bloger'] //因为register_globals = ON 所以这步不用了直接可以用$bloger echo $bloger; ?>这种情况下会导致一些未初始化的变量很容易被修改,这也许是致命的。所以把register_globals = OFF关掉(3)严格配置文件权限。为相应文件夹分配权限,比如包含上传图片的文件不能有执行权限,只能读取2、严格的数据验证,你的用户不全是“好”人。记得笔者和一个朋友在讨论数据验证的时候,他说了一句话:你不要把你用户个个都想得那么坏!但笔者想说的这个问题不该出现在我们开发情景中,我们要做的是严格验证控制数据流,哪怕10000万用户中有一个是坏用户也足以致命,再说好的用户也有时在数据input框无意输入中文的时,他已经不经意变“坏”了。2.1为了确保程序的安全性,健壮性,数据验证应该包括(1) 关键数据是否存在。如删除数据id是否存在(2) 数据类型是否正确。如删除数据id是否是整数(3) 数据长度。如字段是char(10)类型则要strlen判断数据长度(4) 数据是否有危险字符数据验证有些人主张是把功能完成后再慢慢去写安全验证,也有些是边开发边写验证。笔者偏向后者,这两种笔者都试过,然后发现后者写的验证相对健壮些,主要原因是刚开发时想到的安全问题比较齐全,等开发完功能再写时有两个问题,一个phper急于完成指标草草完事,二是确实漏掉某些point。2.2程序员容易漏掉point或者说需要注意的事项:(1) 进库数据一定要安全验证,笔者在广州某家公司参与一个公司内部系统开发的时候,见过直接把$_POST数据传给类函数classFunctionName($_POST),理由竟然是公司内部使用的,不用那么严格。暂且不说逻辑操作与数据操控耦合高低问题,连判断都没判断的操作是致命的。安全验证必须,没任何理由推脱。(2) 数据长度问题,如数据库建表字段char(25),大多phper考虑到是否为空、数据类型是否正确,却忽略字符长度,忽略还好更多是懒于再去判断长度。(这个更多出现在新手当中,笔者曾经也有这样的思想)(3) 以为前端用js判断验证过了,后台不需要判断验证。这也是致命,要知道伪造一个表单就几分钟的事,js判断只是为了减少用户提交次数从而提高用户体验、减少http请求减少服务器压力,在安全情况下不能防“小人”,当然如果合法用户在js验证控制下是完美的,但作为phper我们不能只有js验证而抛弃再一次安全验证。(4) 缺少对表单某些属性比如select、checkbox、radio、button等的验证,这些属性在web页面上开发者已经设置定其值和值域(白名单值),这些属性值在js验证方面一般不会验证,因为合法用户只有选择权没修改权,然后phper就在后端接受数据处理验证数据的时候不会验证这些数据,这是一个惯性思维,安全问题也就有了,小人一个伪表单足矣致命。(5) 表单相应元素name和数据表的字段名一致,如用户表用户名的字段是user_name,然后表单中的用户名输入框也是user_name ,这和暴库没什么区别。(6) 过滤危险字符方面如防注入下面会独立讲解。3、防注入3.1简单判断是否有注入漏洞以及原理。网址:http:www.phpben.com/benwin.php?id=1 运行正常,sql语句如:select * from phpben where id = 1(1) 网址:http:www.phpben.com/ benwin.php?id=1’ sql语句如:select from phpben where id = 1’ 然后运行异常 这能说明benwin.php文件没有对id的值进行“’” 过滤和intval()整形转换,当然想知道有没有对其他字符如“%”,“/”等都可以用类似的方法穷举测试(很多测试软件使用)(2)网址:http:www.phpben.com/ benwin.php?id=1 and 1=1 则sql语句可能是 select * from phpben where id = 1 and 1=1,运行正常且结果和http:www.phpben.com/benwin.php?id=1结果一样,则说明benwin.php可能没有对空格“ ”、和“and”过滤(这里是可能,所以要看下一点)(3)网址:http:www.phpben.com/ benwin.php?id=1 and 1=2则sql语句可能是 select * from phpben where id = 1 and 1=2 如果运行结果异常说明sql语句中“and 1=2”起作用,所以能3个条件都满足都则很确定的benwin.php存在注入漏洞。ps:这里用get方法验证,post也可以,只要把值按上面的输入,可以一一验证。这说明3.2常见的mysql注入语句。(1)不用用户名和密码//正常语句 $sql ="select * from phpben where user_name='admin' and pwd ='123'"; //在用户名框输入’or’=’or’或 ’or 1=’1 然后sql如下 $sql ="select * from phpben where user_name=' 'or'='or'' and pwd ='' "; $sql ="select * from phpben where user_name=' 'or 1='1' and pwd ='' ";这样不用输入密码。话说笔者见到登录框都有尝试的冲动。(2)在不输入密码的情况下,利用某用户。//正常语句 $sql ="select * from phpben where user_name='$username' and pwd ='$pwd'"; //利用的用户名是benwin 则用户名框输入benwin’# 密码有无都可,则$sql变成 $sql ="select * from phpben where user_name=' benwin'#' and pwd ='$pwd'";这是因为mysql中其中的一个注悉是“#”,上面语句中#已经把后面的内容给注悉掉,所以密码可以不输入或任意输入。网上有些人介绍说用“/”来注悉,笔者想提的是只有开始注悉没结束注悉“/”时,mysql会报错,也不是说“/*/”不能注悉,而是这里很难添加上“/”来结束注悉,还有“-- ”也是可以注悉mysql 但要注意“--”后至少有一个空格也就是“-- ”,当然防注入代码要把三种都考虑进来,值得一提的是很多防注入代码中没把“-- ”考虑进防注入范围。(3)猜解某用户密码//正常语句 $sql ="select * from phpben.com where user_name='$username' and pwd ='$pwd'"; //在密码输入框中输入“benwin’ and left(pwd,1)='p'#”,则$sql是 $sql ="select * from phpben.com where user_name=' benwin' and left(pwd,1)='p'#' and pwd ='$pwd'";如果运行正常则密码的密码第一个字符是p,同理猜解剩下字符。(4)插入数据时提权//正常语句,等级为1 $sql = "insert into phpben.com (`user_name`,`pwd`,`level`) values(‘benwin','iampwd',1) "; //通过修改密码字符串把语句变成 $sql = "insert into phpben.com (`user_name`,`pwd`,`level`) values(‘benwin','iampwd',5)#',1) "; $sql = "insert into phpben.com (`user_name`,`pwd`,`level`) values(‘benwin','iampwd',5)-- ',1) ";这样就把一个权限为1的用户提权到等级5(5)更新提权和插入提权同理//正常语句 $sql = "update phpben set `user_name` ='benwin', level=1"; //通过输入用户名值最终得到的$sql $sql = "update phpben set `user_name` ='benwin',level=5#', level=1"; $sql = "update phpben set `user_name` ='benwin',level=5-- ', level=1";(6)恶意更新和删除//正常语句 $sql = "update phpben set `user_name` = ‘benwin' where id =1"; //注入后,恶意代码是“1 or id>0” $sql = "update phpben set `user_name` = ‘benwin' where id =1 or id>0"; //正常语句 $sql = "update phpben set `user_name` =’benwin’ where id=1"; //注入后 $sql = "update phpben set `user_name` ='benwin' where id>0#' where id=1"; $sql = "update phpben set `user_name` ='benwin' where id>0-- ' where id=1";(7)union、join等//正常语句 $sql ="select * from phpben1 where `user_name`=’benwin’ "; //注入后 $sql ="select * from phpben1 where`user_name`=’benwin’ uninon select * from phpben2#’ "; $sql ="select * from phpben1 where`user_name`=’benwin’ left join……#’ ";(8)通配符号%、_//正常语句 $sql ="select * from phpben where `user_name`=’benwin’ "; //注入通配符号%匹配多个字符,而一个_匹配一个字符,如__则匹配两个字符 $sql ="select * from phpben where `user_name` like ’%b’ "; $sql ="select * from phpben where `user_name` like ’_b_’ ";这样只要有一个用户名字是b开头的都能正常运行,“ _b_”是匹配三个字符,且这三个字符中间一个字符时b。这也是为什么有关addslashes()函数介绍时提示注意没有转义%和_(其实这个是很多phper不知问什么要过滤%和_下划线,只是一味的跟着网上代码走)(9)还有很多猜测表信息的注入sql//正常语句 $sql ="select * from phpben1 where`user_name`='benwin'"; //猜表名,运行正常则说明存在phpben2表 $sql ="select * from phpben1 where`user_name`='benwin' and (select count(*) from phpben2 )>0#' "; //猜表字段,运行正常则说明phpben2表中有字段colum1 $sql ="select * from phpben1 where`user_name`='benwin' and (select count(colum1) from phpben2 )>0#'"; //猜字段值 $sql ="select * from phpben1 where`user_name`='benwin' and left(pwd,1)='p'#’'";当然还有很多,笔者也没研究到专业人士那种水平,这里提出这些都是比较常见的,也是phper应该知道并掌握的,而不是一味的在网上复制粘贴一些防注入代码,知然而不解其然。下面一些防注入方法回看可能更容易理解。3.3防注入的一些方法3.3.1 php可用于防注入的一些函数和注意事项。(1)addslashes 和stripslashes。Addslashes给这些 “’”、“””、“\”,“NULL” 添加斜杆“\’”、“\””、“\”,“\NULL”, stripslashes则相反,这里要注意的是php.ini是否开启了magic_quotes_gpc=ON,开启若使用addslashes会出现重复。所以使用的时候要先get_magic_quotes_gpc()检查一般代码类似:if(!get_magic_quotes_gpc()) { $abc = addslashes($abc); }其实这个稍微学习php一下的人都知道了,只不过笔者想系统点介绍(前面都说不是专家级文章),所以也顺便写上了。addslashes(2)mysql_escape_string()和mysql_ real _escape_string()mysql_real_escape_string 必须在(PHP 4 >= 4.3.0, PHP 5)的情况下才能使用。否则只能用mysql_escape_stringif (PHP_VERSION >= '4.3') { $string = mysql_real_escape_string($string); }else { $string = mysql_escape_string($string ); }mysql_escape_string()和mysql_ real _escape_string()却别在于后者会判断当前数据库连接字符集,换句话说在没有连接数据库的前提下会出现类似错误:Warning: mysql_real_escape_string() [function.mysql-real-escape-string]: Access denied for user 'ODBC'@'localhost' (using password: NO) in E:\webphp\test.php on line 11(3)字符代替函数和匹配函数str_replace() 、perg_replace()这些函数之所以也在这里提是因为这些函数可以用于过滤或替代一些敏感、致命的字符。3.3.2防注入字符优先级。防注入则要先知道有哪些注入字符或关键字,常见的mysql注入字符有字符界定符号如“'”、“"”;逻辑关键字如“and”、“or”;mysql注悉字符如“#”,“-- ”,“/*/”;mysql通配符“%”,“_”;mysql关键字“select|insert|update|delete||union|join|into|load_file|outfile”(1)对于一些有规定格式的参数来说,防注入优先级最高的是空格” ”。如一些银行卡号,身份证号,邮箱,电话号码,,生日,邮政编码等这些有自己规定的格式且格式规定不能有空格符号的参数,在过滤的时候一般最先过滤掉空格(包括一些空格“变种”),因为其他字符界定符号,逻辑关键字,mysql注悉,注意下图可以看出重要的是“'”,“ ”ps:空格字符的变种有:“%20”,“\n”,“\r”,“\r\n”,“\n\r”,“chr("32")” 这也是为什么mysql_escape_string()和mysql_real_escape_string() 两个函数转义“\n”,“\r”。其实很多phper只知道转义\n,\r而不知原因,在mysql解析\n,\r时把它们当成空格处理,笔者测试验证过,这里就不贴代码了。(2)“and”,“or”,“\”,“#”,“-- ”逻辑关键可以组合很多注入代码;mysql注悉则把固有sql代码后面的字符全部给注悉掉从而让注入后的sql语句能正常运行;“\”也是能组合很多注入字符\x00,\x1a。ps:sql解析“#”,“-- ”是大多数mysql防注入代码没有考虑到的,也是很多phper忽略。还有因为一些phper给参数赋值的时候会有用“-”来隔开,所以笔者建议不要这样写参数,当然也可以再过滤参数的时候“-- ”(注意有空格的,没空格不解析为注悉)当一个整体过滤而不是过滤“-” ,这样就避免过多过滤参数。(3)“null”,“%”,“_”这几个不能独立,都不要在特定情况下,比如通配字符“%,_”都要在mysql like子句的前提下。所以“%”,“_”的过滤一般在搜索相关才过滤,不能把它们纳入通常过滤队列,因为有些如邮箱就可以有”_”字符(4)关键字“select|insert|update|delete|*|union|join|into|load_file|outfile”也许你会问怎么这些重要关键字却优先级这么低。笔者想说的是因为这些关键字在没有“'”,“"”,“”,“and”,“or”等情况下购不成伤害。换句话说这些关键字不够“独立”,“依赖性”特别大。当然优先级低,不代表不要过滤。3.3.3防注入代码。(1)参数是数字直接用intval()函数注意:现在很多网上流行的防注入代码都只是只是用addslashes()、mysql_escape_string()、mysql_real_escape_string()或三者任意组合过滤,但phper以为过滤了,一不小心一样有漏洞,那就是在参数为数字的时候:$id = addslashes($_POST['id']); //正确是$id = intval($_POST['id']); $sql =" select * from phpben.com where id =$id"; $sql =" select * from phpben.com where id =1 or 1=1";对比容易发现,post过来的数据通过addslashes过滤后的确很多注入已经不起作用,但是$id并没有intval,导致漏洞的存在,这是个小细节,不小心则导致漏洞。(2)对于非文本参数的过滤文本参数是指标题、留言、内容等可能有“’”,“’”等内容,过滤时不可能全部转义或代替。但非文本数据可以。function _str_replace($str ) { $str = str_replace(" ","",$str); $str = str_replace("\n","",$str); $str = str_replace("\r","",$str); $str = str_replace("'","",$str); $str = str_replace('"',"",$str); $str = str_replace("or","",$str); $str = str_replace("and","",$str); $str = str_replace("#","",$str); $str = str_replace("\\","",$str); $str = str_replace("-- ","",$str); $str = str_replace("null","",$str); $str = str_replace("%","",$str); //$str = str_replace("_","",$str); $str = str_replace(">","",$str); $str = str_replace("<","",$str); $str = str_replace("=","",$str); $str = str_replace("char","",$str); $str = str_replace("declare","",$str); $str = str_replace("select","",$str); $str = str_replace("create","",$str); $str = str_replace("delete","",$str); $str = str_replace("insert","",$str); $str = str_replace("execute","",$str); $str = str_replace("update","",$str); $str = str_replace("count","",$str); return $str; }ps:还有一些从列表页操作过来的一般href是”phpben.php?action=delete&id=1”,这时候就注意啦,_str_replace($_GET['action'])会把参数过滤掉,笔者一般不用敏感关键作为参数,比如delete会写成del,update写成edite,只要不影响可读性即可;还有上面代码过滤下划线的笔者注悉掉了,因为有些参数可以使用下划线,自己权衡怎么过滤;有些代码把关键字当重点过滤对象,其实关键字的str_replace很容易“蒙过关”,str_replace(“ininsertsert”)过滤后的字符还是insert,所以关键的是其他字符而不是mysql关键字。(3)文本数据防注入代码。文本参数是指标题、留言、内容等这些数据不可能也用str_replace()过滤掉,这样就导致数据的完整性,这是很不可取的。代码:function no_inject($str) { if(is_array($str)) { foreach($str as $key =>$val) { $str[$key]=no_inject($val); } }else { //把一些敏感关键字的第一个字母代替掉,如or 则用"or"代替 $str = str_replace(" "," ",$str); $str = str_replace("\\","\",$str); $str = str_replace("'"," ' ",$str); $str = str_replace('"'," " ",$str); $str = str_replace("or"," o r",$str); $str = str_replace("and"," and",$str); $str = str_replace("#","# ",$str); $str = str_replace("-- ","-- ",$str); $str = str_replace("null","null",$str); $str = str_replace("%","%",$str); //$str = str_replace("_","_",$str); $str = str_replace(">","͸",$str); $str = str_replace("<",">",$str); $str = str_replace("=","=",$str); $str = str_replace("char","char",$str); $str = str_replace("declare","declare",$str); $str = str_replace("select","select",$str); $str = str_replace("create","create",$str); $str = str_replace("delete","delete",$str); $str = str_replace("insert","insert",$str); $str = str_replace("execute","execute",$str); $str = str_replace("update","update",$str); $str = str_replace("count","count",$str); } return $str; }(4)当然还有其他与addslashes、mysql_escape_string结合的代码。防注入的代码其实来来去去都是那些组合,然后根据自己程序代码变通,笔者这些代码也是没考虑全的,不如cookes、session、request都没全过滤。重要是知道其中原理,为什么过滤这些字符,字符有什么危害。当然还有一些笔者没考虑也没能力考虑到的方面比如还有哪些关键字之类,欢迎mailto:chen_bin_wen@163.com/445235728@qq.com4、防止xss攻击XSS:cross site script 跨站脚本,为什么不叫css,为了不和div+css混淆。4.1Xss攻击过程:(1)发现A站有xss漏洞。(2)注入xss漏洞代码。可以js代码,木马,脚本文件等等,这里假如A站的benwin.php这个文件有漏洞。(3)通过一些方法欺骗A站相关人员运行benwin.php,其中利用相关人员一些会员信息如cookies,权限等。相关人员:管理员(如贴吧版主),管理员一般有一定权限。目的是借用管理员的权限或进行提权,添或加管理员,或添加后门,或上传木马,或进一步渗透等相关操作。A站会员:会员运行A站的benwin.php。目的一般是偷取会员在A站的信息资料。方法:1) 在A站发诱骗相关人到benwin.php的信息,比如网址,这种是本地诱骗2) 在其他网站发诱骗信息或者发邮件等等信息。一般通过伪装网址骗取A站相关人员点击进benwin.php(4)第三步一般已经是一次xss攻击,如果要更进一步攻击,那不断重复执行(2)、(3)步以达到目的。简单例说xss攻击代码:benwin.php文件<html> <head> <title>简单xss攻击例子</title></head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <dody> <form action="phpben.com?user_name=<?php echo $user_name; ?>"> <input type="submit" value="提交" > </form> </body> </html>当用户名$user_name的值是“benwin" onSubmit="alert('这是xss攻击的例子');" class= "”(这里)<form action="phpben.com?user_name=benwin" onSubmit="alert('这是xss攻击的例子');" class= "" > <input type="submit" value="提交" > </form>当提交表单的时候就会弹出提示框。(1) 很明显$user_name在保存进数据库的时候没有过滤xss字符(和防注入很像,这里举例说明)==>发现漏洞(2) 构造xss代码:benwin" onSubmit="alert('这是xss攻击的例子');" class= "" 传入数据库(3) 骗相关人员进来点击“提交”按钮4.2常见xss攻击地方(1)Js地方<script language="javascript"> var testname =" <?php echo $testname;?>"; </script>$testname的值只要符合js闭合关系:“";alert("test xss ");”(以下同理)(2)form表单里面<input type="text" name="##" value="<?php echo $val; ?>" />(3)a标签<a href="benwin.php?id= <?php echo $id; ?>">a标签可以隐藏xss攻击</a>(4)用得很多的img标签<img src="<?php echo $picPath; ?>" />甚至一些文本中插入整个img标签并且用width、 height、css等隐藏的很隐蔽(5)地址栏目总之,有输出数据的地方,更准确的说是有输出用户提交的数据的地方,都有可能是XSS攻击的地方。4.3防XSS方法防xss方法其实和防注入很相似,都是一些过滤、代替、实体化等方法(1)过滤或移除特殊的Html标签。例如:< 、>、<,、> ’、”、<script>、 <iframe> 、<,、>、"(2)过滤触发JavaScript 事件的标签。例如 onload、onclick、onfocus、onblur、onmouseover等等。(3)php一些相关函数,strip_tags()、htmlspecialchars()、htmlentities()等函数可以起作用5、CSRFCSRF跨站请求伪造cross site request forgery。5.1简单说明CSRF原理(1)A登录Site1(如现在网民常上的淘宝、微博、QQ等),产生一些信息,session、cookies等等,且一直保持没退出。(2)A再登录Site2(如一些成人网等,至于怎么跑到Site2,多数是Site通过些手段,邮件欺骗等),打开site2的浏览器和打开site1的一样,否则无效(3)Site2站中伪造了Site1的http请求(如修改密码,买东西,转账等),Site1的服务器误以为A在site1的正常操作(因为同浏览器且A还没登出),然后就运行了请求,那么csrf已成功操作。csrf和xss很相似。笔者开始也一时混淆,一位热心网友提醒下,修改csrf和xss的区别xss:注重于修改、获取页面元素和值,而且可以执行任意html代码或javascript,能执行代码那肯定能偷取cookies等网站信息csrf:不能修改、获取页面元素和值,而且可以执行任意html代码或javascript,注重利用客户已在安全网站(比如淘宝)登录且产生给非安全网站利用的cookies等信息,然后用户在非安全网站点击连接等一些触发非安全网站向安全网站发送请求。伪造的请求可以很多方面,发邮件、改密码、返回用户信息、交易等等,所以相对与xss攻击来说csrf危害更严重。5.2防范方法。对于phper(1)严密操控执行入口执行一些敏感操作比如改密码这些操作前判断请求来源,只有本站服务器发的请求才可以执行。判断方法可以判断ip来源。非本站服务器ip不会执行。(2)本站有外链的话做些必要操作一般site2的hacker会在site1(比如论坛里)里发欺骗连接,因为在site1诱骗的相关人员一般都登录site1了,满足csrf气体条件之一。如当你点击QQ邮件里面的长外链时候,回跳转到一个页面提示“有风险”之类,这样不仅可以减低跳出率,一些不懂的人看到这样的提示,若不是非必要而是处于好奇点击的连接一般不会继续点击访问;还有是QQ邮件正文里的图片在加载内容时是不加载图片的,要点击“显示图片”按钮才显示图片,这里一个原因之一就是避免攻击。当然对于用户体验来说这是不可取的,可以优化的是判断到一些网址(如QQ本身网址)是安全直接可以显示(不用提示),而可疑的才提示或禁止。(3)防止csrf也可以用防xss的方法。6、防盗链盗链问题增加服务器的负担。盗链就是盗链网站盗取被盗链网站资源来实现一些功能。盗链方面主要是图片、视频、以及其他资源下载文件。方法:判断ip,只有本站服务器才能使用站点资源,否则不能使用。代码:(1)在Apache htaccess添加RewriteEngine on RewriteCond %{HTTP_REFERER} !^$ [NC] RewriteCond %{HTTP_REFERER} !phpben.com [NC] RewriteCond %{HTTP_REFERER} !google.com [NC] RewriteCond %{HTTP_REFERER} !baidu.com [NC] RewriteCond %{HTTP_REFERER} !zhuaxia.com [NC] RewriteRule .(jpg|gif|png|bmp|swf|jpeg) /image/replace.gif [R,NC,L] RewriteRule ^(.*)$ http:\/\/phpben.com\/image\/$1 [L]这样,凡是不是phpben.com google.com baidu.com zhuaxia.com 域名请求的都返回replace.gif代替返回7、防CC攻击CC攻击:是利用不断对网站发送连接请求致使形成拒绝服务的目的。详细百度百科:http://baike.baidu.com/view/662394.htm代码:session_start(); $ll_nowtime = $timestamp ; if (session_is_registered('ll_lasttime')){ $ll_lasttime = $_SESSION['ll_lasttime']; $ll_times = $_SESSION['ll_times'] + 1; $_SESSION['ll_times'] = $ll_times; }else{ $ll_lasttime = $ll_nowtime; $ll_times = 1; $_SESSION['ll_times'] = $ll_times; $_SESSION['ll_lasttime'] = $ll_lasttime; } if (($ll_nowtime - $ll_lasttime)<3){ if ($ll_times>=5){ header(sprintf("Location: %s",'http://127.0.0.1')); exit; } }else{ $ll_times = 0; $_SESSION['ll_lasttime'] = $ll_nowtime; $_SESSION['ll_times'] = $ll_times; }
2023年08月04日
15 阅读
0 评论
0 点赞
2023-08-04
让PHP更快的提供文件下载
让PHP更快的提供文件下载一般来说, 我们可以通过直接让URL指向一个位于Document Root下面的文件, 来引导用户下载文件.但是, 这样做, 就没办法做一些统计, 权限检查, 等等的工作. 于是, 很多时候, 我们采用让PHP来做转发, 为用户提供文件下载.<?php$file = "/tmp/dummy.tar.gz"; header("Content-type: application/octet-stream"); header('Content-Disposition: attachment; filename="' . basename($file) . '"'); header("Content-Length: ". filesize($file)); readfile($file);但是这个有一个问题, 就是如果文件是中文名的话, 有的用户可能下载后的文件名是乱码.于是, 我们做一下修改(参考: :<?php$file = "/tmp/中文名.tar.gz"; $filename = basename($file); header("Content-type: application/octet-stream"); //处理中文文件名 $ua = $_SERVER["HTTP_USER_AGENT"]; $encoded_filename = urlencode($filename); $encoded_filename = str_replace("+", "%20", $encoded_filename); if (preg_match("/MSIE/", $ua)) { header('Content-Disposition: attachment; filename="' . $encoded_filename . '"'); } else if (preg_match("/Firefox/", $ua)) { header("Content-Disposition: attachment; filename*=\"utf8''" . $filename . '"'); } else { header('Content-Disposition: attachment; filename="' . $filename . '"'); } header('Content-Disposition: attachment; filename="' . $filename . '"'); header("Content-Length: ". filesize($file)); readfile($file);恩, 现在看起来好多了, 不过还有一个问题, 那就是readfile, 虽然PHP的readfile尝试实现的尽量高效, 不占用PHP本身的内存, 但是实际上它还是需要采用MMAP(如果支持), 或者是一个固定的buffer去循环读取文件, 直接输出.输出的时候, 如果是Apache + PHP mod, 那么还需要发送到Apache的输出缓冲区. 最后才发送给用户. 而对于Nginx + fpm如果他们分开部署的话, 那还会带来额外的网络IO.那么, 能不能不经过PHP这层, 直接让Webserver直接把文件发送给用户呢?今天, 我看到了一个有意思的文章: How I PHP: X-SendFile.我们可以使用Apache的module mod_xsendfile, 让Apache直接发送这个文件给用户:<?php$file = "/tmp/中文名.tar.gz"; $filename = basename($file); header("Content-type: application/octet-stream"); //处理中文文件名 $ua = $_SERVER["HTTP_USER_AGENT"]; $encoded_filename = urlencode($filename); $encoded_filename = str_replace("+", "%20", $encoded_filename); if (preg_match("/MSIE/", $ua)) { header('Content-Disposition: attachment; filename="' . $encoded_filename . '"'); } else if (preg_match("/Firefox/", $ua)) { header("Content-Disposition: attachment; filename*=\"utf8''" . $filename . '"'); } else { header('Content-Disposition: attachment; filename="' . $filename . '"'); } header('Content-Disposition: attachment; filename="' . basename($file) . '"'); //让Xsendfile发送文件 header("X-Sendfile: $file");X-Sendfile头将被Apache处理, 并且把响应的文件直接发送给Client.Lighttpd和Nginx也有类似的模块, 大家有兴趣的可以去找找看 :)
2023年08月04日
22 阅读
0 评论
0 点赞
1
...
139
140
141
...
158