如何读取 FastCGI 请求的所有输出?

2023-12-04

我正在尝试使用 Rust 向 PHP 文件发出请求FastCGI协议。感谢以下答案,我得到了这段代码我之前的问题:

use std::os::unix::net::{UnixStream};
use std::io::{Read, Write};
use std::str;

fn main() {
    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_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 param_name = "SCRIPT_FILENAME".as_bytes();
    let param_value = "/var/www/html/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 requestHeader = vec![
       FCGI_VERSION_1, FCGI_STDIN,
       (requestId >> 8) as u8, (requestId & 0xFF) as u8,
       0, 0,
       0, 0,
    ];
    socket.write_all(&requestHeader).unwrap();

    let mut responseHeader = [0u8; 8];
    socket.read_exact (&mut responseHeader).unwrap();
    assert!(responseHeader[1] == FCGI_STDOUT);  // TODO: proper handling of message type
    let responseLength = ((responseHeader[4] as usize) << 8) | (responseHeader[5] as usize);
    let mut responseBody = Vec::new();
    responseBody.resize (responseLength, 0);
    socket.read_exact (&mut responseBody).unwrap();
    println!("{:?}", std::str::from_utf8(responseBody));
}

该代码启动一个请求,然后写入SCRIPT_FILENAME参数,然后读取 PHP 文件的输出。这是 PHP 文件:

<?php

echo "First file";

?>

当我运行 Rust 文件时,控制台显示如下:

Ok("X-Powered-By: PHP/8.1.11\r\nContent-type: text/html; charset=UTF-8\r\n\r\n")

控制台没有向我显示程序的输出,所以我想问题可能是什么Jmb在他给我的回答中说:

响应可能会分为多个 FCGI_STDOUT 消息,因此您需要一个循环来重新组装它们。

现在我看到了FastCGI协议规范示例3我意识到在该示例中程序的输出仍然是分开的(或者我认为是这样)。那么我怎样才能读取程序的所有输出呢?我知道 Jmb 说我需要一个循环来做到这一点,那么我应该如何实现循环呢?或者在哪里?我想澄清一下,我对 FastCGI 还很陌生,而且对二进制协议没有任何经验,所以如果我遗漏了一些看起来很明显的东西,我真的很抱歉。


您的代码存在一些问题。

Issue #1

最后一个字节FCGI_Record被命名padding。它表示在该有效负载之后应跳过多少字节。您将其设置为 0,因此那里并不重要,但是从服务器读取时,您必须跳过这么多字节:

    let mut pad = vec![0; responseHeader[7] as usize];
    socket.read_exact (&mut pad).unwrap();

这解释了如果您尝试执行读取循环,您将得到垃圾而不是FCGI_END_REQUEST frame.

Issue #2

The params部分需要标头,但您正在发送原始数据。 您必须计算序列化参数的完整长度并在此之前发送标头:

    let params_len: u16 = (param_name.len() + param_value.len() + lengths.len()) as u16;
    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,
    ];
    socket.write_all (&paramsRequest).unwrap();

Issue #3

我没有在任何地方找到所需的最小参数php-fpm, but 浏览源代码,你至少需要添加REQUEST_METHOD:

    let param1_name = "SCRIPT_FILENAME".as_bytes();
    let param1_value = "/var/www/html/index.php".as_bytes();
    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"GET";
    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 params_len = params1_len + params2_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,
    ];
    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();

在这一点上,我认为您应该考虑为这些东西编写正确的类型和序列化原语,而不是原始字节数组......

通过这些更改,我们已经获得了完整的输出:

Ok("X-Powered-By: PHP/8.1.11\r\nContent-type: text/html; charset=UTF-8\r\n\r\nFirst file")

Issue #4

您应该循环读取响应代码,连接任何FCGI_STDOUT数据,记录或忽略其他消息,可能记录FCGI_STDERR消息,直到您收到FCGI_END_REQUEST.

在这个特定的示例中,输出非常短,以至于它适合一条消息,但是如果不执行循环,您就看不到FCGI_END_REQUEST.

之后FCGI_END_REQUEST,取决于您是否使用过FCGI_KEEP_CONN您可以关闭套接字或保持其打开状态以发送新请求。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何读取 FastCGI 请求的所有输出? 的相关文章

