PHP-CGI 帖子为空


PHP-CGI post empty

我正在编写一个Java CGI客户端来与PHP-CGI通信,但是我遇到了一个颠簸,我的php $_POST没有填充任何数据,而我肯定会发送一些数据。我不知道它为什么要这样做,我在任何地方都找不到这个问题。

我直接从 windows.php.net 使用干净的 php 二进制文件,无需任何编辑。

这是我现在用来测试的代码:

public static void main(String[] args)
{
    HashMap<String, String> map = new HashMap<String, String>();
    String body = "data=Foo+Bar";
    String queryString = "yes=no&a=b";
    String requestMethod = "POST";
    String contentType = "application/x-www-form-urlencoded";
    String contentLength = Integer.toString( body.length() );
    String docRoot =  "D:/http test";
    String scriptName = "/index.php";
    String scriptFileName = docRoot + scriptName;
    String pathInfo = "";
    String pathTranslated = docRoot + pathInfo;
    String requestUri = scriptName + pathInfo + ("?" + queryString);
    String documentUri = scriptName + pathInfo;
    String serverProtocol = "HTTP/1.1";
    String gatewayInterface = "CGI/1.1";
    map.put( "QUERY_STRING" , queryString);
    map.put( "REQUEST_METHOD" , requestMethod);
    map.put( "CONTENT_TYPE" , contentType);
    map.put( "CONTENT_LENGTH" , contentLength);
    map.put( "SCRIPT_FILENAME" , scriptFileName);
    map.put( "SCRIPT_NAME" , scriptName);
    map.put( "PATH_INFO" , pathInfo);
    map.put( "PATH_TRANSLATED" , pathTranslated);
    map.put( "REQUEST_URI" , requestUri);
    map.put( "DOCUMENT_URI" , documentUri);
    map.put( "DOCUMENT_ROOT" , docRoot);
    map.put( "SERVER_NAME" , "localhost" );
    map.put( "SERVER_PROTOCOL" , serverProtocol);
    map.put( "GATEWAY_INTERFACE" , gatewayInterface);
    Client c = new Client( "127.0.0.1" , 8090 );
    System.out.println("'n" + c.doRequest( map , body ));
}

客户端.java:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.Map.Entry;
public class Client
{
    private Socket socket;
    public Client( String host, int port ) throws UnknownHostException, IOException
    {
        socket = new Socket( host, port );
    }
    public String doRequest( Map<String, String> params, String content ) throws IOException
    {
        ByteArrayOutputStream paramBytes = new ByteArrayOutputStream();
        for ( Entry<String, String> param: params.entrySet() )
            paramBytes.write( nvpair( param.getKey() , param.getValue() ) );
        Packet beginRequest = new Packet( FCGI.BEGIN_REQUEST, FCGI.NULL_REQUEST_ID, new byte[] { 0, FCGI.RESPONDER, FCGI.KEEP_CONN, 0, 0, 0, 0, 0 } );
        Packet requestParams = new Packet( FCGI.PARAMS, FCGI.NULL_REQUEST_ID, paramBytes.toByteArray() );
        Packet requestContent = new Packet( FCGI.STDIN, FCGI.NULL_REQUEST_ID, content.getBytes( "UTF-8" ) );
        OutputStream stream = socket.getOutputStream();
        stream.write( beginRequest.getBytes() );
        stream.write( requestParams.getBytes() );
        stream.write( requestContent.getBytes() );
        return readResponse();
    }
    private String readResponse() throws IOException
    {
        InputStream stream = socket.getInputStream();
        // TODO buffering
        String out = null;
        for ( Packet p = new Packet( stream ); p.getType() != FCGI.END_REQUEST; p = new Packet( stream ) )
        {
            System.out.print( p.getType() + ", " );
            if ( p.getType() == FCGI.STDOUT )
                out = new String( p.getContent() );
        }
        return out;
    }
    public byte[] nvpair( String name, String value )
    {
        try
        {
            int nl = name.length();
            int vl = value.length();
            ByteArrayOutputStream bytes = new ByteArrayOutputStream( nl + vl + 10 );
            if ( nl < 128 )
                bytes.write( b( nl ) );
            else
                bytes.write( new byte[] { b( nl >> 24 ), b( nl >> 16 ), b( nl >> 8 ), b( nl ) } );
            if ( vl < 128 )
                bytes.write( b( vl ) );
            else
                bytes.write( new byte[] { b( vl >> 24 ), b( vl >> 16 ), b( vl >> 8 ), b( vl ) } );
            bytes.write( name.getBytes( "UTF-8" ) );
            bytes.write( value.getBytes( "UTF-8" ) );
            return bytes.toByteArray();
        }
        catch( IOException e )
        {
            e.printStackTrace();
        }
        return null;
    }
    public byte b( int i )
    {
        return (byte) i;
    }
}

