最有效的方式显示1x1像素的GIF(跟踪像素,网络信标)

22
我正在构建一个基本的分析服务,理论上基于Google Analytics的工作原理,但是我不是请求实际图像,而是将图像请求路由到接受数据然后输出图像的脚本。因为浏览器将在每次加载时请求此图像,所以每毫秒都很重要。
我正在寻找一种最有效的方式,让文件从PHP脚本输出gif文件。到目前为止,我已经确定了3种主要方法。
是否有更有效的方法可以从PHP脚本中输出1x1 GIF文件?如果没有,其中哪一个是最有效和可扩展的?
三种确定的方法
PHP图像构建库
$im = imagecreatetruecolor(1, 1);
imagefilledrectangle($im, 0, 0, 0, 0, 0xFb6b6F);
header('Content-Type: image/gif');
imagegif($im);
imagedestroy($im);

file_get_contents函数从服务器获取图像并输出它。

$im = file_get_contents('raw.gif'); 
header('Content-Type: image/gif'); 
echo $im; 

base64_decode the image

header('Content-Type: image/gif');
echo base64_decode("R0lGODdhAQABAIAAAPxqbAAAACwAAAAAAQABAAACAkQBADs=");

我的直觉是base64会更快,但我不知道这个函数需要多少资源;而且file_get_contents可能扩展性不太好,因为它增加了另一个文件系统动作。
参考一下,我正在使用的GIF图像在这里:http://i.stack.imgur.com/LQ1CR.gif 编辑
所以,我提供这张图片的原因是,我的分析库构建一个查询字符串并将其附加到此图像请求中。我不想解析日志,所以将请求路由到一个处理数据并响应图像的PHP脚本中,以便最终用户的浏览器不会挂起或抛出错误。我的问题是,在脚本的限制下,如何最好地服务于那个图像?

1
如果您的目的是提供一个1x1像素的GIF,请让您的Web服务器静态地提供它。如果您实际上想提供一个更大、动态构建的图像,您应该使用代表性数据进行基准测试。 - Thomas
@Savetheinternet 很难模拟数百/数千个查询的效果,但是可能需要进行一些基准测试。我还在问是否还有其他可能更好的方法。 - Yahel
@Thomas,1x1像素的图片只是为了避免请求挂起;我将分析信息附加到请求的查询字符串中,并以此作为我的数据收集基础。实际上,我并没有在页面中使用这个图片。这是Google Analytics使用的方法,比其他选项(即隐藏的iframe)更优雅和高效。 - Yahel
@yc:嗯,你仍然可以通过Web服务器提供服务,并在后台分析日志。 - zerkms
1
哦,我明白了。我以为你在画图表或其他什么东西。没关系! - Thomas
@yc - 你不能直接使用Apache重写吗?将这个1x1像素的GIF访问记录到一个专用的Apache日志文件中,稍后可以解析它。 - ajreal
5个回答

40

可能

header('Content-Type: image/gif');
//equivalent to readfile('pixel.gif')
echo "\x47\x49\x46\x38\x37\x61\x1\x0\x1\x0\x80\x0\x0\xfc\x6a\x6c\x0\x0\x0\x2c\x0\x0\x0\x0\x1\x0\x1\x0\x0\x2\x2\x44\x1\x0\x3b";

这将输出一个二进制字符串,与1x1透明gif文件的二进制内容完全相同。我认为这是效率高的,因为它不执行任何缓慢的IO操作,例如读取文件,也不调用任何函数。

如果您想创建自己的上述十六进制字符串版本,也许是为了更改颜色,您可以使用此代码生成php代码以进行echo语句。

printf('echo "%s";', preg_replace_callback('/./s', function ($matches) {
    return '\x' . dechex(ord($matches[0]));
}, file_get_contents('https://upload.wikimedia.org/wikipedia/en/d/d0/Clear.gif')));

1
为什么它应该是最佳解决方案?这个字符串是什么?请改进这个答案... - nulll
优秀的解决方案。一个小问题是生成的gif不是透明的...我如何从现有的gif文件中制作这样的字符串? - RonaldPK
1
在PHP中,"\x1"是否等同于"\x01"?我正在尝试将此像素适配到Python上,但似乎不支持单个十六进制代码。 - Carl G
1
@CarlG 是的。只是检查以确保。文档明确说明在“x”后面可以有1或2个字符。 - mpen

1
使用Laravel:
$pixel = "\x47\x49\x46\x38\x39\x61\x1\x0\x1\x0\x80\x0\x0\xff\xff\xff\x0\x0\x0\x21\xf9\x4\x1\x0\x0\x0\x0\x2c\x0\x0\x0\x0\x1\x0\x1\x0\x0\x2\x2\x44\x1\x0\x3b";
return response($pixel,200,[
    'Content-Type' => 'image/gif',
    'Content-Length' => strlen($pixel),
]);

如果有人出于某种原因需要这样做。
或者,如果您不喜欢在代码中使用较长的十六进制字符串:
base64_decode('R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw')

1
就我所做的速度测试而言,base64_decode 的速度比编码后的十六进制字符串慢了5倍。 - artfulrobot

1
   header('Content-Type: image/gif'); 
   header("Content-Length: " . filesize("image.gif"));
   $f = fopen('image.gif', 'rb');
   fpassthru($f);
   fclose($f);

可能从磁盘读取图像是最快的方式,但是(特别是如果您正在使用字节码缓存),对于预先知道的小图像,我认为使用base64方式会更快。发送Content-Length也是一个好主意,对于小图像,浏览器在接收到字节后大多数情况下不会等待任何东西,因此虽然您的服务器需要花费同样的时间,但使用体验会稍微好一些。

另一种方法是让Apache/lighttpd/nginx提供图像服务,记录访问并离线解析。


1
嗯,添加Content-Length会导致整体加载时间减少10%。对此点赞。 :) - Yahel
考虑记录路径。如果这样做,我可能会切换到 S3以提高性能并使用他们的日志记录。 - Yahel

0

为什么不直接重定向到静态图片,而是动态生成/输出图片呢?

<?php
// process query param stuff

header('Location: pixel.gif');
exit();
?>

6
这肯定是一个选项。在测试之前,我的直觉告诉我这可能不太有效率。这会强制浏览器发起第二个HTTP请求(因为第一个请求将被302/301重定向)。额外的HTTP请求通常对性能影响很大。 - Yahel

0
我发现这是最有效的1x1像素的gif图像,而且这个图像是透明的。
header('Content-Type: image/gif');
die("\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x90\x00\x00\xff\x00\x00\x00\x00\x00\x21\xf9\x04\x05\x10\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x04\x01\x00\x3b");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接