我目前正在和一些同事一起开展一个小型个人项目,需要进行FastCGI请求,目前我们拥有的代码如下:
#![allow(non_snake_case)]
use std::os::unix::net::{UnixStream};
use std::io::{Read, Write};
fn main() {
// here we create the request body, which should be sent in the FCGI_STDIN record
let data = "foo=bar";
// here we define the constants that FastCGI needs
const FCGI_VERSION_1: u8 = 1;
const FCGI_BEGIN_REQUEST:u8 = 1;
const FCGI_END_REQUEST: u8 = 3;
const FCGI_STDIN: u8 = 5;
const FCGI_STDOUT: u8 = 6;
const FCGI_STDERR: u8 = 7;
const FCGI_RESPONDER: u16 = 1;
const FCGI_PARAMS: u8 = 4;
// here we connect to the socket
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
}
};
// we send the record FCGI_BEGIN_REQUEST
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();
// send the FCGI_PARAMS
let param1_name = b"SCRIPT_FILENAME";
let param1_value = b"/var/www/public/index.php";
let lengths1 = [ param1_name.len() as u8, param1_value.len() as u8 ];
let params1_len: u16 = (param1_name.len() + param1_value.len() + lengths1.len()) as u16;
let param2_name = b"REQUEST_METHOD";
let param2_value = b"POST";
let lengths2 = [ param2_name.len() as u8, param2_value.len() as u8 ];
let params2_len: u16 = (param2_name.len() + param2_value.len() + lengths2.len()) as u16;
let param3_name = b"CONTENT_LENGTH";
let content = data.len().to_string();
let param3_value = content.as_bytes();
let lengths3 = [ param3_name.len() as u8, param3_value.len() as u8 ];
let params3_len: u16 = (param3_name.len() + param3_value.len() + lengths3.len()) as u16;
let param4_name = b"CONTENT_TYPE";
let param4_value = "application/x-www-form-urlencoded".as_bytes();
let lengths4 = [ param4_name.len() as u8, param4_value.len() as u8 ];
let params4_len: u16 = (param4_name.len() + param4_value.len() + lengths4.len()) as u16;
let params_len = params1_len + params2_len + params3_len + params4_len;
let paramsRequest = vec![
FCGI_VERSION_1, FCGI_PARAMS,
(requestId >> 8) as u8, (requestId & 0xFF) as u8,
(params_len >> 8) as u8, (params_len & 0xFF) as u8,
0, 0,
];
/*
* here we write the parameters SCRIPT_FILENAME, REQUEST_METHOD, CONTENT_LENGTH and CONTENT_TYPE (which are sent correctly)
*/
socket.write_all (¶msRequest).unwrap();
socket.write_all (&lengths1).unwrap();
socket.write_all (param1_name).unwrap();
socket.write_all (param1_value).unwrap();
socket.write_all (&lengths2).unwrap();
socket.write_all (param2_name).unwrap();
socket.write_all (param2_value).unwrap();
socket.write_all (&lengths3).unwrap();
socket.write_all (param3_name).unwrap();
socket.write_all (param3_value).unwrap();
socket.write_all (&lengths4).unwrap();
socket.write_all (param4_name).unwrap();
socket.write_all (param4_value).unwrap();
/*
* From here we start working with FCGI_STDIN.
*
* Frst we calculate the length of the content and then we send the FCGI_STDIN record together with the request body
*/
let contentLength = data.as_bytes().len();
let requestHeader = vec![
FCGI_VERSION_1, FCGI_STDIN,
(requestId >> 8) as u8, (requestId & 0xFF) as u8,
(contentLength >> 8) as u8, (contentLength & 0xFF) as u8,
0, 0,
];
socket.write_all (&requestHeader).unwrap();
socket.write_all (&data.as_bytes()).unwrap();
let mut stdout: String = String::new();
// get the response
let requestHeader = vec![
FCGI_VERSION_1, FCGI_STDOUT,
(requestId >> 8) as u8, (requestId & 0xFF) as u8,
0, 0,
0, 0,
];
socket.write_all(&requestHeader).unwrap();
loop {
// read the response header
let mut responseHeader = [0u8; 8];
socket.read_exact (&mut responseHeader).unwrap();
if responseHeader[1] != FCGI_STDOUT && responseHeader[1] != FCGI_STDERR{
if responseHeader[1] == FCGI_END_REQUEST {
println!("FCGI_END_REQUEST: {:?}", responseHeader);
break;
} else {
println!("NOT FCGI_END_REQUEST: {}", responseHeader[1]);
break;
}
}
// read the body
let responseLength = ((responseHeader[4] as usize) << 8) | (responseHeader[5] as usize);
let mut responseBody = vec![0; responseLength];
socket.read_exact (&mut responseBody).unwrap();
stdout.push_str(&String::from_utf8_lossy(&responseBody));
// read the padding
let mut pad = vec![0; responseHeader[6] as usize];
socket.read_exact (&mut pad).unwrap();
}
println!("Output: {}", stdout)
}
那段代码发送一个FCGI请求到一个php文件,该文件的内容如下:
<?php
var_dump($_POST);
?>
当我运行Rust代码时,我会得到以下输出:
FCGI_END_REQUEST: [1, 3, 0, 1, 0, 8, 0, 0]
Output: X-Powered-By: PHP/8.1.13
Content-type: text/html; charset=UTF-8
array(0) {
}
除了PHP未收到请求正文外,其余一切正常,对我来说,
$_POST 的值不应该为空。因此,我猜问题在于FCGI_STDIN
记录上。我查看了FastCGI规范,我找到的唯一相关内容是,如果CONTENT_LENGTH的值与FCGI_STDIN接收到的字节数不同,则连接将关闭,但即使我只发送 FCGI_STDIN 记录而不发送请求正文,我也看不到任何事情发生
我做错了什么?为什么PHP没有收到请求正文?
更新
当我运行留在这个问题的答案中的代码时,也就是说,这段代码:
$pl = file_get_contents('php://input');
var_dump($pl);
当我从我的Rust代码中执行该PHP文件时,我得到了一个空字符串,具体如下:
string(0) ""
那么我的 Rust 代码有什么问题呢?为什么数据没有被正确发送?
let data = b"foo=bar"
吗? - Ahmed Masuddata
时,我会将它作为data.as_bytes()
发送。 - Agni21