索引.php:

<pre><?php
    echo "Hello World'n";
    echo "REQUEST = "; print_r($_REQUEST);
    echo "GET = "; print_r($_GET);
    echo "POST = "; print_r($_POST);
    echo php_ini_loaded_file(), "'n";
    echo file_get_contents("php://input"), "'n";
    echo php_sapi_name();
?></pre>

他的是我得到的结果:

6, 
X-Powered-By: PHP/5.6.3
Content-type: text/html; charset=UTF-8
<pre>Hello World
REQUEST = Array
(
    [yes] => no
    [a] => b
)
GET = Array
(
    [yes] => no
    [a] => b
)
POST = Array
(
)
D:'Programs'PHP 5.6.3 x64 TS'php.ini
cgi-fcgi</pre>

我想要的是让$_POST包含Array( [data] => Foo Bar )(这就是我要发送的内容(。

有谁知道我如何修复它,以便$_POST也填充数据?

好吧,如果你看一下FastCGI规范第3.3章的副标题"记录类型",它说:

流记录

是流的一部分,即流类型的一系列零条或多个非空记录(长度 != 0(,后跟流类型的空记录(长度 == 0(。

这意味着正确的流由至少一个非空数据包组成,由相同流类型的空数据包终止。

您还可以查看附录 B(典型协议消息流(,了解其外观。

这与你的问题有什么关系?

PHP 期望一个或多个非空"写入"到FCGI_PARAMS流,然后,为了标记参数流的结束,一个空的FCGI_PARAMS数据包。

如果您查看doRequest方法,则您正在写入一个非 emtpy FCGI_PARAMS 数据包,然后直接写入FCGI_STDIN数据包。

PHP 所做的是读取您的第一条FCGI_PARAMS记录,然后如果您的FCGI_STDIN数据包到达,它只会读取标头并停止。因此,下一个fcgi_read读取无效数据并静默失败 - 但 PHP 继续使用空的 STDIN-stream 处理请求。或者,如果你的请求正文太小(如 body = "a"(,PHP 将永远阻塞。

如果您修改doRequest方法并以空写入终止流(我称它们为 EOF 数据包(,它应该可以工作:

public String doRequest( Map<String, String> params, String content ) throws IOException
{
    ByteArrayOutputStream paramBytes = new ByteArrayOutputStream();
    for ( Entry<String, String> param: params.entrySet() )
        paramBytes.write( nvpair( param.getKey() , param.getValue() ) );
    Packet beginRequest = new Packet( FCGI.BEGIN_REQUEST, FCGI.NULL_REQUEST_ID, new byte[] { 0, FCGI.RESPONDER, FCGI.KEEP_CONN, 0, 0, 0, 0, 0 } );
    Packet requestParams = new Packet( FCGI.PARAMS, FCGI.NULL_REQUEST_ID, paramBytes.toByteArray() );
    Packet requestParamsEOF = new Packet( FCGI.PARAMS, FCGI.NULL_REQUEST_ID, new byte[] {} );
    Packet requestContent = new Packet( FCGI.STDIN, FCGI.NULL_REQUEST_ID, content.getBytes( "UTF-8" ) );
    Packet requestContentEOF = new Packet( FCGI.STDIN, FCGI.NULL_REQUEST_ID, new byte[] {} );
    OutputStream stream = socket.getOutputStream();
    stream.write( beginRequest.getBytes() );
    stream.write( requestParams.getBytes() );
    stream.write( requestParamsEOF.getBytes() );        
    stream.write( requestContent.getBytes() );
    stream.write( requestContentEOF.getBytes() );      
    return readResponse();
}
这样做

的原因是您可以将大型流拆分为多个数据包(因为每个数据包只能容纳 64 KiB 的有效负载(。从理论上讲,您必须检查请求正文的长度并将其拆分为 64 KiB(2^16 字节(的块。因此,即使是这个版本的doRequest也可以改进。