随机推荐

  • initContainers 和 Kubernetes 中容器的区别

    我注意到在部署文件中有两个容器字段 例如initContainers and containers对我来说看起来很困惑 我通过互联网搜索但无法理解 谁能告诉我两者之间的区别initContainers and containers以及我们如
  • OAuth2 登录后 Facebook 获取个人资料链接

    我通过 Facebook 社交网络为网站上的用户使用 OAuth2 登录 登录后是否仍然可以获取特定用户的 Facebook 个人资料 URL 例如通过accessToken 或者 Facebook 现在隐藏这些信息 在发生数据泄露事件之后
  • 为什么 imshow 的范围参数会翻转我的图像?

    import matplotlib pyplot as plt import numpy as np n 16 im np eye n fig plt figure ax fig add subplot 121 ax imshow im a
  • 为什么会调用 OnlyOnCanceled 延续?

    打电话时await RunAsync 在下面的代码中 我希望继续TaskContinuationOptions OnlyRanToCompletion继续运行 但是OnlyOnCanceled继续被调用 产生调试输出 任务已取消 Why p
  • 我可以使用 Homebrew 安装最新的 AWS EB CLI 吗?

    自制似乎正在安装 an 旧版本 不支持 of the AWS 电子银行工具 有没有办法让 Homebrew 安装当前的版本 我是自制软件的新手 Homebrew 依靠志愿者来更新配方 如果您发现过时的公式 请提交错误或拉取请求
  • jquery post codeigniter 验证

    我们使用 jquery 将表单 load 到 div 中 然后我们使用 jquery post 将该形式发送到 codeigniter 控制器 即 app post 然后我们希望 Codeigniter 执行验证 但不确定如何返回页面以显示
  • 为什么积压已满时 ServerSocket 连接不会被拒绝?

    无私的好奇心 在 Java 中 我监听一个套接字 积压为 1 ServerSocket ss new ServerSocket 4000 1 我在壳里跑 netcat localhost 4000 很多次 到目前为止 5 次 连接永远不会被
  • 如何检索必须在另一个线程上计算的值

    在许多情况下 线程 A 需要一个必须在线程 B 上计算的值 最常见的是 B EDT 考虑以下示例 String host SwingUtilities invokeAndWait new Runnable public void run h
  • 如何在 pyomo 中将积分定义为目标函数?

    我希望能够定义一个积分pyomo作为目标函数的一部分 我无法弄清楚积分需要什么样的表达式 这是我最好的猜测 model ConcreteModel model t ContinuousSet bounds 0 1 model y Var m
  • Java GIF 动画无法正确重画

    我正在尝试为 GIF 图像制作动画 动画可以 但画得不好 It shows like this non animated screenshot In the image the tail wags like this 正如您所看到的 图像重
  • 使用 WinPcap 获取原始 WiFi 数据包

    考虑简单的 C 代码发送单个原始数据包与WinPcap 与构建数据包标头相关的行以以下注释开头 假设在以太网上 将 mac 目标设置为 1 1 1 1 1 1 因此 您可能会猜测 为了发送原始 WiFi 数据包 您应该相应地更改此代码块 然
  • .java 文件中的包使类文件无法使用

    自从我上次做Java以来 已经太久了 我不记得为什么会发生以下情况 给定这个由标准 Maven 项目创建的文件 如下所示 Maven教程 package com mycompany app Hello world public class
  • 如何移动对话中的所有消息?

    我需要知道如何同时移动对话中的所有消息 我的宏当前显示为 Sub Archive Set ArchiveFolder Application GetNamespace MAPI GetDefaultFolder olFolderInbox
  • 如何扩展 KineticJS 形状

    对于 KineticJS 版本4 0 0或者更少的形状扩展了一个类并且可以通过以下方式扩展 var MyCircle Kinetic Circle extend init function config this super config
  • 尝试在 SSE 编程中使用 and 掩码添加 __m128

    我正在尝试使用比较操作的结果添加到 SSE 变量 我刚刚意识到 当使用 mm cmplt ps如果结果为真 则操作将返回 NAN 因为无法表示 0xffffffff 这对我来说没有用 m128 va m128 vb m128 result
  • .NET Core 3.1 始终加密

    使用 NET Core 3 1 和 SQL Always Encrypted 时出现以下错误 3 1支持这个吗 也许我在这里遗漏了一些东西 Keyword not supported column encryption setting 目前
  • 是否可以在 Android 中创建某种全局异常处理程序?

    我的应用程序包括一系列活动 用户必须以线性方式进行这些活动 假设这一系列活动如下所示 A 代表主菜单 B C D E 用户的操作过程如下 A gt B gt C gt D gt E 在这些活动中 用户必须输入数据或允许设备自动获取数据 例如
  • 使用 Symfony Messenger 异步发送电子邮件时如何翻译电子邮件?

    我将 Symfony 邮件程序配置为使用 Messenger 发送电子邮件 https symfony com doc current mailer html sending messages async 我的电子邮件有两种语言 我依靠请求
  • 将字符向量转换为时间?

    我想将以下字符向量转换为时间变量 times lt c 9 9 2015 16 03 13 9 9 2015 17 03 13 9 9 2015 17 56 38 9 9 2015 17 57 29 9 9 2015 19 52 55 9
  • 如何读取 FastCGI 请求的所有输出?

    我正在尝试使用 Rust 向 PHP 文件发出请求FastCGI协议 感谢以下答案 我得到了这段代码我之前的问题 use std os unix net UnixStream use std io Read Write use std st