* Experimental CGI program using only HP COBOL II/iX source code * Lars Appel, 25-Mar-98 / 11-Apr-98 (inspired by iprof 98 visit) * This program creates a very simple CGI response by echoing * a few parameters and then writing a number of lines back to * the client. It handles http Method GET and Method POST. * GET example URL is http://your.server/cgi-bin/cobcgi?nnn * where nnn is the loop count (in range 1 through 1000) * POST example URL is http://your.server/cgi-bin/cobcgi * with HTML form sending the loop count and and optional * text message similar to "mytext=bla+bla&myloop=100". * Compile with * COB85XL source,object [,$null] * Link with * LINK object, ./cobcgi ;rl=/lib/libc.a ;posix ;share * Test GET in Shell * export REQUEST_METHOD=GET * export QUERY_STRING=10 * ./cobcgi | cat -v * Test POST in Shell * export REQUEST_METHOD=POST * export CONTENT_LENGTH=22 * echo "mytext=hello&myloop=10" | ./cobcgi | cat -v $control post85 identification division. program-id. demo. environment division. configuration section. special-names. symbolic characters NUL is 1, LF is 11, CR is 14. data division. working-storage section. * buffer for composing output and calling print intrinsic 01 print-buf pic x(8000). 01 print-pos pic s9(9) comp value 1. 01 print-len pic s9(9) comp. * buffers for reading env vars via getenv() from Posix C lib 01 env-name pic x(80). 01 env-pointer pic s9(9) comp. 01 env-text pic x(80). 01 env-length pic s9(9) comp. * counter for the test loop writing lines back to client 01 loop-count pic 9(9). * buffers for reading client input from stdin (Method POST) 01 post-length pic s9(9) comp. 01 post-buf pic x(80). 01 post-get pic s9(9) comp. 01 post-got pic 9(9). 01 post-pos pic s9(9) comp. * buffers for extracting "field1=value1&field2=value2..." 01 field-name pic x(80). 01 field-name-len pic s9(9) comp. 01 field-value pic x(80). 01 field-value-len pic s9(9) comp. * message for the test loop (only passed with Method POST) 01 test-msg pic x(80). 01 test-msg-len pic s9(9) comp. * MAIN PROGRAM * First send http header to signal HTML type document, then send * some level one HTML headline, branch to body handler depending * on REQUEST_TYPE and finally add footing to the HTML page, which * also helps to emphasise the end-of-test-page visually. procedure division. main-body. string "Content-Type: text/html" CR LF CR LF delimited by size into print-buf with pointer print-pos perform my-print string "

Simple COBOL CGI Demo

" CR LF delimited by size into print-buf with pointer print-pos perform my-print string "REQUEST_METHOD" NUL delimited by size into env-name perform my-getenv evaluate env-text when "GET" perform handle-get when "POST" perform handle-post when other perform handle-err end-evaluate string "
Lars Appel, April 1998
" CR LF delimited by size into print-buf with pointer print-pos perform my-print stop run. * HANDLE-GET: Handle an http Method GET request from client * First retrieve the QUERY_STRING Posix env var and echo it back * to the client. Then check the value and adjust to proper range * if appropriate. Finally perform loop to send test lines back to * the client (hey, this is not meant to be a fancy web page...) handle-get. string "QUERY_STRING" NUL delimited by size into env-name perform my-getenv if env-length = 0 then string "

GET Method needs valid QUERY_STRING

" CR LF delimited by size into print-buf with pointer print-pos perform my-print else string "

GET Method received QUERY_STRING as " env-text (1:env-length) "

" CR LF delimited by size into print-buf with pointer print-pos perform my-print compute loop-count = function numval (env-text) if loop-count < 1 then move 1 to loop-count string "

Loop Count was less than one

" CR LF delimited by size into print-buf with pointer print-pos perform my-print end-if if loop-count > 1000 then move 1000 to loop-count string "

Loop Count was greater than 1000

" CR LF delimited by size into print-buf with pointer print-pos perform my-print end-if * Now return the (loop-count) lines to the web client string "
" CR LF
               delimited by size into print-buf with pointer print-pos
             perform my-print

             perform with test before until loop-count < 1
               string "Test message loop countdown at " loop-count CR LF
                 delimited by size into print-buf with pointer print-pos
               perform my-print
               subtract 1 from loop-count
             end-perform

             string "
