在 Rust 中客户端发起 FCGI 请求时出现问题

6

我目前正在和一些同事一起开展一个小型个人项目,需要进行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 (&paramsRequest).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 代码有什么问题呢?为什么数据没有被正确发送?


1
我无法找到您代码中的任何问题。我尝试修改值,看看会发生什么情况,但没有用。我想这可能是由于您错过了需要发送的记录。我开始查看其他实现,看看是否能够发现差异,但现在时间已经很晚了,所以我放弃寻找答案了。我认为您可以查看nginx如何执行此操作https://github.com/nginx/nginx/blob/bfc5b35827903a3c543b58e4562db8b62021c164/src/http/modules/ngx_http_fastcgi_module.c,并查看您的执行方法是否存在任何差异。 - Lunfel
1
我已经尝试在那里查找,但没有发现任何问题。我还查看了fastcgi规范。显然,您可以看到我的代码与第二个示例完全相同:https://www.mit.edu/~yandros/doc/specs/fcgi-spec.html#SB。所以我不知道可能出了什么问题。 - Agni21
顺便提一下,fcgi 不支持 Unicode,因此可能会导致 "foo=bar" 无法到达目的地。你考虑过尝试使用 let data = b"foo=bar" 吗? - Ahmed Masud
@AhmedMasud 是的,实际上当我发送data时,我会将它作为data.as_bytes()发送。 - Agni21
1个回答

0

非常抱歉,我从未使用过FastCGI,但我非常有信心来查看这个有效负载 :)

// returns json
$pl = file_get_contents('php://input');

var_dump($pl);

然后,您可以将结果存储在数组中以更好地处理它们:

$params = json_decode($pl, true);

希望这是你正在寻找的答案,
最好的祝福。


这不完全是我想要的,但我已经编辑了我的问题,并提供了更多关于你的代码的信息:D。 - Agni21

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接