You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过FastCGI获取程序输出?Rust调用PHP-FPM遇阻

Rust中FastCGI调用PHP-FPM的问题解决

问题描述

在Rust中使用FastCGI协议调用PHP-FPM时,仅能收到响应头,无法获取PHP文件的输出内容。代码及执行输出如下:

原代码

#![allow(non_snake_case)]
#![allow(unused_must_use)]
use std::os::unix::net::{UnixStream};
use std::io::{Read, Write};

fn main() {
    const FCGI_VERSION_1: u8    = 1;
    const FCGI_BEGIN_REQUEST:u8 = 1;
    const FCGI_END_REQUEST: u8 = 3;

    const FCGI_RESPONDER: u16  = 1;

    const FCGI_PARAMS: u8 = 4;

    const FCGI_GET_VALUES: u8 = 9;

    let socket_path = "/run/php-fpm/php-fpm.sock";

    let mut socket = match UnixStream::connect(socket_path) {
        Ok(sock) => sock,
        Err(e) => {
            println!("Couldn't connect: {e:?}");
            return
        }
    };

    let requestId: u16 = 1;

    let role: u16 = FCGI_RESPONDER;

    let beginRequest = vec![
       // FCGI_Header
       FCGI_VERSION_1, FCGI_BEGIN_REQUEST,
       (requestId >> 8) as u8, (requestId & 0xFF) as u8,
       0x00, 0x08, // This is the size of `FCGI_BeginRequestBody`
       0, 0,
       // FCGI_BeginRequestBody
       (role >> 8) as u8, (role & 0xFF) as u8,
       0, // Flags
       0, 0, 0, 0, 0, // Reserved
    ];

    socket.write_all(&beginRequest).unwrap();

    let data = vec![
        (100) as u8,
    ];

    let contentLength = data.len();

    assert!(contentLength <= usize::MAX);

    let requestHeader = vec![
       FCGI_VERSION_1, FCGI_BEGIN_REQUEST,
       (requestId >> 8) as u8, (requestId & 0xFF) as u8,
       (contentLength >> 8) as u8, (contentLength & 0xFF) as u8,
      0, 0,
    ];

    socket.write_all(&requestHeader).unwrap();


    let param_name = "SCRIPT_FILENAME".as_bytes();
    let param_value = "index.php".as_bytes();
    let lengths = [ param_name.len() as u8, param_value.len() as u8 ];
    socket.write_all (&lengths).unwrap();
    socket.write_all (param_name).unwrap();
    socket.write_all (param_value).unwrap();

    let mut response = String::new();

    socket.read_to_string(&mut response);

    print!("response: {}", response);
}

执行输出

response: DX-Powered-By: PHP/8.1.11
Content-type: text/html; charset=UTF-8

推测FastCGI未执行传入的PHP文件,希望了解遗漏的步骤及如何获取PHP文件的输出。


问题分析与修正

你的代码存在几个关键错误,不符合FastCGI协议规范,导致PHP-FPM未正确处理请求:

  • 参数封装错误:第二个请求头错误使用FCGI_BEGIN_REQUEST类型,参数数据必须用FCGI_PARAMS类型包裹;且需要发送空的FCGI_PARAMS记录告知服务端参数已发送完毕;参数长度编码未遵循规范(≤127用1字节,>127用4字节)。
  • 无关数据发送错误:发送的vec![100u8]未用正确请求头包裹,无POST数据时需发送空的FCGI_STDIN标记输入结束。
  • 响应读取错误:FastCGI响应由多个记录组成(FCGI_STDOUT、FCGI_STDERR、FCGI_END_REQUEST等),直接read_to_string无法完整解析。

修正后的代码

#![allow(non_snake_case)]
use std::os::unix::net::UnixStream;
use std::io::{Read, Write};

// FastCGI常量定义
const FCGI_VERSION_1: u8 = 1;
const FCGI_BEGIN_REQUEST: u8 = 1;
const FCGI_PARAMS: u8 = 4;
const FCGI_STDIN: u8 = 5;
const FCGI_STDOUT: u8 = 6;
const FCGI_STDERR: u8 = 7;
const FCGI_END_REQUEST: u8 = 3;
const FCGI_RESPONDER: u16 = 1;

// 封装FastCGI头部
fn build_header(version: u8, type_: u8, request_id: u16, content_len: usize) -> Vec<u8> {
    let mut header = Vec::with_capacity(8);
    header.push(version);
    header.push(type_);
    header.push((request_id >> 8) as u8);
    header.push((request_id & 0xFF) as u8);
    header.push((content_len >> 8) as u8);
    header.push((content_len & 0xFF) as u8);
    header.push(0); // padding长度
    header.push(0); // reserved
    header
}

// 编码参数的长度
fn encode_length(len: usize) -> Vec<u8> {
    if len <= 127 {
        vec![len as u8]
    } else {
        vec![
            0x80 | ((len >> 24) & 0xFF) as u8,
            ((len >> 16) & 0xFF) as u8,
            ((len >> 8) & 0xFF) as u8,
            (len & 0xFF) as u8,
        ]
    }
}

fn main() {
    let socket_path = "/run/php-fpm/php-fpm.sock";
    let mut socket = match UnixStream::connect(socket_path) {
        Ok(sock) => sock,
        Err(e) => {
            eprintln!("连接失败: {e:?}");
            return;
        }
    };

    let request_id: u16 = 1;

    // 1. 发送BEGIN_REQUEST记录
    let begin_request_body = vec![
        (FCGI_RESPONDER >> 8) as u8,
        (FCGI_RESPONDER & 0xFF) as u8,
        0, // flags
        0, 0, 0, 0, 0, // reserved
    ];
    let begin_header = build_header(FCGI_VERSION_1, FCGI_BEGIN_REQUEST, request_id, begin_request_body.len());
    socket.write_all(&begin_header).unwrap();
    socket.write_all(&begin_request_body).unwrap();

    // 2. 发送FCGI_PARAMS记录
    let param_name = "SCRIPT_FILENAME".as_bytes();
    let param_value = "/path/to/your/index.php".as_bytes(); // 替换为实际的PHP文件绝对路径
    let mut params_data = Vec::new();
    params_data.extend_from_slice(&encode_length(param_name.len()));
    params_data.extend_from_slice(&encode_length(param_value.len()));
    params_data.extend_from_slice(param_name);
    params_data.extend_from_slice(param_value);

    let params_header = build_header(FCGI_VERSION_1, FCGI_PARAMS, request_id, params_data.len());
    socket.write_all(&params_header).unwrap();
    socket.write_all(&params_data).unwrap();

    // 发送空的FCGI_PARAMS记录表示参数结束
    let empty_params_header = build_header(FCGI_VERSION_1, FCGI_PARAMS, request_id, 0);
    socket.write_all(&empty_params_header).unwrap();

    // 3. 发送空的FCGI_STDIN记录表示输入结束(无POST数据)
    let empty_stdin_header = build_header(FCGI_VERSION_1, FCGI_STDIN, request_id, 0);
    socket.write_all(&empty_stdin_header).unwrap();

    // 4. 读取并解析响应
    let mut buffer = [0u8; 1024];
    let mut stdout = String::new();
    let mut stderr = String::new();

    loop {
        // 读取响应头部
        let mut header = [0u8; 8];
        match socket.read_exact(&mut header) {
            Ok(_) => {},
            Err(_) => break, // 连接关闭
        }

        let version = header[0];
        let type_ = header[1];
        let _req_id = ((header[2] as u16) << 8) | header[3] as u16;
        let content_len = ((header[4] as usize) << 8) | header[5] as usize;
        let padding_len = header[6] as usize;

        if version != FCGI_VERSION_1 {
            eprintln!("不支持的FastCGI版本");
            break;
        }

        // 读取内容
        let mut content = vec![0u8; content_len];
        socket.read_exact(&mut content).unwrap();
        // 读取padding
        if padding_len > 0 {
            let mut padding = vec![0u8; padding_len];
            socket.read_exact(&mut padding).unwrap();
        }

        match type_ {
            FCGI_STDOUT => stdout.push_str(&String::from_utf8_lossy(&content)),
            FCGI_STDERR => stderr.push_str(&String::from_utf8_lossy(&content)),
            FCGI_END_REQUEST => break, // 请求结束
            _ => {}, // 忽略其他类型
        }
    }

    println!("标准输出:\n{}", stdout);
    if !stderr.is_empty() {
        eprintln!("错误输出:\n{}", stderr);
    }
}

关键修正点说明

  • 参数封装:用FCGI_PARAMS类型包裹参数数据,并发送空记录表示参数结束
  • 输入结束标记:发送空的FCGI_STDIN记录,告知PHP-FPM没有更多输入数据
  • 响应解析:循环读取FastCGI的每个响应记录,分别处理标准输出和错误输出,直到收到FCGI_END_REQUEST记录
  • 路径正确性:SCRIPT_FILENAME必须使用PHP文件的绝对路径,否则PHP-FPM无法找到文件

内容的提问来源于stack exchange,提问作者DFG

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 01:01:07