Project

General

Profile

[Solved] CGI (ash) output always buffered

Added by jens-maus about 6 years ago

Hi lighttpd users and developers,

I am currently facing a problem where I am seeking for some help regarding CGI output buffering. I have tried to search through the web and this fora but didn't find a suitable answer nor solution for my problem.

I am developing an application here were I provide users a html website (served by lighttpd 1.4.48) to upload a file which then will be processed and progress output sent back accordingly. For the html part I am using the following form-data definition:

<form name="frmUpload" action="cgi-bin/firmware_upload.cgi" method="post" enctype="multipart/form-data">
  <input type="submit" value="Submit" >
</form>

The cgi script in question is the following:

#!/bin/sh

if [[ "$1" != __UNBUFFERED__ ]]; then
    prog="$0" 
    /usr/bin/stdbuf --output=0 --error=0 "$prog" __UNBUFFERED__ "$@" 
    exit $?
fi

shift #discard __UNBUFFERED__

echo -ne "Content-Type: text/plain\r\n\r\n" 
echo -ne "Receiving Uploaded File.. " 

# fake read boundary+disposition, etc.
read boundary
read disposition
read ctype
read junk

# get length
a=${#boundary}
b=${#disposition}
c=${#ctype}

# Due to \n\r line breaks we have 2 extra bytes per line read,
# 6 + 2 newlines == 10 junk bytes
a=$((a*2+b+c+d+10))

# extract all params from QUERY_STRING
eval $(echo ${QUERY_STRING//&/;})

# write out the data
SIZE=$((HTTP_CONTENT_LENGTH-a))
filename=$(mktemp -p /usr/local/tmp)
head -c $SIZE >${filename}

echo -ne "$(stat -c%s ${filename}) bytes received.\r\n" 

echo -ne "Calculating SHA256 checksum: "               
CHKSUM=$(/usr/bin/sha256sum ${filename})               
if [ $? -ne 0 ]; then                                  
  echo -ne "ERROR (sha256sum)\r\n"                     
  exit 1                                               
fi                                                     
echo -ne "$(echo ${CHKSUM} | awk '{ print $1 }')\r\n" 

As you can see, this script is already using some mechanism (using stdbuf) to provide stdout/stderr output unbuffered so that lighttpd should receive response data (Content-Type and echo output) right away without having to wait until the shell itself is clearing its buffers. The actual part that shows a problem is, that after the cgi script has written out all stdin data successfully to ${filename} it actually calculates a SHA256 checksum which of course takes some time.

The problem is now, that the browser only shows the echoed result text upon the whole script terminates. Until now I was thinking that as soon as the shell script provides stdout data to lighttpd as a response it will immediately send it to the the client (browser) so that it can immediately display it - that's why I have added the 'stdbuf' part at the very top of the script.

I might of course miss some lighttpd config setting to disable output buffering or completely fail to understand why lighttpd is waiting until complete termination of the cgi script before it delivers the output response to the browser. So I would be happy for any hint in how I could resolve the problem so that my application can immediately deliver the response to the browser as users should get some progress information rather than having to wait until the script is completely done.

regards,
jens


Replies (9)

RE: [Solved] CGI (ash) output always buffered - Added by gstrauss about 6 years ago

You missed the streaming options in Docs_ConfigurationOptions.
server.stream-response-body = 1

Another convention for doing this is to fork your script to continue processing in a child process. The main script can then send the response and exit. (Once the main script closes stdout, lighttpd will send a TERM signal to the initial CGI procress.)

RE: [Solved] CGI (ash) output always buffered - Added by jens-maus about 6 years ago

Thanks for your response. However, I have already tried to use "server.stream-response-body = 1" (which I forgot to mention, I am afraid) but without success.

And regarding the suggestion to fork the script so that the script continues processing in a child process. AFAIK then the child process cannot send any further progress output to the browser as soon as the parent exited properly. Or did I miss something?

RE: [Solved] CGI (ash) output always buffered - Added by gstrauss about 6 years ago

If you tried server.stream-response-body = 1, then please verify your config applies that setting to the CGI, and try something like Perl or Python instead of your shell script. Please make sure that your shell script is behaving as you think it is. lighttpd is unable to send things which are not first sent to it.

You are correct that the child process can not send further output once the main CGI exits.

https://stackoverflow.com/questions/3465619/how-to-make-output-of-any-shell-command-unbuffered
Among other things, the page notes that stdbuf does not work on statically linked executables. If I am not mistaken, ash is statically linked. You'll have to use pipe tricks instead, or better, reconsider your choices in scripting language, especially since syntax like eval $(echo ${QUERY_STRING//&/;}) potentially contains multiple exploitable security holes.

RE: [Solved] CGI (ash) output always buffered - Added by jens-maus about 6 years ago

HI again,

I have continued my testing in trying to find a solution for my above mentioned problem in not getting any continues progress information while my cgi upload and processing script is running. Thanks for your comments also about stdbuf not working on statically linked binaries (I didn't keep that in mind).

For testing purposes and to understand the issue more throughoutly I have written a small C-application that should help to demonstrate the problem and remove potential issues hided in the shell output buffering, etc. The source code of this C-application is as follows:

#include <stdio.h>
#include <unistd.h>

void main(void)
{
  setvbuf(stdout, NULL,  _IONBF, 0);

  printf("Content-Type: text/plain\r\n");
  printf("\r\n");
  fflush(stdout);

  printf("TEST\r\n");
  int i=0;
  for(i=0; i < 10; i++)
  {
    printf(".");
    fflush(stdout);
    sleep(1);
  }
  printf("\r\n");
  fflush(stdout);
  printf("DONE\r\n");
}

As you can see I am using setvbuf() and fflush() to ensure that the stdout buffer is flushed as soon as possible so that progress information is outputted throughout each step of using printf() here. I have then compiled this C application and used it as a cgi script instead of the ash shell script for being the form-data cgi script during uploading data via a web browser. However, even with that unbuffered C application lighttpd is not returning me the single dots (.) throughout the 10 seconds of execution of the C application once the file has been uploaded. In fact, the web browser only displays the complete outputted text in one chunk once this C application finished no matter if I have "server.stream-response-body" set to 0 or 1 in lighttpd.conf.

This makes me wonder if lighttpd is really performing correctly as you suggested since this C application should really run completly unbuffered and thus lighttpd should immediately forward the output to the webbrowser. Or do I still miss something here?

RE: [Solved] CGI (ash) output always buffered - Added by jens-maus about 6 years ago

Oh well, I actually found the culprit. It actually is so simple that it is already strange that this should really be the issue :)

All I had to change was NOT to use "Content-Type: text/plain" as the first output string but to use "Content-Type: text/html" instead. I still don't fully understand why this makes the big difference but this actually solved the problem (is there something within lighttpd that it only directly forwards the response to the browser if the content-type of the response is text/html?) and now I continuously get each single printf() outputted immediately. In fact, I just tested it with my ash script and actually this also works now as expected.

RE: [Solved] CGI (ash) output always buffered - Added by jens-maus about 6 years ago

More info: This even gets worse. In fact, it seems to be browser specific as for Chrome/Chromium it seems to be enough that "Content-Type: text/html" is specified in the response while for Firefox Content-Type: text/html; charset=iso-8859-1 is required to let it directly render the response received from the cgi. Well, for Firefox it doesn't seem to be relevant if the content type is text/plain or text/html, but the charset have to be specified. Really awkward and I still don't know why they are behaving so strange regarding receiving continues responses.

RE: [Solved] CGI (ash) output always buffered - Added by gstrauss about 6 years ago

To summarize, there was no bug in lighttpd. Your issue turned out to be you getting fooled by your browser(s). In the future, please remove the browser by testing with telnet or curl with debugging enabled.

RE: [Solved] CGI (ash) output always buffered - Added by jens-maus about 6 years ago

Yes, indeed. Sorry for having bothered you. Hopefully someone else will step over this discussion and learn the same lesson. However, I am still curious why on earth browsers require the content-type to actually render responses right away. However, I guess this is a discussion for a different fora and shouldn't be discussed here.

RE: [Solved] CGI (ash) output always buffered - Added by gstrauss about 6 years ago

I am still curious why on earth browsers require the content-type to actually render responses right away.

The very definition of "Content-Type" is the answer to that question. Without being told the type of content, how should a GUI browser know whether to attempt to render it as text or to pop up a dialog to save a binary or to pop up an application to play it as a video or ... ?

    (1-9/9)