简体   繁体   中英

PHP-CGI post empty

I'm writing a Java CGI client to talk to PHP-CGI but I've hit a bump, my php $_POST is not filling with any data while I'm definitely sending some. I have no idea why it's doing this and I can't find this problem anywhere.

I use clean php binaries directly from windows.php.net without any edits.

Here's the code I'm using to test right now:

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 ));
}

Client.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;
    }
}

index.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>

And his is the result I'm getting:

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>

What I want is for $_POST to contain Array( [data] => Foo Bar ) (which is what I'm sending).

Does anyone know how I can fix it so that $_POST also fills with data?

Well if you look at the FastCGI-spec chapter 3.3 under the subtitle "Types of Record Types" it says:

A stream record is part of a stream, ie a series of zero or more non-empty records (length != 0) of the stream type, followed by an empty record (length == 0) of the stream type.

That means a proper stream is composed of at least one non-empty packets, terminated by an empty packet of the same stream type.

You can also check out Appendix B (Typical Protocol Message Flow) for how that should look.

How is that relevant to your problem?

PHP expects one or many non-empty "writes" to the FCGI_PARAMS stream and then, to mark the end of the params-stream, an empty FCGI_PARAMS packet.

If you look at your doRequest method you are writing one non emtpy FCGI_PARAMS packet and then directly after that your FCGI_STDIN packet.

What PHP does is it reads your first FCGI_PARAMS record and then if your FCGI_STDIN packet arrives it only reads the header and stops. So the next fcgi_read reads invalid data and fails silently - but PHP continues to handle the request with an empty STDIN-stream. Alternatively, if your request body is too small (like body = "a"), PHP will block for ever.

If you modify your doRequest method and terminate the stream with an empty write (I'll call them EOF-packets), it should work:

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();
}

The reason it is done that way is so you can split up large streams in multiple packets (since each packet can only hold a payload of 64 KiB). Theoretically you'd have to check the length of the request body and split it up in chunks of 64 KiB (2^16 bytes). So even this version of doRequest could be improved.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM