PHP插件获取网页返回的html源码
字数 956 2025-08-26 22:12:02

PHP插件获取网页返回的HTML源码 - 详细教学文档

1. 背景与目标

本教学文档详细讲解如何通过PHP扩展插件来获取网页返回的HTML源码。这种方法不受ob_start()等输出缓冲函数的影响,能够直接捕获PHP脚本的所有输出内容。

2. 技术原理

2.1 PHP输出缓冲机制分析

PHP的输出缓冲机制主要通过output_globals全局变量和一系列输出处理函数实现:

  1. ob_start()函数在main/output.cmain/php_output.h中定义
  2. 所有输出相关的函数最终都会调用php_output_op函数
  3. PHP通过判断OG(active)是否为NULL来决定是否进入缓冲区

2.2 关键数据结构

typedef struct _php_output_buffer {
    char *data;      // 缓冲区数据
    size_t size;     // 缓冲区大小
    size_t used;     // 已使用大小
    uint free:1;     // 是否释放标志
    uint _reserved:31; // 保留位
} php_output_buffer;

3. 插件开发步骤

3.1 创建PHP扩展

在PHP源码的ext目录下执行:

./ext_skel --extname=myext

3.2 定义全局变量

php_hook_output_ext.h中定义全局变量:

ZEND_BEGIN_MODULE_GLOBALS(myext)
    char *data;     // 缓存区
    size_t size;    // 缓存区大小
    size_t used;    // 数据长度
ZEND_END_MODULE_GLOBALS(myext)

3.3 初始化与析构函数

static void php_myext_globals_ctor(zend_myext_globals *G TSRMLS_DC)
{
    G->data = NULL;
    G->size = 0;
    G->used = 0;
}

static void php_myext_globals_dtor(zend_myext_globals *G TSRMLS_DC)
{
    efree(G->data);
}

3.4 Hook输出操作码

ZEND_ECHO操作码为例:

static int hookecho(ZEND_OPCODE_HANDLER_ARGS)
{
    zend_op *opline = execute_data->opline;
    zval *z = EX_CONSTANT(opline->op1);
    
    if (Z_TYPE_P(z) == IS_STRING) {
        zend_string *str = Z_STR_P(z);
        if (ZSTR_LEN(str) != 0) {
            get_data(ZSTR_VAL(str), ZSTR_LEN(str));
        }
    } else {
        zend_string *str = _zval_get_string_func(z);
        if (ZSTR_LEN(str) != 0) {
            get_data(ZSTR_VAL(str), ZSTR_LEN(str));
        } 
        zend_string_release(str);
    }
    return ZEND_USER_OPCODE_DISPATCH;
}

3.5 数据缓冲区处理

static int get_data(char *str, size_t str_len)
{
    if(str_len){
        // 空间不足时扩展缓冲区
        if ((MYEXT_G(size) - MYEXT_G(used)) <= str_len){
            size_t grow_int = PHP_OUTPUT_HANDLER_INITBUF_SIZE(MYEXT_G(size));
            size_t grow_buf = PHP_OUTPUT_HANDLER_INITBUF_SIZE(str_len - (MYEXT_G(size) - MYEXT_G(used)));
            size_t grow_max = MAX(grow_int, grow_buf);
            MYEXT_G(data) = erealloc(MYEXT_G(data), MYEXT_G(size) + grow_max);
            MYEXT_G(size) += grow_max;
        }
        memcpy(MYEXT_G(data) + MYEXT_G(used), str, str_len);
        MYEXT_G(used) += str_len;
    }
    return 1;
}

3.6 模块生命周期管理

PHP_MINIT_FUNCTION(myext)
{
#ifdef ZTS
    ts_allocate_id(&myext_globals_id,
                  sizeof(zend_myext_globals),
                  (ts_allocate_ctor)php_myext_globals_ctor,
                  (ts_allocate_dtor)php_myext_globals_dtor);
#else
    php_myext_globals_ctor(&myext_globals TSRMLS_CC);
#endif
    zend_set_user_opcode_handler(ZEND_ECHO, hookecho);
    return SUCCESS;
}

PHP_RSHUTDOWN_FUNCTION(myext)
{
#ifndef ZTS
    php_myext_globals_dtor(&myext_globals TSRMLS_CC);
#endif
    FILE *fp;
    fp = fopen("/web/php/log","a");
    fwrite(MYEXT_G(data), MYEXT_G(used), 1, fp);
    fwrite("\n------------------\n", 21, 1, fp);
    fclose(fp);
    return SUCCESS;
}

4. 完整代码实现

4.1 头文件 (php_myext.h)

