JSON current time client & server example
=========================================
Overview
--------
This example implements a simple JSON REST API client and server in C++.
For more information about the use of JSON in gSOAP, see the [JSON documentation.](../../doc/xml-rpc-json/html/index.html)
Implementing the client application
-----------------------------------
The client application [`currentTime.cpp`](currentTime.cpp) is run at the command line and displays the current time:
#include "json.h"
using namespace std;
int main()
{
soap *ctx = soap_new1(SOAP_C_UTFSTRING | SOAP_XML_INDENT);
ctx->send_timeout = 10; // 10 sec, stop if server is not accepting msg
ctx->recv_timeout = 10; // 10 sec, stop if server does not respond in time
value request(ctx), response(ctx);
// make the JSON REST POST request and get response
request = "getCurrentTime";
if (json_call(ctx, "http://locahost:8080", request, response))
soap_print_fault(ctx, stderr);
else if (response.is_string()) // JSON does not support a dateTime value: this is a string
cout << "Time = " << response << endl;
else // error?
cout << "Error: " << response << endl;
// clean up
soap_destroy(ctx);
soap_end(ctx);
soap_free(ctx);
return 0;
}
/* Don't need a namespace table. We put an empty one here to avoid link errors */
struct Namespace namespaces[] = { {NULL, NULL} };
The JSON request message is the string `"getCurrentTime"` that is POSTed to the server. The response is a string with the current time in UTC.
The context `ctx` is initialized with `SOAP_C_UTFSTRING` to ensure 8-bit strings contain Unicode in UTF-8 format. The `SOAP_XML_INDENT` flag is optional, it displays JSON with an indented layout (just as XML is indented with this flag).
The `struct value` data represents a JSON value (and in fact also an XML-RPC value).
The JSON API is called with `json_call(ctx, "URL", request, response)` using an HTTP POST because both input and output parameters are non-NULL.
The call `json_call(ctx, "URL", in, out))` with pointers `in` and `out` to JSON values supports HTTP POST (both `in` and `out` are non-NULL), HTTP GET (`in` is NULL, `out` is non-NULL), HTTP PUT (`in` is non-NULL, `out` is NULL), and HTTP DELETE (`in` and `out` are NULL).
JSON values can be read with `json_read(ctx, v)` with pointer `v` to an (empty) JSON value that will be assigned. To read from a file descriptor, set the context `ctx->recvfd` to the descriptor. To read from a string, set the context `ctx->is` to a `char*` string which works in C only. In C++ set the context `ctx->is` input stream to a string stream.
JSON values can be written with `json_write(ctx, v)` with pointer `v` to a JSON value that will be saved. To write to a file descriptor, set the context `ctx->sendfd` to the descriptor. To write to a string, set the context `ctx->os` to point to a `char*` to be assigned with the string saved, which works in C only. In C++ set the context `ctx->os` output stream to a string stream.
An empty request and response value is created with `request(ctx)` and `response(ctx)`. The response JSON value is a string that is verified with `response.is_string()` and the string value is obtained by (internal) type casts.
A JSON value `v` can be assigned a choice of value types:
v = 12345LL; // 64 bit int
v = 12.34; // double float
v = "abc"; // string
v = string("abc"); // std::string
v = L"xyz"; // wide string (converted to UTF-8)
v = wstring(L"xyz"); // std::wstring (converted to UTF-8)
v = false; // Boolean
v = (ULONG64)time(0); // ULONG64 values are serialized as ISO 8601 date time
v[0] = 24; // an array
v[1] = 99.99;
v[2] = "abc"
v["name"] = "gsoap"; // an object
v["major"] = 2.8;
v[L"©"] = 2015; // wide string properties are OK
A JSON value `v` can be cast:
(double)v // 64 bit double or 0.0 if not numeric
(int)v // 32 bit int or 0 if not numeric
(LONG64)v // 64 bit int or 0 if not numeric
(char*)v // convert to string
(string)v // convert to std::string
(wchar_t*)v // convert to wide string
(wstring)v // convert to std::wstring
(bool)v // same as is_true()
(ULONG64)v // nonzero time_t if v contains an ISO 8601 date time
Before casting, you may want to check the type of a JSON value `v`:
v.is_null() // true if value is not set (JSON null)
v.is_bool() // true if value is a Boolean "true" or "false" value
v.is_true() // true if value is Boolean "true"
v.is_false() // true if value is Boolean "false"
v.is_number() // true if value is a number (int or float)
v.is_int() // true if value is a 32 or a 64 bit int
v.is_double() // true if value is a 64 bit double floating point (not integer)
v.is_string() // true if value is a string or wide string
v.is_array() // true if array of values
v.is_struct() // true if object structure
When working with JSON numbers, do not use `is_double()` but `is_number()` to check if a value is a number. JSON does not differentiate between integers and floats. However, gSOAP makes sure that 64 bit integer values are accurately represented in JSON and decoded without loss.
The following methods can be used to inspect JSON arrays and objects:
v.size(int) // reset array size or pre-allocate space
v.size() // returns array or object size or 0
v.empty() // true if array or object is empty
v.has(int) // true if index is within array bounds
v.has(const char*) // true if object has property
v.has(const wchar_t*) // true if object has property
v.nth(int) // returns index >= 0 if index is in array bounds, < 0 otherwise
v.nth(const char*) // returns index >= 0 of property in object, < 0 otherwise
v.nth(const wchar_t*) // returns index >= 0 of property in object, < 0 otherwise
v[int] // returns value at index in array or object
v[const char*] // returns property value of object
v[const wchar_t*] // returns property value of object
See the [JSON documentation](../../doc/xml-rpc-json/html/index.html) for more details on the JSON functions to create, inspect, and retrieve values.
For more information about authentication and HTTPS, see the [tutorials.](../../tutorials.html)
Also see the [tutorials](../../tutorials.html) on how to set timeouts and handle signals.
[![To top](../../images/go-up.png) To top](#)
Build steps for the client application
--------------------------------------
The use of JSON requires a one-time step to generate a data model that supports both JSON and the older XML-RPC protocols:
[command]
soapcpp2 -CSL xml-rpc.h
The `xml-rpc.h` file and all other source code files that we need can be found in the `samples/xml-rpc-json` directory of the gSOAP package.
To build the example client application we need `stdsoap2.h`, `stdsoap2.cpp`, `json.h`, `json.cpp`, `xml-rpc.cpp` and the generated `soapStub.h`, `soapH.h`, and `soapC.cpp` files:
[command]
c++ -DWITH_OPENSSL -DWITH_GZIP -o currentTime currentTime.cpp \
xml-rpc.cpp json.cpp stdsoap2.cpp soapC.cpp -lcrypto -lssl -lz
This may look odd, to include "soap-related" files in the build steps, but this is with a reason. We can combine JSON with all of our XML/SOAP Web services by simply adding an `#import "xml-rpc.h"` to the header file for soapcpp2 to process. The `xml-rpc.h` file specifies the data model to support JSON and XML-RPC. When combining XML/SOAP with JSON, we compile the `json.cpp` and `xml-rpc.cpp` files also, as shown.
OpenSSL and Zlib are required for our client application to work propertly with HTTPS.
[![To top](../../images/go-up.png) To top](#)
Implementing a CGI server application
-------------------------------------
The server application `currentTimeServer.cpp` implements the JSON API with CGI as follows:
#include "json.h"
using namespace std;
int main(int argc, char **argv)
{
soap *ctx = soap_new1(SOAP_C_UTFSTRING);
// process CGI request
value request(ctx);
if (soap_begin_recv(ctx)
|| json_recv(ctx, request)
|| soap_end_recv(ctx))
{
soap_send_fault(ctx); // or better use json_send_fault(ctx)
}
else
{
value response(ctx);
// if the name matches: set response to time, else error
if (request.is_string() && !strcmp(request, "getCurrentTime"))
{
response = (ULONG64)time(0);
}
else
{
response["fault"] = "Wrong method";
response["detail"] = request;
}
// set the http content type
ctx->http_content = "application/json; charset=utf-8";
// send the response
if (soap_response(ctx, SOAP_FILE)
|| json_send(ctx, response)
|| soap_end_send(ctx))
soap_print_fault(ctx, stderr);
}
soap_destroy(ctx);
soap_end(ctx);
soap_free(ctx);
return 0;
}
/* Don't need a namespace table. We put an empty one here to avoid link errors */
struct Namespace namespaces[] = { {NULL, NULL} };
Install the CGI application in the cgi-bin folder of your Web server.
[![To top](../../images/go-up.png) To top](#)
Implementing a stand-alone multi-threaded server application
------------------------------------------------------------
This example shows a stand-alone iterative server that accepts incoming requests on a host port. The program is the same as the CGI service except for the service request dispatching over sockets in a loop:
#include "json.h"
#include "plugin/threads.h"
using namespace std;
int serve_request(soap*);
int main()
{
soap *ctx = soap_new1(SOAP_C_UTFSTRING);
int port = 8080;
if (!soap_valid_socket(soap_bind(ctx, NULL, port, 100)))
{
soap_print_fault(ctx, stderr);
exit(1);
}
soap_set_mode(ctx, SOAP_IO_KEEPALIVE); // enable HTTP keep-alive, which is optional
ctx->send_timeout = 10;
ctx->recv_timeout = 10;
ctx->transfer_timeout = 30;
while (1)
{
if (!soap_valid_socket(soap_accept(ctx)))
soap_print_fault(ctx, stderr);
else
{
THREAD_TYPE tid;
void *arg = (void*)soap_copy(ctx);
// use updated THREAD_CREATE from plugin/threads.h https://www.genivia.com/files/threads.zip
if (!arg)
soap_closesock(ctx);
else
while (THREAD_CREATE(&tid, (void*(*)(void*))serve_request, arg))
sleep(1);
}
}
soap_destroy(ctx);
soap_end(ctx);
soap_free(ctx);
return 0;
}
int serve_request(soap* ctx)
{
THREAD_DETACH(THREAD_ID);
// HTTP keep-alive max number of iterations
unsigned int k = ctx->max_keep_alive;
value request(ctx);
do
{
if (ctx->max_keep_alive > 0 && !--k)
ctx->keep_alive = 0;
// receive JSON request
if (soap_begin_recv(ctx)
|| json_recv(ctx, request)
|| soap_end_recv(ctx))
{
soap_send_fault(ctx); // or better use json_send_fault(ctx)
}
else
{
value response(ctx);
if (request.is_string() && !strcmp(request, "getCurrentTime"))
{
response = (ULONG64)time(0);
}
else
{
response["fault"] = "Wrong method";
response["detail"] = request;
}
ctx->http_content = "application/json; charset=utf-8";
if (soap_response(ctx, SOAP_FILE)
|| json_send(ctx, response)
|| soap_end_send(ctx))
soap_print_fault(ctx, stderr);
}
// close (keep-alive may keep socket open when client supports it)
soap_closesock(ctx);
} while (ctx->keep_alive);
int err = ctx->error;
// clean up
soap_destroy(ctx);
soap_end(ctx);
soap_free(ctx);
return err;
}
/* Don't need a namespace table. We put an empty one here to avoid link errors */
struct Namespace namespaces[] = { {NULL, NULL} };
HTTP Keep Alive is enabled by adding the `SOAP_IO_KEEPALIVE` to `soap_new1()` or with `soap_set_mode(ctx, SOAP_IO_KEEPALIVE)`. This enables the inner loop to handle multiple requests over the current connection. Make the JSON response indented with the `SOAP_XML_INDENT` flag.
To combine JSON REST with SOAP/XML services that are based on `soap_serve()` to serve SOAP/XML requests, see the [XML-RPC & JSON/JSONPath](https://www.genivia.com/doc/xml-rpc-json/html/index.html#soap) documentation.
See the [tutorials](https://www.genivia.com/tutorials.html) on how to use HTTPS and how to harden stand-alone services.
See the [documentation](https://www.genivia.com/docs.html) on how to use ISAPI and Apache modules to develop services.
[![To top](../../images/go-up.png) To top](#)
Build steps for the server application
--------------------------------------
Similar to the client example above, the use of JSON requires a one-time step to generate a data model that supports both JSON and the older XML-RPC protocols:
[command]
soapcpp2 -CSL xml-rpc.h
The `xml-rpc.h` file and all other source code files that we need can be found in the `samples/xml-rpc-json` directory of the gSOAP package.
To build the example server application we need `stdsoap2.h`, `stdsoap2.cpp`, `json.h`, `json.cpp`, `xml-rpc.cpp` and the generated `soapStub.h`, `soapH.h`, and `soapC.cpp` files:
[command]
c++ -DWITH_OPENSSL -DWITH_GZIP -o currentTimeServer currentTimeServer.cpp \
xml-rpc.cpp json.cpp stdsoap2.cpp soapC.cpp -lcrypto -lssl -lz
OpenSSL and Zlib are required for our server application to work propertly with HTTPS.
[![To top](../../images/go-up.png) To top](#)
Running the example
-------------------
[command]
./currentTimeServer 8080 &
./currentTime
Time = 2017-06-05T17:26:12Z
[![To top](../../images/go-up.png) To top](#)