What good is it to have the relatively new COBOL verbs XML-PARSE and XML-GENERATE without a web client or server? Well, I am being a little facetious. You could use an MQ request-reply model, but, for instance, you probably wouldn’t use the native COBOL XML support in a CICS program since CICS has it’s own support for XML (SOAP and RESTful and more natively TRANSFORM XMLTODATA and TRANSFORM DATATOXML). Probably IMS/TM, IDEAL, etc., don’t have anything like this, though.
So, let’s assume you need to access some service provided by some server out there on the internet. How hard would it be to implement a simple HTTP client in COBOL? Sure, you could use a C subroutine, but for the sake of argument let’s assume that for some political, bureaucratic or monetary reason, you can’t do that. Or you could code in java which makes HTTP clients pretty easy.
There are a few different socket interfaces available to use, the C interface (which is no longer being enhanced), the UNIX (or POSIX) C socket library, the X/Open Transport Interface (which I doubt anyone uses), the Macro API available to assembler programs, a Call Interface, a Rexx Interface and a Pascal Interface. I could use the Call interface and it would work just fine. However, I will want to use other POSIX services anyway, sample programs that I will want to use as a guide use the C socket style programming (thanks Google) and in the future I will want to upgrade the program use SSL and those samples use a C API.
Copybooks (a digression)
But, I will need COBOL layouts for various C structs, so I experimented:
I ran the Micro Focus h2cpy utility against in.h which I ftp’d from the mainframe directory along with a bunch of other included headers that were required so see what the sockaddr_in struct would give me in COBOL:
h2cpy in.h -C -I . -D__TARGET_LIB__=__EDC_LE -D_ALL_SOURCE
* struct sockaddr_in { * unsigned char sin_len; * sa_family_t sin_family; * in_port_t sin_port; * struct in_addr sin_addr; * unsigned char sin_zero[8]; * }; 01 sockaddr-in is typedef. 02 sin-len usage uns-char. 02 sin-family usage uns-char. 02 sin-port usage uns-short. 02 sin-addr usage in-addr. 02 sin-zero occurs 8 usage uns-char.
You can probably tell that the COBOL generated is not something we can use directly. But it’s close. At the top of the generated in.cpy we see stuff like:
77 char pic s9(2) comp-5 is typedef. 77 uns-char pic 9(2) comp-5 is typedef. 77 short pic s9(4) comp-5 is typedef. 77 uns-short pic 9(4) comp-5 is typedef. 77 int pic s9(9) comp-5 is typedef. 77 uns-int pic 9(9) comp-5 is typedef. 77 long pic s9(18) comp-5 is typedef. 77 uns-long pic 9(18) comp-5 is typedef.
and later
* struct in_addr { * in_addr_t s_addr; * }; 01 in-addr is typedef. 02 s-addr usage uns-long.
PIC 9(2) COMP-5
?? I guess PIC X(01)
will have to do. Blah, I think this isn’t worth the time. Harrumph! I’ll stick to hand coding whilst reviewing the structs in the book.
Overview
For this project, we will need to do the following….
- Look up an IP address given the hostname of a server supplying the service.
- Connect to that IP address
- Convert & send the server a request using the HTTP protocol
- Receive the reply from the server and convert it
- Close the connection with the server
I’ll demonstrate this by accessing an openly available internet service provided by www.iheartquotes.com via their API. I’m not going to address the XML possibilities in this post, there’s plenty of ground to cover without that; save that for later.
getaddrinfo()
To connect to a server, you need the IP address and port number for the server and application. That is, a server at a particular IP address is running a service application that is listening on a particular port number.
getaddrinfo()
is what you use to get the IP address for a particular server name. gethostbyname()
is what I used to use for this but, apparently, this function is now discouraged–who knew? Anyway, the function prototype looks like this:
int getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res);
The function returns an integer, that is a PIC S9(8) COMP
value which, if non-zero, indicates an error. nodename is the hostname as a C string value (i.e. null terminated). servname is to allow for different service names, which we aren’t going to use–does anyone? You pass the function the address of a pointer (**res
) that it will update (on success) with the address of a linked-list of struct addrinfo
. So we need a layout for a struct addrinfo
. For C, struct addrinfo
is defined in /usr/include/netdb.h. I copy/pasted it into a copybook and reinterpreted it info COBOL as best I could.
* struct addrinfo { 01 addrinfo. * int ai_flags; /* AI_PASSIVE, AI_CANONNAME */ 05 ai-flags pic 9(8) binary. * int ai_family; /* PF_xxx */ 05 ai-family pic 9(8) binary. 88 ai-family-afinet value 2. 88 ai-family-afinet6 value 19. * int ai_socktype; /* SOCK_xxx */ 05 ai-socktype pic 9(8) binary. * int ai_protocol; /* 0 or IPPROTO_xxx */ 05 ai-protocol pic 9(8) binary. * socklen_t ai_addrlen; /* length of ai_addr */ 05 ai-addrlen pic 9(8) binary. * int __ai_reserved;/* reserved */ 05 ai-reserved pic 9(8) binary. * __pad31( __ai_canonname_r,4 ) /* 31-bit padding */ 05 filler pic 9(8) binary. * char *ai_canonname; /* canonical name for * hostname */ 05 ai-canonname pointer sync. * __pad31( __ai_addr_r,4 ) /* 31-bit padding */ 05 filler pic 9(8) binary. * struct sockaddr *ai_addr; /* binary address */ 05 ai-addr pointer sync. * __pad31( __ai_next_r,4 ) /* 31-bit padding */ 05 filler pic 9(8) binary. * struct addrinfo *ai_next; /* next structure in list */ 05 ai-next pointer sync. * };
The hints addrinfo struct is used to provide some search arguments in the ai-flags
field. I’m pretty sure the rest of the struct is not used, but it can be null anyway so I will skip it. With addrinfo in the linkage section, calling getaddrinfo
becomes:
77 ws-server-name pic x(21) value z'www.iheartquotes.com'. 77 ws-null-ptr pointer value null. 77 ws-addrinfo-ptr pointer. 77 ws-errno pic 9(8) binary. [...] call 'getaddrinfo' using by value address of ws-server-name ws-null-ptr ws-null-ptr by reference ws-addrinfo-ptr returning ws-errno if ws-errno not = 0 then call 'perror' using by content z'getaddrinfo' goback end-if set address of addrinfo to ws-addrinfo-ptr
We could walk through the linked-list with something like:
perform until address of addrinfo = null * do something set address of addrinfo to ai-next end-perform
But we will only use the first one in the list to make the connection. getaddrinfo
allocates storage to provide the results and you should call freeaddrinfo
when you are done with the results:
call 'freeaddrinfo' using by value ws-addrinfo-ptr returning ws-void
connect()
The struct addrinfo
returned by getaddrinfo
includes a pointer to a struct sockaddr
which is where it IP address is stored. It could really be a struct sockaddr_in
or sockaddr_in6
depending n whether it is an IPV4 or an IPV6 address. See connect()
for more on this. I defined these in a copybook so be included in the linkage section, as well.
* struct sockaddr_in { 01 sockaddr-in. * unsigned char sin_len; 05 sin-len pic x(1). 88 sin-len-ipv4 value x'08'. 88 sin-len-ipv6 value x'1c'. * unsigned char sin_family; 05 sin-family pic x(1). 88 sin-family-afinet value x'02'. 88 sin-family-afinet6 value x'13'. * unsigned short sin_port; 05 sin-port pic 9(4) binary. * struct in_addr sin_addr; 05 sin-addr pic 9(8) binary. * unsigned char sin_zero[8]; 05 sin-zero pic x(8). * }; * struct sockaddr_in6 { 01 sockaddr-in6. * uint8_t sin6_len; 05 sin6-len pic x(1). 88 sin6-len-ipv4 value x'08'. 88 sin6-len-ipv6 value x'1c'. * sa_family_t sin6_family; 05 sin6-family pic x(1). 88 sin6-family-afinet value x'02'. 88 sin6-family-afinet6 value x'13'. * in_port_t sin6_port; 05 sin6-port pic 9(4) binary. * uint32_t sin6_flowinfo; 05 sin6-flowinfo pic 9(8) binary. * struct in6_addr sin6_addr; 05 sin6-addr pic x(16). * uint32_t sin6_scope_id; 05 sin6-scope-id pic 9(8) binary. * }
Then,
if ai-family-afinet then set address of sockaddr-in to ai-addr else set address of sockaddr-in6 to ai-addr end-if
We’ll just use the ai-addr
in the first struct addrinfo
in the linked-list returned, but we do need to fill in the server’s service port number. connect()
requires a socket (i.e. a file descriptor) so we’ll get one of those first:
77 ws-socket pic s9(8) binary. * SOCK_STREAM from /usr/include/sys/socket.h 77 ws-sock-stream pic s9(8) binary value 1. * IPPROTO_TCP from getprotobyname() or 'TCPIP.ETC.PROTO' 77 ws-ipproto-tcp pic s9(8) binary value 6. [...] * Create a socket in the right family, SOC_STREAM, IPPROTO_TCP. * int socket(int domain, int type, int protocol); call 'socket' using by value ai-family ws-sock-stream ws-ipproto-tcp returning ws-socket if ws-socket = -1 then call 'perror' using by content z'socket' goback end-if
Then we can finally connect():
* int connect(int socket, * const struct sockaddr *address, * socklen_t address_len); call 'connect' using by value ws-socket ai-addr ai-addrlen returning ws-rc if ws-rc = -1 then call 'perror' using by content z'connect' goback end-if
send()
Now that we have connected successfully to the server, we can send it a request. We have to build a request that is in the proper HTTP format which is pretty simple in this case:
GET /api/v1/random?format=text HTTP/1.1<cr><lf> Host: www.iheartquotes.com<cr><lf> <cr><lf>
where <cr>
represents the carriage-return character (x’0d’ in ASCII) and <lf>
is the linefeed character (x’0a’ in ASCII). There are many ways to code this, as static data in working-storage for example. I chose to string it together.
77 ws-server-name pic x(21) value z'www.iheartquotes.com'. 77 ws-service-uri pic x(26) value '/api/v1/random?format=text'. 77 ws-null pic x(1) value low-values. 77 ws-zero pic s9(8) binary value zero. 77 ws-carriage-return-line-feed pic x(2) value x'0d15'. 77 ws-request-length pic s9(8) binary. 77 ws-request pic x(100). [...] string 'GET ' delimited by size ws-service-uri delimited by size ' HTTP/1.1' delimited by size ws-carriage-return-line-feed delimited by size 'Host: ' delimited by size ws-server-name delimited by low-values ws-carriage-return-line-feed delimited by size ws-carriage-return-line-feed delimited by size ws-null delimited by size into ws-request
Note that this is all in EBCDIC so I need to convert this to ASCII before sending it to the server. The string is null terminated with ws-null
so we can use __etoa()
to convert it and give us the length.
call '__etoa' using by value address of ws-request returning ws-request-length
Now we’re ready to send it:
call 'send' using by value ws-socket address of ws-request ws-request-length ws-zero returning ws-length
recv()
77 ws-reply-length pic s9(8) binary. 77 ws-reply pic x(999). [...] call 'recv' using by value ws-socket address of ws-reply length of ws-reply ws-zero returning ws-reply-length
recv()
blocks until the server response (or error) is returned. Of course, it will be in ASCII so we convert it back to ECBDIC (with __atoe()
).
call '__atoe' using by value address of ws-reply returning ws-length
and we get something like this:
HTTP/1.1 200 OK Server: nginx Date: Fri, 30 Aug 2013 21:18:25 GMT Content-Type: text/plain Transfer-Encoding: chunked Connection: keep-alive Etag: "435cbcb0292d8bb72143e3070af0c8ea" X-Ua-Compatible: IE=Edge,chrome=1 X-Runtime: 0.071015 Cache-Control: max-age=0, private, must-revalidate c2 Basic, n.: A programming language. Related to certain social diseases in that those who have it will not admit it in polite company. [codehappy] http://iheartquotes.com/fortune/show/15179 0
This has a few line of HTTP protocol header, the most important being the first line which indicates success with HTTP return code 200 (OK). The actual result is returned after the blank line.
close()
Now all we have to do is clean up a bit. Close the socket (which will end the connection if it is still active):
call 'close' using by value ws-socket returning ws-rc
and free up the addrinfo linked-list:
call 'freeaddrinfo' using by value ws-addrinfo-ptr returning ws-void
Conclusion
Here are the artifacts: cobsock.cob netdb.cob socket.cob in.cob
Check this earlier post for clues on compiling & running this program.