PHP:从 Web 服务显示图像

4

我正在使用一个外部网络服务,该服务将返回一个图像URL,我将在我的网站上显示该图像。例如:

$url = get_from_web_service();
echo '<img url="'.$url.'" />';

除了要显示100张图片之外,一切正常,但是调用网络服务变得费时并消耗资源。

//the problem
foreach($items as $item) {
   $url = get_from_web_service($item);
   echo '<img url="'.$url.'" />';
}

现在我在考虑两种选项:

//Option1: Using php get_file_contents():
foreach($items as $item)
{
   echo '<img url="url_to_my_website/get_image.php?id='.$item->id.'" />'
}

get_image.php :

$url = get_from_web_service($id);
header("Content-Type: image/png");
echo file_get_contents($url);


//Option2: Using ajax:

echo '<img scr="dummy_image_or_website_logo" data-id="123" />';

//ajax call to the web service to get the id=123 and get the url then add the src attribute to that image.

想法

  1. 第一种选择似乎更为直接,但我的服务器可能会超载并参与每个图像请求。
  2. 第二种选择完全由浏览器和Web服务完成,因此我的服务器根本不涉及。但是对于每个图像,我都要进行两次调用,即一个ajax调用以获取图像URL,另一个调用以获取图像。因此,加载时间可能会有所不同,并且大量调用可能会导致ajax调用失败。

信息

  1. 该页面将显示约50张图片。
  2. 该服务将同时被约100个用户使用。
  3. 我无法控制Web服务,因此无法更改其功能,并且它不接受每个调用的多个图像ID。

我的问题

  1. 是否有更好的选择?
  2. 如果没有,我应该遵循哪个选项?最重要的是为什么我应该遵循那个选项?

谢谢


你有没有尝试过“懒加载”扩展?示例 - Drazzi
是的,我已经安装了它,但您认为进行100个Ajax调用和100个图像调用是一个好主意吗?@Drazzi - trrrrrrm
@ra_htial:不,这并不是最好的想法...根本不是。特别是因为您没有设置任何缓存控制头...尝试将图像保存在数据库中,像您的生命取决于它一样缓存它们,即使如此...客户端会通过AJAX调用请求那100个图像?为什么?这对客户端和服务器都很痛苦。 - Elias Van Ootegem
@ra_htial 用户没有浏览您的整个页面,我在下面的回答中已经解释了,至少可以将其减少到不到100。 - Drazzi
@ra_htial,我已经更新了你的悬赏问题答案。请仔细阅读并查看它是否有帮助。如果你有任何问题,请在评论区留言,我会尽快回复你。 - user1846065
9个回答

9

方法一:PHP渲染

优点:

  • Allows for custom headers that're independent of any server software. If you're using something that's not generally cached (like a PHP file with a query string) or are adding this to a package that needs header functionality regardless of server software, this is a very good idea.

  • If you know how to use GD or Imagick, you can easily resize, crop, compress, index, etc. your images to reduce the image file size (sometimes drastically) and make the page load significantly faster.

  • If width and height are passed as variables to the PHP file, the dimensions can be set dynamically:

    <div id="gallery-images">
        <noscript>
            <!-- So that the thumbnail is small for old mobile devices //-->
            <img src="get-image.php?id=123&h=200&w=200" />
        </noscript>
    </div>
    <script type="text/javascript">
        /* Something to create an image element inside of the div.
         * In theory, the browser height and width can be pulled dynamically
         * on page load, which is useful for ensuring that images are no larger
         * than they need to be. Having a function to load the full image
         * if the borwser becomes bigger isn't a bad idea though.
         */
    </script>
    

    This would be incredibly considerate of mobile users on a page that has an image gallery. This is also very considerate of users with limited bandwidth (like almost everyone in Alaska. I say this from personal experience).

  • Allows you to easily clear the EXIF data of images if they're uploaded by users on the website. This is important for user privacy as well as making sure there aren't any malicious scripts living in your JPGs.

  • Gives potential to dynamically create a large image sprite and drastically reduce your HTTP requests if they're causing latency. It'd be a lot of work so this isn't a very strong pro, but it's still something you can do using this method that you can't do using the second method.

缺点:

  • 根据图片的数量和大小,这可能会对您的服务器造成很大的压力。当与浏览器缓存一起使用时,动态图像是从缓存中提取而不是重新生成的,但机器人仍然很容易被提供动态图像多次。

  • 它需要了解HTTP标头、基本的图像处理技能以及如何在PHP中使用图像处理库才能发挥作用。

方法2:AJAX

优点:

  • 页面将在任何图像之前完成加载。如果您的内容绝对需要尽快加载,并且图像并不重要,则这很重要。

  • 比任何动态PHP解决方案更简单、易于实现且速度显著更快。

  • 它将HTTP请求分隔开,因此初始内容加载更快(由于HTTP请求可以基于浏览器操作而不仅仅是页面加载)。

缺点:

  • 它不会减少HTTP请求的数量,只是将它们分隔开。还要注意,除了所有这些图像之外,至少还有一个额外的外部JS文件。

  • 如果终端设备(如旧移动设备)不支持JavaScript,则不显示任何内容。您唯一可以解决此问题的方法是在某些<noscript>标记之间正常加载所有图像,这需要PHP生成两倍的HTML。

  • 需要您添加loading.gif(和另一个HTTP请求)或请稍候,直到这些图像加载完成文本到您的页面。作为网站用户,我个人认为这很烦人,因为我希望在页面“加载完成”时看到所有内容。

结论:

如果您具有背景知识或时间来学习如何有效地使用方法1,则它具有更大的潜力,因为它允许在页面加载后对您的页面发送的图像和HTTP请求进行操作。

相反,如果您正在寻找一种简单的方法来分隔您的HTTP请求或通过使额外的图像稍后加载使您的内容加载更快,则方法2是您的答案。

回顾方法1和方法2,看起来将两种方法组合使用可能是最好的答案。其中两个缓存和压缩的图像与页面一起加载(一个可见,另一个是缓冲区,以便用户不必每次单击“下一个”时都等待),其余的则逐个加载,视用户情况而定。

在您的特定情况下,如果您的图像可以以“幻灯片”的方式显示,我认为方法2会是最有效的。如果需要一次加载所有图像,请尝试压缩它们并使用方法1应用浏览器缓存。如果页面加载时过多的图像请求破坏了您的速度,请尝试图像精灵技术。

3

目前,您正在联系webservice 100次。您应该更改它,使其仅联系webservice一次并检索所有100个图像的数组,而不是分别检索每个图像。

然后,您可以循环遍历此数组,这将非常快速,因为不需要进一步的webtransactions。


3
假设该服务包括这样的方法,否则接口可以更改... - Kwebble

2

如果从web服务中获取的图像在本质上不是动态的,即不经常更改/修改,我建议在您的服务器上设置定期处理/计划任务,从web服务获取图像并将其存储在本地(在您的服务器上),这样您可以仅从您的服务器上显示图像,并避免每次为最终用户提供网页时需要访问第三方服务器。


1

这两个选项都无法解决您的问题,甚至可能会使情况变得更糟。

对于选项1:

花费最长时间的过程是"get_from_web_service($item)",而且只有在另一个脚本执行时(如果文件"get_image.php"在同一服务器上被执行)该代码才会被执行。

对于选项2:

它只会触发浏览器中的"get-image-resource-request",但您的服务器还需要处理"get_from_web_service($item)"。

必须明确一件事,问题与"get_from_web_service" 的性能有关,直接的建议是提高其性能。另一方面,我们可以减少并发连接的数量。我还没有深入思考这个问题,只有两个建议:

  1. 异步:用户没有浏览整个页面,他们只会注意到页面的顶部。如果您提到的图像不全部显示在顶部,您可以使用jquery.lazyload扩展,它可以使不可见区域的图像资源在可见时再向服务器请求。

  2. CSS Sprites:图像精灵是将多个图像放入单个图像中的集合。如果页面上的图像更改频率不高,您可以编写一些代码每天将它们合并。

  3. 缓存图像:您可以在您的服务器或另一个服务器(最好)缓存您的图像,并进行一些键值对工作:键与$item有关,值为资源目录(url)。

我不是母语为英语的人,希望我清楚地表达并对您有所帮助。


0

正如我们在上面看到的,您正在包含一个URL到提供图像的Web服务中,就在<img>标签的src属性中,因此可以安全地假设这些URL不是机密的。

知道了这一点,以下来自get_image.php的代码片段将以最小的开销工作:

$url = get_from_web_service($id);
header("Location: $url");

如果客户端正在对同一id发起大量的后续请求,您可以通过利用浏览器的内部缓存来减少请求的数量。
header("Cache-Control: private, max-age=$seconds");
header("Expires: ".gmdate('r', time()+$seconds));

否则可以通过Memcached、数据库或普通文件等方式使用服务器端缓存:

is_dir('cache') or mkdir('cache');
$cachedDataFile = "cache/$id";
$cacheExpiryDelay = 3600; // an hour

