Search code examples
phpjsonoutput-buffering

PHP ob_get_length seems to return incorrect value


I have a function that should return JSON responses to the client and then carry on processing without locking up the client, this seems to work fine unless I add the Content-Length header (which I'd like to do to ensure the client is freed up). The function is:

function replyAndCarryOn($responseText){
    ignore_user_abort(true);//stop apache killing php
    ob_start();//start buffer output
    echo $responseText;
    session_write_close();//close session file on server side to avoid blocking other requests
    header("Content-Encoding: none");
    header('Content-Type: application/json; charset=utf-8');                   
    header("Content-Length: ".ob_get_length());
    header("Connection: close");
    ob_end_flush();
    flush();
}

This works fine except the JSON string returned to the browser is truncated, due to the Content-Length being wrong. For example the following string

{"result":"AUTH","authList":[{"uid":"Adam","gid":"Users","authid":1}, "uid":"Admin","gid":"Admin","authid":2},{"uid":"George","gid":"Users","authid":3},{"uid":"test","gid":"Users","authid":4}],"id":"Payment"}

will turn up as:

{"result":"AUTH","authList":[{"uid":"Adam","gid":"Users","authid":1},{"uid":"Admin","gid":"Admin","authid":2},{"uid":"George","gid":"Users","authid":3},{"uid":"test","gid":"Users","authid":4}],"id":"

I could just leave the Content-Length header out and apache (2.2) will automatically add the 'Transfer-Encoding:"chunked"' header which seems to work, but I'd like to get to the bottom of why ob_get_length doesn't return the value I need, I understand it can produce results that are too long if gzip is enabled but I'm seeing the opposite where the value is too short.

So I'd like to know:

a) what am I doing wrong in getting the content length?

b) is there any issue leaving it out?

Following the comment by @Xyv it seems the server outputs a new line and eight spaces before the output string, however this is not included in the ob_get_length return. Embarrassingly it turns out it was a carriage return and eight spaces somehow added before the first php tag.


Solution

  • I thought I'd post this as answer for better readability.

    Maybe first capture the output, then make the headers, and then the body?

    For me this example works:

    <?php
    function replyAndCarryOn($responseText){
        ignore_user_abort(true);//stop apache killing php
        ob_flush( );
        ob_start( );//start buffer output
        echo $responseText;
        session_write_close();//close session file on server side to avoid blocking other requests
        header("Content-Encoding: none");
        header('Content-Type: application/json; charset=utf-8');                   
        header("Content-Length: ".ob_get_length());
        header("Connection: close");
        echo ob_get_flush();
        flush();
    }
    
    
    replyAndCarryOn(  '{"result":"AUTH","authList":[{"uid":"Adam","gid":"Users","authid":1}, "uid":"Admin","gid":"Admin","authid":2},{"uid":"George","gid":"Users","authid":3},{"uid":"test","gid":"Users","authid":4}],"id":"Payment"}'  );
    ?>
    

    Update Important to know would be that ob_start() should come before outputting any body. The buffers will miss this data, and there fore it will probably not be counted. I am not an expert with output buffering, but ensure to place ob_start in the beginning of your script, this way it will be hard(er) to make any mistakes. (So be carefull placing spaces and/or Tabs before your initial <?php).