Web services ------------ There are two situations that might arise for use with web services 1) Serving 4gl functions for use as web services 2) Using web services with 4gl There is a special case of '2' - when you want to use a 4gl function being served as a web service with a 4gl function. In order to create the web services - you will need to have installed the "gSOAP" library. This provides us with 2 important commands : soapcpp2 and wsdl2h If you cannot run these commands - you will not be able to generate the code necessary to serve or use webservices using this mechanism. IMPORTANT : Please check the licensing for gSOAP is ok for your situation. Project page http://gsoap2.sourceforge.net/ or http://www.cs.fsu.edu/~engelen/soap.html (license frame http://www.cs.fsu.edu/~engelen/soaplicense.html). Serving 4gl functions --------------------- Serving 4gl functions is relatively easy. All functions are served from what should be thought of as a single threaded application. There is every likelyhood though that you will not connect back to the service which you have previously called, no state will be preserved etc. You should therefore consider function calls to be 'atomic'. For example, you can't declare a cursor in one web service call, then read it in another.. That cursor might well not exist... Ok - thats the pre-amble - lets get started.. In order to export a function - then a definition of a function has to be created and used, this can be done using the 'fglproto' program. If you don't already have the 'fglproto' program - you should be able to compile it in the aubit4glsrc source code distribution/CVS by : cd compilers/4glc make fglproto Then - create a datafile containing your 4gl definition with : 4glpc -t WRITE somemod.4gl You can do this with multiple 4gls, just run the command for each 4gl module. At this stage though - you only need to specify modules which : 1) Contain functions you wish to export 2) Contain functions used *DIRECTLY* in RETURN statements for functions you are exporting You may wish to consolidate the functions into a single module - or create wrapper function to call your real functions so you don't export all of the functions in a complex 4gl ! Remember - here we are wanting to create "stub" functions to manage the Web service. **Later** we will need the moduled containing all the other functions that are needed to link our application together, but that is not at this "stage".. After you have run 4glpc -t WRITE for each module, we can generate the stub functions for all non-LOCAL functions in our 4gl module. We do this with 'fglproto' : fglproto -w somemod If you have specified more than one 4gl module - add more to that one : fglproto -w somemod1 somemod2 Remember - here we will create the logic to export *all* of the functions not marked as LOCAL, you dont need to use all the modules for the application, you can omit any that are needed just for linking.. This should generate some files : prototypes_server.c prototypes_client.c blacklist prototypes.h (You can ignore 'blacklist' completely) Now - in order to create a server you need to use 'soapcpp2' to process our 'prototypes.h' file : soapcpp2 -c prototypes.h (We're using normal 'C' generation so we need to use the '-c' option to tell soapcpp2 not to generate C++ code) That should generate us some more files ;-) For our server - we will need to use the soapcpp2 generated 'C' files : soapC.c soapServerLib.c prototypes_server.c You will also need a function to start the server itself - you can use this basic one as a starting point (see tools/test/gsoap/server.c) : #include "soapH.h" /* get the gSOAP-generated definitions */ #include "fglserver.nsmap" /* get the gSOAP-generated namespace bindings */ #include int aclfgl_run_server(int n) { int m, s; /* master and slave sockets */ int port; struct soap *soap = soap_new(); if (n==0) { soap_serve(soap); /* serve as CGI application */ soap_done(soap); free(soap); return 0; } port=A4GL_pop_int(); printf("Listening on port %d\n",port); m = soap_bind(soap, NULL, port, 100); /* bind to the port supplied as command-line argument */ if (m < 0) { soap_print_fault(soap, stderr); exit(-1); } fprintf(stderr, "Socket connection successful: master socket = %d\n", m); for (;;) { s = soap_accept(soap); fprintf(stderr, "Socket connection successful: slave socket = %d\n", s); if (s < 0) { soap_print_fault(soap, stderr); exit(1); } soap_serve(soap); soap_end(soap); } soap_done(soap); free(soap); return 0; } You can call this function from within 4gl with something like : main call run_server(9090) end main Putting it all together ----------------------- Assuming you've put that "aclfgl_run_server' function in a file called 'server.c', and the "main..end main" in a file called 'main.4gl' : 4glpc -o server soapC.c soapServerLib.c server.c functions.o prototypes_server.c main.4gl [all your 4gl modules] -lgsoap Eg. if your module containing functions you want to serve is called 'functions.4gl', but needs 'subrouts.4gl' to link : 4glpc -t WRITE functions.4gl fglproto -w functions soapcpp2 -n -c prototypes.h 4glpc -o functions.o functions.4gl 4glpc -o subrouts.o subrouts.4gl 4glpc -g -o server soapC.c soapServerLib.c server.c functions.o subrouts.o nain.4gl prototypes_server.c -lgsoap Now - if you want to create a client to use the webservices for these functions - its very straightforward. You simply need to compile you 4gl along with the generated client code and the 'gsoap' library, and optionally alter the function to include the URL where they will be served. 4glpc -g -o client_4gl soapC.c soapClient.c prototypes_client.c [your 4gls] -lgsoap The functions can have an optional first parameter specifying the URL where the functions will be served from. This is a default of "http://localhost:9090" if not specified- but the default can be manually changed in the 'prototypes.h' generated by "fglproto -w" eg. main call get_tabname(54321) returning lv_tabname call get_tabname("http://localhost:9090",54321) returning lv_tabname display lv_tabname,":" end main This assumes that you have exported a function similar to : database test1 main call run_server(9090) end main function get_tabname(lv_id) define lv_tabname char(18) define lv_id integer display "lv_id=",lv_id select tabname into lv_tabname from systables where tabid=lv_id return lv_tabname end function See the code in tools/test/gsoap for this example..... Using non-aubit4gl web services ------------------------------- Note: Not all webservices will be directly compatible. In particular - we can only handle certain datatypes, and cannot handle arrays etc. If there is a particular service you want to use which cannot be compiled - please pass on the details to support@aubit.com and we will see if it is possible to add support for them. Using non-aubit4gl web services requires the use of the wsdl2fgl compile (aubit4glsrc/compilers/wsdl2fgl), you can use either a wsdl, or the header file generated from soapcpp2 : wsdl2fgl someservice.wsdl or wsdl2fgl someservice.h If used with a 'wsdl' file then the 'wsdl2h' from gsoap with be run to generate the header file. The output from wsdl2fgl should be files called soapClient.c soapC.c Client_4gl.c . These should be linked with your 4gl code along with the gsoap library, eg. 4glpc -o client_4gl [your 4gls] soapClient.c soapC.c Client_4gl.c -lgsoap Check the Client_4gl.c file for what functions are available - and what parameters/return values are used, noting that normally an 'aclfgl_' prefix will be added to the function names - which themselves will have a gsoap namespace prefix. For example, a "getdirections" webservice might be created as : aclfgl___ns2__getdirections You would need to call this as '__ns2__getdirections' from within your 4gl (ie. removing the leading 'aclfgl_') Lets work through a full example... Using http://xmethods.net/ve2/ViewListing.po?key=427560, we download the WSDL file and save it as 'driving.wsdl'. We then run 'wsdl2fgl' on the wsdl : wsdl2fgl driving.wsdl Looking at the generate Client_4gl.c : we have aclfgl___ns2__getdirections // Parameter 1 - fromAddress CHAR(...) // Parameter 2 - toAddress CHAR(...) // Parameter 3 - distanceUnit CHAR(...) // Parameter 4 - expresswayEnabled CHAR(...) and aclfgl___ns3__getdirections // Parameter 1 - fromAddress CHAR(...) // Parameter 2 - toAddress CHAR(...) // Parameter 3 - distanceUnit CHAR(...) // Parameter 4 - expresswayEnabled CHAR(...) (One is calling via "service name" drivingSoap and the other drivingSoap12) So - using the first one - and using some applicable parameters - we might have a 4gl program like this (testclient.4gl) : main define lv_data char(1024) call __ns2__getdirections( "1600 Amphitheatre Parkway Mountain View, CA ,USA", "2775 Middlefield Road Palo Alto, CA,USA", "miles", "true" ) returning lv_data display lv_data clipped end main (This is from Google HQ to a starbucks close by) Now - to compile it all up : 4glpc -o client.4ae testclient.4gl soapClient.c soapC.c Client_4gl.c -lgsoap So - to summarise - we just need : wsdl2fgl driving.wsdl 4glpc -o client.4ae testclient.4gl soapClient.c soapC.c Client_4gl.c -lgsoap :-)