#ifndef PHP_MYEXT_H
#define PHP_MYEXT_H

extern zend_module_entry myext_module_entry;
#define phpext_myext_ptr &myext_module_entry

#define PHP_MYEXT_VERSION "0.1.0"

// ... 其他标准头文件内容 ...

#define MYEXT_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(myext,v)
ZEND_BEGIN_MODULE_GLOBALS(myext)
    char *data;
    size_t size;
    size_t used;
ZEND_END_MODULE_GLOBALS(myext)

#define ZEND_OPCODE_HANDLER_ARGS zend_execute_data *execute_data

PHP_FUNCTION(confirm_myext_compiled);
static int hookecho(ZEND_OPCODE_HANDLER_ARGS);
static int get_data(char *str, size_t str_len);
static void init_myext_global();

#endif

4.2 主实现文件 (myext.c)

// ... 标准头文件包含 ...

ZEND_DECLARE_MODULE_GLOBALS(myext);

// ... 其他函数实现 ...

const zend_function_entry myext_functions[] = {
    PHP_FE(confirm_myext_compiled, NULL)
    PHP_FE_END
};

zend_module_entry myext_module_entry = {
    STANDARD_MODULE_HEADER,
    "myext",
    myext_functions,
    PHP_MINIT(myext),
    PHP_MSHUTDOWN(myext),
    PHP_RINIT(myext),
    PHP_RSHUTDOWN(myext),
    PHP_MINFO(myext),
    PHP_MYEXT_VERSION,
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_MYEXT
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(myext)
#endif

5. 编译与使用

  1. 将扩展代码放入PHP源码的ext目录
  2. 运行phpize生成配置
  3. 执行./configuremake编译扩展
  4. 将生成的.so文件添加到php.ini中

6. 结果验证

插件会将捕获的所有输出内容保存到/web/php/log文件中,每条记录以分隔线------------------分隔。

7. 注意事项

  1. 使用Apache或Nginx时,需要确保日志文件路径在web根目录下,或修改相关配置
  2. 在多线程环境(ZTS)下需要特别注意线程安全
  3. 缓冲区大小需要根据实际输出内容调整

8. 扩展思路

  1. 可以Hook更多输出相关的操作码,如printprintf
  2. 可以添加过滤功能,只捕获特定格式的输出
  3. 可以实现网络传输功能,将捕获的内容发送到远程服务器

9. 参考资源

  1. PHP源码中的main/output.cmain/php_output.h
  2. PHP操作码Hook技术
  3. PHP扩展开发官方文档
PHP插件获取网页返回的HTML源码 - 详细教学文档 1. 背景与目标 本教学文档详细讲解如何通过PHP扩展插件来获取网页返回的HTML源码。这种方法不受 ob_start() 等输出缓冲函数的影响,能够直接捕获PHP脚本的所有输出内容。 2. 技术原理 2.1 PHP输出缓冲机制分析 PHP的输出缓冲机制主要通过 output_globals 全局变量和一系列输出处理函数实现: ob_start() 函数在 main/output.c 和 main/php_output.h 中定义 所有输出相关的函数最终都会调用 php_output_op 函数 PHP通过判断 OG(active) 是否为NULL来决定是否进入缓冲区 2.2 关键数据结构 3. 插件开发步骤 3.1 创建PHP扩展 在PHP源码的ext目录下执行: 3.2 定义全局变量 在 php_hook_output_ext.h 中定义全局变量: 3.3 初始化与析构函数 3.4 Hook输出操作码 以 ZEND_ECHO 操作码为例: 3.5 数据缓冲区处理 3.6 模块生命周期管理 4. 完整代码实现 4.1 头文件 (php_ myext.h) 4.2 主实现文件 (myext.c) 5. 编译与使用 将扩展代码放入PHP源码的ext目录 运行 phpize 生成配置 执行 ./configure 和 make 编译扩展 将生成的.so文件添加到php.ini中 6. 结果验证 插件会将捕获的所有输出内容保存到 /web/php/log 文件中,每条记录以分隔线 ------------------ 分隔。 7. 注意事项 使用Apache或Nginx时,需要确保日志文件路径在web根目录下,或修改相关配置 在多线程环境(ZTS)下需要特别注意线程安全 缓冲区大小需要根据实际输出内容调整 8. 扩展思路 可以Hook更多输出相关的操作码,如 print 、 printf 等 可以添加过滤功能,只捕获特定格式的输出 可以实现网络传输功能,将捕获的内容发送到远程服务器 9. 参考资源 PHP源码中的 main/output.c 和 main/php_output.h PHP操作码Hook技术 PHP扩展开发官方文档