Announcement

Collapse
No announcement yet.

Server 2003 / Server 2008 CgiIn

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Server 2003 / Server 2008 CgiIn

    I have the following HTML code that when clicked will send a Cgi string to the app tdjclg.cgi when usin Server 2003. However the identical HTML when served by Server 2008 returns nothing, the function ReadCgi = ""

    Code:
    <div id="top_bar">
    <div class="top_bar_right">
    <form action="/catalogue/tdjclg.cgi" method="post" class="inline">
    <input type="hidden" name="fty" value="qss">
    <label for="top_bar_search_field">Quick Search : </label>
    <input type="hidden" name="r" value="e">
    <input type="text" Name="qs" id="top_bar_search_field">
    <input type="image" src="/images/global/header/go_button.gif" alt="search go" id="top_bar_search_go" />
    </form>

  • #2
    With IIS 7 (Vista, Server 2008), you need to enable the CGI role first.

    See http://www.wrensoft.com/zoom/support/faq_cgi_iis.html

    Comment


    • #3
      CGI is already installed. The CGI app will execute and display the form but when data is entered and submitted, no data is received.

      Comment


      • #4
        OK Made a simple test program

        Code:
        '
        ' Cgi Read Test
        '
        #INCLUDE "pbcgi.inc"
        
        FUNCTION PBMAIN
        '
        LOCAL CgiIn AS STRING
        '
        CgiIn = ReadCgi
        '
        STDOUT "Content-type: text/html"
        STDOUT 
        STDOUT "<html>"
        STDOUT "<head>"
        STDOUT "<title>Test</title>"
        STDOUT "</head>"
        STDOUT 
        STDOUT "<body>"
        STDOUT TIME$ + "<br>"
        STDOUT "Cgi Read As :"+CgiIn+":<br><br>"
        STDOUT "<form action=""test.cgi"" method=""post"">" 
        STDOUT "<input type=""hidden"" name=""fty"" value=""qss"">" 
        STDOUT "Quick Search"
        STDOUT "<input type=""hidden"" name=""r"" value=""e"">" 
        STDOUT "<input type=""text"" Name=""qs"">" 
        STDOUT "<input type=""submit"" value=""go"" />" 
        STDOUT "</form>" 
        STDOUT 
        STDOUT "</body>"
        STDOUT "</html>"
        
        END FUNCTION
        I enter the text "test"

        When called on Windows 2003 server the page displays

        Code:
        20:29:06
        Cgi Read As :fty=qss&r=e&qs=test:
        And from Server 2008 the page displays

        Code:
        20:31:06
        Cgi Read As ::

        Comment


        • #5
          OK after more testing..

          I have XP machines on the local LAN and the do actually return the CGI read as expected. Most of my testing is RDP to a VMWare XP machine and for some reason this does not return the Cgi string. I have no idea why.

          Comment


          • #6
            Solution for STDIN issues on 2008 r2 IIS 7 and above

            I've been having this problem for a few months, but mine was random. I migrated from an old ms 2000 server to a 2008 R2 (64 bit) running IIS 7.5. What I thought was a file locking/release issue (due to server side includes) actually turned out to be random sizes of the input buffer being collected from form data. Well not totally random (sizes of 0, 704, 1460, and 2164 bytes), but about 99% of the time it was less than what was expected. If you submitted 2164 bytes or under you could actually get all of the form data about 50% of the time. To see what I'm talking about try the 2 top cgi form versions (the bottom 2 are GETs) at: http://www.camus.com/pbcgitest.html This happens only with the POST method. My theory is that the IO is not keeping up with the cgi on the new IIS servers (I have no idea why or how the cgi is launched without all the data waiting in the que) and that the PB CC 5.0 (compiler I'm using) STDEOF command (tried looping STDIN LINE) appears to be reporting a false End of File. If you submit your forms with the GET method things work fine. This is what I did temporarily...using short variable names to get under the size limitation (~2k). Not a good thing for big forms with lots of hidden variables.

            There is a solution...You have to use the Win32API readfile function plus keep checking the input buffer for up to 2 seconds even though it is saying it has nothing left. When I say "up to" I mean that I won't wait any longer than that...if I get all the data I'm expecting (= Content_Length) then I move on, no need to wait. Two seconds worked everytime for me, but you can bump it if needed. I don't think 1 second will work because it could be much less based on when your machine grabbed the TIMER (could be as little as 1 machine cycle in rare instances, then you'd be right back where you started).

            The solution was tested up to about 280k of content length. After that the cgi locks up (had to kill the process on the web server). I didn't go any further, but I'm guessing there is a max buffer setting in IIS or CGIModule for this. 256k+ is plenty enough for what I need to do. One suggestion is to go and reduce your CGI time-out setting in IIS Manager, I moved mine from 15 minutes to 15 seconds. If people make multiple calls to these cgi's they could hang up the system until they time out.

            Hats off to Dave N. and Don D., if I wouldn't have found this page (http://www.powerbasic.com/support/te.../pbcgiboth.txt) it would have taken me much longer I'm sure.

            If you are using PBCGI.inc replace all of the ReadCGI() function with the code below (leave the cgiread macro as it is):

            Code:
            #IF %DEF(%WINAPI)
            '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Thanks Dave N and Don D. ~~~~~~~~~~~~~~~~~~~~~~
            ' webdataStdIn
            ' Replacement for StdIn Line to read standard input.
            ' StdIn Line has a limit
            ' *edited by Rusty Camus on 2/11/2011 to deal with server IO delays on IIS7+
            '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            FUNCTION webdataStdIn() AS STRING
            
              DIM hInput AS LONG
              DIM iRead AS LONG, iReadTot 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  '** needed delay so Server's STDIN could catchup to the cgi
                iTicks = TIMER
                iWait = 2    '** 2 seconds worked everytime for me, depending on server you may need to fiddle
                WHILE iReadTot < Content_Length AND ABS(TIMER-iTicks) < iWait
                  sBuffer = SPACE$(Content_Length + 1024)     ' ** padded to be safe
                  iResult = ReadFile(hInput, BYVAL STRPTR(sBuffer), Content_Length + 1, iRead, BYVAL %NULL)
            
                  '- If there was an error, return nothing
                  IF iResult = 0 THEN
                    EXIT LOOP
                  ELSE
                    sOutBuffer = sOutBuffer + LEFT$(sBuffer, iRead)
                    iReadTot = iReadTot + 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)
                     STDIN LINE sPost
              #ELSE
                     sPost = webdataStdIn()
              #ENDIF
                    FUNCTION = sPost
            
                CASE ELSE
                    FUNCTION = COMMAND$
            
                END SELECT
            
            END FUNCTION
            Russell P. Camus

            Comment


            • #7
              STDEOF just tells you that there's no more data. It can't tell you whether more data will be available later. Using a timeout is likely to be a perfectly appropriate solution, in this case. You don't need to use API calls at all.

              Comment


              • #8
                I did try the STDIN LINE with a timed loop checking for up to as much as 10 seconds and the rest of the data never made it. I forced the issue anyway and the STDIN LINE was returning chr$(26)'s, which I have to rtrim$ away (tried it without trimming also). I also tried putting 1/2 second SLEEPs in the loop. The version of the cgi I have up now at the url I gave checks for 5 seconds and uses 1/2 second SLEEPs (you will notice the delay), but still the same result. Also, I did try it with the PBCGI.ini left exactly as it was in the samples\internet\cgi folder...that was first.

                It could just be this particular environment...this all ran pefectly under 2000 Advanced Server. It could also be my setup, as I coudn't find a solution to my problem on the interent...and I looked a long time. That's the worse kind, random and noone else seems to have exactly the same issue.
                Russell P. Camus

                Comment


                • #9
                  This time issue stuff got me to thinking some more so I did numerous tests using both methods to see if it was possible to do without using API calls.

                  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
                  Since both the PB STDIN LINE and the API ReadFile functions return a max of 16k per read you could theoretically calculate the amount of time you need to pause by deviding Content_Length by 16k and adding that to the intial SLEEP before the loop (and take out those in the loop). Again, just so the STDIN LINE reads don't catch up to the buffer, basically an EOF, meaning it's read a CTRL Z (chr$(26)). Once this happens every subsequent STDEOF will be TRUE and every STDIN LINE will return CTRL Z...no matter how long you SLEEP. (Note: also strange is the fact that everytime STDIN LINE returned anything less than 16k (16,384) bytes, then every read after gave CTRL Z, and STDEOF read TRUE.) This means that if the data is late (user has a very slow internet connection, server gets bogged down) in filling the buffer then the SLEEP delays I setup in the above code could still fail. A delay is needed for consistent results in any scenario. If I take out the delay(SLEEP) entirely then the CGI fails often, with the frequency increasing along with the size of the data sbmitted with the form. Meaning a small (500 byte) form may work about 90% of the time and anything over 50k fails ~90% of the time.

                  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
                  Russell P. Camus

                  Comment

                  Working...
                  X