if (is_file($cachedDataFile) && filesize($cachedDataFile) 
     && filemtime($cachedDataFile) + $cacheExpiryDelay > time()) {
    $url = file_get_contents($cachedDataFile);
} else {
    $url = get_from_web_service($id);
    file_put_contents($cachedDataFile, $url,  LOCK_EX);
}

header("Cache-Control: private, max-age=$cacheExpiryDelay");
header("Expires: ".gmdate('r', time() + $cacheExpiryDelay));
header("Location: $url");

0

我不是专家,但我认为每次回显都需要时间。获取100个图像不应该(仅仅)是一个问题。

此外,或许get_from_web_service($item);应该能够接收一个数组?

$counter = 1;
$urls = array();
foreach($items as $item)
{
   $urls[$counter] = get_from_web_service($item);
   $counter++;
}
// and then you can echo the information?
foreach($urls as $url)
{
   //echo each or use a function to better do it
    //echo '<img url="url_to_my_website/get_image?id='.$url->id.'" />'

}

get_image.php :

$url = get_from_web_service($item);
header("Content-Type: image/png");
echo file_get_contents($url);

最后,如果您可以直接调用就太好了。

get_from_web_service($itemArray); //intake the array and return images 

0

选项一是最佳选择。我还想确保图像在服务器上缓存,以便不需要从原始Web服务器进行多次往返来获取相同的图像。

如果您感兴趣,这是我用于缓存图像等核心代码(请注意,缺少一些内容,例如将相同内容返回给客户端等):

<?php
function error404() {
    header("HTTP/1.0 404 Not Found");
    echo "Page not found.";
    exit;
}

function hexString($md5, $hashLevels=3) {
    $hexString = substr($md5, 0, $hashLevels );
    $folder = "";

    while (strlen($hexString) > 0) {
        $folder =  "$hexString/$folder";
        $hexString = substr($hexString, 0, -1);
    }

    if (!file_exists('cache/' . $folder))
        mkdir('cache/' . $folder, 0777, true);

    return 'cache/' . $folder . $md5;
}

if (!isset($_GET['img']))
    error404();

getFile($_GET['img']);

function getFile($url) {
    // true to enable caching, false to delete cache if already cached
    $cache = true;

    $defaults = array(
        CURLOPT_HEADER => FALSE,
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_FOLLOWLOCATION => 1,
        CURLOPT_MAXCONNECTS => 15,
        CURLOPT_CONNECTTIMEOUT => 30,
        CURLOPT_TIMEOUT => 360,
        CURLOPT_USERAGENT => 'Image Download'
    );

    $ch = curl_init();
    curl_setopt_array($ch, $defaults);
    curl_setopt($ch, CURLOPT_URL, $_GET['img']);

    $key  = hexString(sha1($url));
    if ($cache && file_exists($key)) {
        return file_get_contents($key);
    } elseif (!$cache && file_exists($key)) {
        unlink($key);
    }

    $data = curl_exec($this->_ch);
    $info = curl_getinfo($this->_ch);

    if ($cache === true && $info['http_code'] == 200 && strlen($data) > 20)
        file_put_contents($key, $data);
    elseif ($info['http_code'] != 200)
        error404();

    return $data;
}

$content = getURL($_GET['img']);
if ($content !== null or $content !== false) {
    // Success!
    header("Content-Type: image");
    echo $content;
}

0

两个选项都无法解决服务器资源使用问题。但在这两个选项中,我建议选择第一种。第二种会延迟页面加载,导致网站速度变慢,降低您的SEO评级。

对您来说最好的选择是:

foreach($items as $item) {
    echo '<img url="url_to_my_website/get_image.php?id='.$item->id.'" />'
}

然后魔法发生的地方是 get_image.php

if(file_exists('/path_to_local_storage/image_'.$id.'.png')) {
    $url = '/path_to_images_webfolder/image_'.$id.'.png';
    $img = file_get_contents($url);
} else {
    $url = get_from_web_service($id);
    $img = file_get_contents($url);
    $imgname = end(explode('/', $url));
    file_put_contents($imgname, $img);
}

header("Content-Type: image/png");
echo $img;

这样做的好处是,每张图片只需要向 Web 服务请求一次,然后将其存储在本地空间中。下次请求该图片时,您将从本地空间提供它,跳过向 Web 服务的请求。
当然,前提是图像 ID 是唯一且持久的。
这可能不是最佳解决方案,但对您来说应该可以很好地工作。

0
选项3:缓存对Web服务的请求

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