COBOL POSIX Sockets

By | August 30, 2013

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.

Leave a Reply

Your email address will not be published. Required fields are marked *