Building on earlier articles COBOL POSIX Sockets, COBOL SSL Sockets and Consuming an XML service with COBOL, we want to enhance our project to handle multiple requests to the XML service, to better handle some errors and HTTP chunking.
Objectives,
- Handle multiple requests read in from SYSIN.
- Process the requests over a single connection to the server–don’t connect/disconnect for each request.
- Interrupt the XML parsing once the data required is found.
- Handle ‘not found’ conditions returned from the server.
- Handle server disconnects by retrying once before failing.
- Handle chunking better.
First cut, the approach is fairly straightforward:
procedure division. perform initialize-ssl-environment open input sysin-fd read sysin-fd into ws-service-uri-address at end set ws-sysin-eof to true end-read perform with test before until ws-sysin-eof if ws-not-connected perform connect-to-server thru connect-to-server-exit end-if perform send-request thru send-request-exit perform read-reply thru read-reply-exit perform parse-geocoderesponse display sysin-address display ws-formatted-address display ' ' read sysin-fd into ws-service-uri-address at end set ws-sysin-eof to true end-read end-perform perform disconnect-from-server perform close-ssl-environment close sysin-fd goback .
This approach will allow the processing of multiple requests using the same TCP/IP and SSL connection to the server. With HTTP 1.1 the connection can be resused by default, although the server can choose to close the connection at ay time. When that happens, we would get an error, most likely in the read-reply
(gsk_secure_socket_read). We can handle this by detecting the error, cleaning up the old connection and starting a new one and resend the request–we don’t have to reinitialize the SSL environment, however. We’ll use a flag so that we only retry once.
set ws-continue-on-gsk-error to true perform send-request thru send-request-exit if ws-not-connected set ws-exit-on-gsk-error to true perform connect-to-server thru connect-to-server-exit perform send-request thru send-request-exit end-if set ws-continue-on-gsk-error to true perform read-reply thru read-reply-exit if ws-not-connected set ws-exit-on-gsk-error to true perform connect-to-server thru connect-to-server-exit perform send-request thru send-request-exit perform read-reply thru read-reply-exit end-if perform parse-geocoderesponse
connect-to-server
is responsible for detecting that the old socket is still around and cleaning up before opening a new one. And in the gsk_error
routine there is some new code for this:
if ws-exit-on-gsk-error goback end-if set ws-not-connected to true .
So, when send-request
or read-reply
have an error, we will be ws-not-connected
and go through some retry logic, but only once since the retry logic sets ws-exit-on-gsk-error
which will cause gsk_error
to goback
if there is another error.
Now, for this exercise I only want to get the formatted address for a given freehand address fro the Google geocoder API. So I’ve adjusted the XML parsing logic to quit when it hits that XML element. Also, we nned to handle the condition when the data we send to the API to so garbled that it doesn’t return any good result, status not = ‘OK’.
goecoderesponse-handler. * parse the XML and quit once we find the formatted-address * display xml-event ' ' xml-text evaluate xml-event when 'START-OF-ELEMENT' move xml-text to ws-element-name when 'CONTENT-CHARACTERS' if ws-element-name = 'formatted_address' move xml-text to ws-formatted-address move -1 to XML-CODE else if ws-element-name = 'status' and xml-text not = 'OK' move xml-text to ws-formatted-address move -1 to XML-CODE end-if end-if when 'END-OF-ELEMENT' move spaces to ws-element-name when 'EXCEPTION' display 'Exception ' XML-CODE ' at offset ' length of xml-text '.' end-evaluate .
OK, that’s everything but the chunking. I’m not particularly confident of my dechunking logic, but here it goes:
* de-chunk the reply * . 1st skip past header perform varying ws-offset from 1 by 1 until ws-reply (ws-offset : 4) = ws-double-cr-lf end-perform compute ws-offset = ws-offset + 4 * . 2nd process <length><cr>lf><data><cr><lf>...<0><cr><lf> move ws-offset to ws-tail-offset perform with test after until ws-chunk-length = 0 call 'sscanf' using by value address of ws-reply (ws-tail-offset : ) by content z'%x' by value address of ws-chunk-length returning ws-items if ws-chunk-length > 0 then perform varying ws-tail-offset from ws-tail-offset by 1 until ws-reply (ws-tail-offset : 2) = ws-carriage-return-line-feed end-perform compute ws-tail-offset = ws-tail-offset + 2 move ws-reply (ws-tail-offset : ws-chunk-length) to ws-reply (ws-offset : ws-chunk-length) compute ws-offset = ws-offset + ws-chunk-length compute ws-tail-offset = ws-tail-offset + ws-chunk-length + 2 end-if end-perform move ws-null to ws-reply (ws-offset : 1)
Here’s the full source at this point, though I expect to continue debugging…
Remember, to copybooks are in an earlier posting.
Here’s my compile/link/go JCL for this:
//* //* Set a runtime option of POSIX=ON //* //CEEUOPT EXEC ASMAC //C.SYSLIB DD DSN=CEE.SCEEMAC //C.SYSIN DD * CEEUOPT CSECT CEEUOPT AMODE ANY CEEUOPT RMODE ANY CEEXOPT POSIX=ON END CEEUOPT //C.SYSLIN DD DSN=&&LOADSET //* //* Compile, Link and GO //* //COBXML EXEC IGYWCLG,LNGPRFX='IGY', // PARM.LKED='MAP,DYNAM(DLL),RENT,CASE(MIXED)' //COBOL.SYSIN DD DISP=SHR,DSN=xxxxxxxx.SRC.COBOL(COBXML2) //COBOL.SYSLIB DD DISP=SHR,DSN=xxxxxxxx.SRC.COBOL //LKED.SYSLMOD DD DSNTYPE=LIBRARY //LKED.SYSLIB DD //LKED.SYSIN DD * INCLUDE '/usr/lpp/gskssl/lib/GSKSSL.x' INCLUDE '/usr/lpp/gskssl/lib/GSKCMS31.x' ENTRY COBXML //GO.SYSPRINT DD SYSOUT=* //GO.SYSOUT DD SYSOUT=* //GO.SYSIN DD * 1 New Orchard Road, Armonk, New York 1 Infinite, Cupertino, California 500 Oracle Parkway, Redwood Shores, CA 123 main street, anytown usa asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfs 760 United Nations Plaza, New York, New York 1600 pennsylvania avenue, washington dc 10 downing street, london, england 5046 S Greenwood Av, Chicago, IL