In Short:
Yes you can do this using the PB CC 5 STDIN LINE command, if all variables remain within set parameters, namely connection speed.
In Long:
From what I can tell IIS 7+ starts the execution of CGI's (using CGIModule) without having received all of the form data. This means that if your CGI out paces the input buffer then that's it, you won't be able to read in more data no matter how long you wait...if you use the STDIN LINE command.
This requires the use of delays, namely SLEEPs. The most efficient way I could find how to do this without an API call is:
Code:
'--------------------------------------------------------------------------------------- ' (RC) needed to account for cgi's called right before midnight (within iWait seconds) ' FUNCTION iCSeconds(BYVAL iNumTicks AS LONG) AS LONG LOCAL iCurrentT AS LONG iCurrentT = TIMER IF iCurrentT < iNumTicks THEN iCurrentT = iCurrentT + (24*60*60) '** add a day of seconds FUNCTION = iCurrentT-iNumTicks END FUNCTION '------------------------------------------------------------------------------ ' ReadCGI returns the input data for the CGI program. ' FUNCTION ReadCGI () AS STRING SELECT CASE Request_Method CASE "GET", "PUT", "HEAD" FUNCTION = ENVIRON$("QUERY_STRING") CASE "POST" LOCAL sPost AS STRING, sPostTemp AS STRING '** try to calculate a decent time to wait for the buffer to initialize SLEEP INT(Content_Length/333)+10 '** ex: per 256k we must wait ~3/4 of a second, ouch LOCAL Ticks AS LONG, iWait AS LONG Ticks = TIMER '** need to delay so Server's STDIN can catchup to the program iWait = 12 '** # of seconds you plan to wait for the cgi to read-in all the form data '** keep looping if we haven't gotten all of the data for up to iWait seconds '** remember that this 12 seconds will be in addition to the initial SLEEP above '** you can change this if needed, but 12 seconds should allow for up to 1.4 meg '** atleast that was the case in my scenario: Win 2008 R2 (64bit) IIS7.5 on a virtual server '** after testing my guess is that this will vary widely on user's connection speed as well WHILE LEN(sPost) < Content_Length AND iCSeconds(Ticks) < iWait SLEEP 100 STDIN LINE sPostTemp sPost = sPost + RTRIM$(sPostTemp,CHR$(26)) WEND FUNCTION = sPost CASE ELSE FUNCTION = COMMAND$ END SELECT END FUNCTION
If you are using small forms (anything under 16k) then you don't really need this. Put a small delay (maybe 2/10's of a second...SLEEP 200) before your STDIN LINE and it should be fine. However if you are building a CGI to release to clients, or that will have to handle varying form data sizes then you need to use the API ReadFile call (in my opinion).
The ultimate solution, to handle all situations, would to be able to know/read when the buffer has received all the content. I tried geting the STDIN handle and opening it as a file (as a BINARY and as an INPUT) hoping to get a LOF reading, but as the PB instructions state; this only works with disk files.
Anyone know of a way to check the buffer size waiting in the input? This way we could just check/wait/check again until all of it is there then read everything with as many STDIN LINE commands as needed (with no guessing on the delays).
Anyway, the main reason why the API ReadFile function works (all the time) is that it can give you less than 16k and/or read EOF and still give you data the next time you call it (meaning the EOF changes state).
Below is the updated Windows API ReadFile solution I'm using:
Code:
#IF %DEF(%WINAPI) '--------------------------------------------------------------------------------------- '** (RC) needed to account for cgi's called right before midnight (within iWait seconds) ' FUNCTION iCSeconds(BYVAL iNumTicks AS LONG) AS LONG LOCAL iCurrentT AS LONG iCurrentT = TIMER IF iCurrentT < iNumTicks THEN iCurrentT = iCurrentT + (24*60*60) '** add a day of seconds FUNCTION = iCurrentT-iNumTicks END FUNCTION '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Thanks Dave N and Don D. ~~~~~~~~~~~~~~~~~~~~~~~~~~ ' webdataStdIn ' Replacement for StdIn Line to read standard input. ' StdIn Line has a limit ' *edited by Rusty Camus(RC) on 2/11/2011 to deal with server IO delays on IIS7+ '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ FUNCTION webdataStdIn() AS STRING DIM hInput AS LONG DIM iRead AS LONG DIM iResult AS LONG DIM sBuffer AS STRING DIM sOutBuffer AS STRING hInput = GetStdHandle(%STD_INPUT_HANDLE) IF hInput THEN DIM iTicks AS LONG, iWait AS LONG '** (RC) needed delay so Server's STDIN could catchup to the cgi iTicks = TIMER iWait = 11 '** (RC) # of seconds you plan to wait for the cgi to read-in all the form data '** depending on amount of data you plan to receive you may need to change '** this can also be used to protect from someone overloading the cgi/server '** supposedly there is no limit on POST data size, this should allow up to ~2 meg '** another way to protect would be to deny the read when Content_Length is too high WHILE LEN(sOutBuffer) < Content_Length AND iCSeconds(iTicks) < iWait '** (RC) sBuffer = SPACE$(Content_Length + 1024) '** (RC) padded to be safe IF LEN(sOutBuffer) THEN SLEEP 75 '** (RC) this extra time gave close to max efficiency in my scenario iResult = ReadFile(hInput, BYVAL STRPTR(sBuffer), Content_Length + 1, iRead, BYVAL %NULL) '** (RC) '- If there was an error, return nothing IF iResult = 0 THEN EXIT LOOP ELSE sOutBuffer = sOutBuffer + LEFT$(sBuffer, iRead) END IF WEND END IF FUNCTION = sOutBuffer END FUNCTION #ENDIF '------------------------------------------------------------------------------ ' ReadCGI returns the input data for the CGI program. ' FUNCTION ReadCGI () AS STRING SELECT CASE Request_Method CASE "GET", "PUT", "HEAD" FUNCTION = ENVIRON$("QUERY_STRING") CASE "POST" LOCAL sPost AS STRING #IF NOT %DEF(%WINAPI) SLEEP 200 '** (RC) should work fine for reads less than 16k STDIN LINE sPost #ELSE sPost = webdataStdIn() #ENDIF FUNCTION = sPost CASE ELSE FUNCTION = COMMAND$ END SELECT END FUNCTION MACRO cgiRead = ReadCGI
Leave a comment: