php runtime 中 headers already sent 问题解决方案

本文涉及的产品
Serverless 应用引擎 SAE,800核*时 1600GiB*时
函数计算FC,每月15万CU 3个月
简介: 本文旨在梳理 Cannot modify header information - headers already sent by (output started at xx 问题的原因,触发条件以及相应的解法

问题

  • 问题1:
    最近有不少用户反馈使用 php runtime的时候遇见如下报错

Cannot modify header information - headers already sent by (output started at ...

  • 问题2:
    如果更改php 的session 目录?

本文旨在梳理此类问题的原因,触发条件以及相应的解法, 在介绍原因之前,先过一遍php runtime http trigger 使用的两种方式。

php runtime 使用FAQ 主目录

php runtime http trigger的使用姿势

使用 php runtime http trigger 的时候中一般有两种使用方式:

不使用 fcPhpCgiProxy

官方文档使用方法,当不需要进行部署较大的php工程的使用方法, 比如:

<?php
    use RingCentral\Psr7\Response;
    function handler($request, $context): Response{
        return new Response(
            200,
            array(
                "custom_header1" => "v1",
                "custom_header2" => ["v2", "v3"],
                "Set-Cookie" => urlencode("test php") . '=' . urlencode('test;more')
            ),
            "hello world"
        );
    }

注意:强烈建议您所有返回的headers都放在构造Response对象的参数里面,如上面示例,不要单独使用能改变header的方法,比如 header 、 setcookie 等

使用 fcPhpCgiProxy

此时的函数入口相当传统Apache/Nginx 的 conf 中 location, 将对应的php 文件送给 php-fpm 去解析,然后将解析后的结果返回给用户

详解fcPhpCgiProxy

具体详情参考

image.png

  • 函数计算的执行环境相当于传统 web 服务的 Apache/Nginx
  • 用户函数相当于实现 Apache/Nginx 的 conf 中 location
  • 用户将 Web 网站部署在 NAS,然后挂载 NAS 到函数的执行环境,

函数计算为用户提供了一个 $GLOBALS['fcPhpCgiProxy'] 对象用来和 php-fpm 进行交互,对
PHP 工程中的 php 文件进行解析,该对象提供了两个重要的接口:

requestPhpCgi($request, $docRoot, $phpFile = "index.php", $fastCgiParams = [], $options = [])
  • requestPhpCgi

    • $request: 跟 php http invoke 入口的参数一致
    • $docRoot: Web 工程的根目录
    • $phpFile: 用于拼接 cgi 参数中的 SCRIPT_FILENAME 的默认参数
    • $fastCgiParams: 函数计算内部尽量根据 $request 给您构造 default cgi params, 但是如果您不是想要的,可以使用$fastCgiParams覆盖一些参数 (reference: cgi)
    • $options: array类型,可选参数, debug_show_cgi_params 设为 true ,会打印每次请求 php 解析时候的 cgi 参数, 默认为 false ;readWriteTimeout 设置解析的时间, 默认为 5 秒

原因

错误出现的原因简单描述就是当php 收到第一个 output (print, echo, )时,它会flush所有收集到的headers。然后它可以发送它想要的所有输出。但是这个时候再发送HTTP header是不允许的。详情可以参考:how-to-fix-headers-already-sent-error-in-php

通常来说就是用户第一种方式(不使用 fcPhpCgiProxy) 使用http trigger的时候,使用如下方法可能会出现以下问题:

解法

  • 不使用 fcPhpCgiProxy

        <?php
    use RingCentral\Psr7\Response;
    function handler($request, $context): Response{
    return new Response(
        200,
        array(
            "custom_header1" => "v1",
            "custom_header2" => ["v2", "v3"],
            "Set-Cookie" => urlencode("test php") . '=' . urlencode('test;more')
        ),
        "hello world"
    );
    }

不单独使用能改变header的方法,比如 header 、 setcookie 等,将您所有返回的headers都放在构造Response对象的参数里面

  • 使用fcPhpCgiProxy
    至少两个文件, 一个入口函数文件,一个待解析的文件, for example:

    <?php
       // 入口函数文件
        function http_fc_func_cgi($request, $context): Response{
        $requestURI = $request->getAttribute("requestURI");
        // parse $requestURI
        $proxy = $GLOBALS['fcPhpCgiProxy'];
        return $proxy->requestPhpCgi($request, __DIR__ . '/www', "a.php", ['SERVER_NAME' => 'abc.com']);
    }
    <?php 
    // 待解析的文件
    $body = @file_get_contents('php://input');
    
    http_response_code(500);
    header('customheader1: v1');
    header('customheader1: v3', false);
    header('customheader2: v2');
    
    echo "<HTML>" . "\n";
    echo "<HEAD><TITLE>Simple Virtual HTML Document</TITLE></HEAD>" . "\n";
    echo "<BODY>" . $body . "\n";
    
    echo "REQUESTHEADERS: " . "\n";
    foreach ($_SERVER as $key => $value) {
        if (strpos($key, 'HTTP_') === 0) {
            echo $key . " : " . $value . PHP_EOL;
        }
    }
    
    echo "QUERYSTRING: " . $_SERVER['QUERY_STRING'] . PHP_EOL;
    
    echo "<H1>" . "Virtual HTML" . "</H1>" . "<HR>" . "\n";
    echo "Hey look. I just created a virtual (yep. virtual) HTML document!" . "\n";
    echo "</BODY></HTML>" . "\n";
    
    ?> 

    建议: 如果只是使用http trigger 实现类似一个api的功能,尽量不要使用fcPhpCgiProxy, 即不要单独使用改变header的方法; 如果是类似一个web的工程的开发或者迁移,尽量使用fcPhpCgiProxy

session 目录更改的姿势

由于函数计算serverless的特性,我们会把公共的东西放在nas目录,比如web 工程, 临时tmp缓存; 在php runtime使用场景中, session目录肯定是想放在nas 公共目录中的.

参考函数计算 php runtime - 如何加载卸载内置扩展

只要是在php ini 可以设置的,我们都可以通过上述的方法达到目标,并且我们鼓励使用这种方法来裁剪不需要的extension, 比如针对session 设置,我们可以使用如下自定义的ini:

extension=session.so
extension=ftp.so
extension=shmop.so
extension=bcmath.so
extension=gettext.so
extension=pcntl.so
extension=simplexml.so
extension=xmlreader.so
extension=bz2.so
extension=gmp.so
extension=pdo.so
extension=soap.so
extension=xmlrpc.so
extension=calendar.so
extension=iconv.so
extension=pdo_mysql.so
extension=sockets.so
extension=xmlwriter.so
extension=ctype.so
extension=imagick.so
extension=phar.so
extension=sysvmsg.so
extension=dom.so
extension=json.so
extension=posix.so
extension=sysvsem.so
extension=exif.so
extension=mbstring.so
extension=protobuf.so
extension=sysvshm.so
extension=fileinfo.so
extension=mysqli.so
extension=redis.so
extension=zip.so
extension=memcached.so
extension=tokenizer.so
session.save_path=/mnt/www
session.auto_start=1 

最后两句用于session的设置,设置到nas的 /mnt/www 目录 并且自动开启

目录
相关文章
|
7天前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
3月前
|
SQL 存储 安全
PHP 与现代 Web 应用的安全挑战与解决方案
随着 Web 应用的发展,PHP 作为一种广泛使用的服务器端脚本语言,面临着越来越复杂的安全挑战。本文探讨了当前 PHP 开发中常见的安全问题,并提供了相应的解决方案,帮助开发者构建更安全可靠的 Web 应用。 【7月更文挑战第8天】
60 1
|
3月前
|
运维 Serverless API
函数计算产品使用问题之如何使用PHP Runtime非内置扩展
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
|
5月前
|
缓存 NoSQL PHP
【PHP 开发专栏】Redis 作为 PHP 缓存的解决方案
【4月更文挑战第30天】本文探讨了Redis作为PHP缓存的优势,如高性能、丰富数据结构、数据持久化和分布式支持。通过安装配置Redis、选择PHP客户端、执行读写操作及制定缓存策略实现缓存。应用场景包括页面、数据和会话缓存。但需注意数据一致性、过期时间、容量和安全问题,以确保应用稳定和安全。Redis能有效提升PHP应用响应速度和处理能力。
126 2
|
PHP
php开发实战分析(4):php调用封装函数包含文件路径自适应不同目录的解决方案($_SERVER[‘DOCUMENT_ROOT‘]与__DIR__魔术常量)
php开发实战分析(4):php调用封装函数包含文件路径自适应不同目录的解决方案($_SERVER[‘DOCUMENT_ROOT‘]与__DIR__魔术常量)
171 0
|
SQL JSON 关系型数据库
php执行语句在MySQL批量插入大数据量的解决方案及计算程序执行时间(大数据量、MySQL语句优化)
php执行语句在MySQL批量插入大数据量的解决方案及计算程序执行时间(大数据量、MySQL语句优化)
245 1
|
运维 API PHP
漏刻有时API接口实战开发系列(13):小鹅通云服务PHP-API二维数组传参解决方案
漏刻有时API接口实战开发系列(13):小鹅通云服务PHP-API二维数组传参解决方案
102 1
PHP报错Call to undefined function utf8_decode()的解决方案
PHP报错Call to undefined function utf8_decode()的解决方案
131 0
|
JSON 前端开发 安全
ajax中实现访问url已阅即焚的解决方案(url动态参数、变量加密、常量不变、php加密解密、API访问验证方式)
ajax中实现访问url已阅即焚的解决方案(url动态参数、变量加密、常量不变、php加密解密、API访问验证方式)
260 0
|
存储 监控 算法
php开发实战分析(9):使用实现短地址的分享的解决方案(第三方短链接服务、数据库自增ID转换、自定义短地址生成算法、自增数字短码)
php开发实战分析(9):使用实现短地址的分享的解决方案(第三方短链接服务、数据库自增ID转换、自定义短地址生成算法、自增数字短码)
233 0
下一篇
无影云桌面