" CR LF delimited by size into print-buf with pointer print-pos perform my-print end-if. * HANDLE-POST: Handle an http Method POST request from client * First retrieve the CONTENT_LENGTH Posix env var and echo it back * to the client. Then read that number of bytes from stdin, unless * it is more than our buffer can hold, and echo it back to the web * page. Parse the input string for fields named MYTEXT and MYLOOP, * supplying default values in case they are missing. Finally send * output lines with MYTEXT according to the MYLOOP count. * Notice that the output loop does not call the PRINT intrinsic * for every line but tries to combine as many lines as possible * in the (relatively large) output buffer. This might improve the * performance and reduce the resource consumption. The loops in * handle-post and handle-get have been implemented differently to * allow comparing them easily (try 1000 loops, for example). handle-post. string "CONTENT_LENGTH" NUL delimited by size into env-name perform my-getenv string "

POST Method received CONTENT_LENGTH as " env-text (1 : env-length) "

" CR LF delimited by size into print-buf with pointer print-pos perform my-print compute post-length = function numval (env-text) if post-length > function length (post-buf) then string "

Too much data for my POST buffer

" CR LF delimited by size into print-buf with pointer print-pos perform my-print else compute post-get = - post-length call intrinsic "readx" using post-buf , post-get giving post-got string "

Received " post-got " bytes with " post-buf (1 : post-got) "

" CR LF delimited by size into print-buf with pointer print-pos perform my-print * Use defaults if MYLOOP and/or MYTEXT missing in HTML Form move 10 to loop-count move "Default Test Message" to test-msg move 20 to test-msg-len * Terminate the buffer string for easier parsing loop move "&" to post-buf (post-got + 1 : 1) * Begin parsing to extract MYLOOP and MYTEXT fields move 1 to post-pos perform with test before until post-length <= 0 initialize field-name initialize field-value unstring post-buf delimited by "=" into field-name count in field-name-len with pointer post-pos unstring post-buf delimited by "&" into field-value count in field-value-len with pointer post-pos evaluate field-name when "mytext" move field-value to test-msg move field-value-len to test-msg-len when "myloop" compute loop-count = function numval (field-value) end-evaluate subtract field-name-len from post-length subtract field-value-len from post-length subtract 2 from post-length end-perform * Now return the (loop-count) lines to the web client, * but utilize our buffer to use fewer calls to PRINT string "
" CR LF
               delimited by size into print-buf with pointer print-pos
             perform my-print

             perform with test before until loop-count <= 0

               if print-pos
                 + function length ("Countdown at ")
                 + function length (loop-count)
                 + function length (" with message ")
                 + test-msg-len + 2
                 > function length (print-buf)
               then
                 perform my-print
               end-if

               string "Countdown at " loop-count
                 " with message " test-msg (1 : test-msg-len) CR LF
                 delimited by size into print-buf with pointer print-pos

               subtract 1 from loop-count

             end-perform

             perform my-print

             string "
" CR LF delimited by size into print-buf with pointer print-pos perform my-print end-if. * HANDLE-ERR: Return error to client for unknown REQUEST-METHOD handle-err. string "

Unknown REQUEST_METHOD " env-text (1 : env-length) " received

" CR LF delimited by size into print-buf with pointer print-pos perform my-print. * MY-PRINT: send print buffer to client by calling intrinsic * assumes print-buf has been composed with string verb using * print-pos as "cursor" pointer. computes length parm for the * intrinsic call and resets print-pos to 1 for convenience. * notice that this version does not check condition code (ouch) my-print. compute print-len = - ( print-pos - 1 ) if print-len <> 0 then call intrinsic "print" using print-buf , print-len , 0 end-if move 1 to print-pos. * MY-GETENV: retrieve Posix env var using Posix C lib functions * getenv() returns pointer to C string, strlen() determines * string length and strncpy() copies appropriate number of * characters to a buffer var in our working-storage section. * notice that this version silently clips "too long" input * and returns a zero-length string for undefined env vars. my-getenv. initialize env-text call "getenv" using env-name giving env-pointer if env-pointer <> 0 then call "strlen" using \env-pointer\ giving env-length if env-length > function length(env-text) then compute env-length = function length(env-text) end-if call "strncpy" using env-text , \env-pointer\ , \env-length\ else move 0 to env-length end-if. end program demo.