如何通过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(¶ms_header).unwrap(); socket.write_all(¶ms_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
相关产品推荐
相关产品推荐

