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.c`](currentTime.c) is run at the command line and displays the current time:
#include "json.h"
int main()
{
struct soap *ctx = soap_new1(SOAP_C_UTFSTRING | SOAP_XML_INDENT);
struct value *request = new_value(ctx);
struct value response;
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 */
/* make the JSON REST POST request and get response */
*string_of(request) = "getCurrentTime";
if (json_call(ctx, "http://localhost:8080", request, &response))
soap_print_fault(ctx, stderr);
else if (is_string(&response)) /* JSON does not support a dateTime value: this is a string */
printf("Time = %s\n", *string_of(&response));
else /* error? */
{
printf("Error: ");
json_write(ctx, &response);
}
/* 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 value is created with `new_value(ctx)`. The response JSON value is a string that is verified with `is_string(&response)` and the string value is obtained with `string_of(&response)`.
A JSON value `v` can be assigned a choice of value types:
*int_of(v) = 12345LL; /* 64 bit int */
*double_of(v) = 12.34; /* double float */
*string_of(v) = "abc"; /* string */
*string_of(v) = soap_wchar2s(ctx, L"xyz"); /* wide string (converted to UTF-8) */
*bool_of(v) = 0; /* Boolean false (0) or true (1) */
*dateTime_of(v) = soap_dateTime2s(ctx, time(0));
/* time_t value serialized as ISO 8601 date time */
/* create an array [24, 99.99, "abc"] */
*int_of(nth_value(v, 0)) = 24;
*double_of(nth_value(v, 1)) = 99.99;
*string_of(nth_value(v, 2)) = "abc";
/* create object {"name": "gsoap", "major": 2.8, "©": 2015} */
*string_of(value_at(v, "name")) = "gsoap";
*double_of(value_at(v, "major")) = 2.8;
*int_of(value_atw(v, L"©")) = 2015; /* wide string properties are OK */
These functions are also used to retrieve values. For example
int n = *int_of(v);
which takes or converts the JSON value `v` to an integer. Before taking or converting a JSON value, you may want to check its type:
is_null(v) /* true if value is not set (JSON null) */
is_bool(v) /* true if value is a Boolean "true" or "false" value */
is_true(v) /* true if value is Boolean "true" */
is_false(v) /* true if value is Boolean "false" */
is_number(v) /* true if value is a number (int or float) */
is_int(v) /* true if value is a 32 or a 64 bit int */
is_double(v) /* true if value is a 64 bit double floating point (not integer) */
is_string(v) /* true if value is a string */
is_array(v) /* true if array of values */
is_struct(v) /* true if object structure */
The following functions can be used with JSON arrays and objects:
void set_struct(v) /* reset/create an empty object structure */
void set_size(v, int) /* reset/change array size or pre-allocate space */
int has_size(v) /* returns array or object structure size or 0 */
struct value *nth_value(v, int) /* returns nth value in array or object structure */
struct value *value_at(v, const char*) /* returns property value of object structure */
struct value *value_atw(v, const wchar_t*) /* returns property value of object structure */
int nth_at(v, const char*) /* returns nth index of property of object or -1 */
int nth_atw(v, const wchar_t*) /* returns nth index of property of object or -1 */
int nth_nth(v, int) /* returns nth index if nth index exists in array or -1 */
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 -c -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.c`, `json.h`, `json.c`, `xml-rpc.c` and the generated `soapStub.h`, `soapH.h`, and `soapC.c` files:
[command]
cc -DWITH_OPENSSL -DWITH_GZIP -o currentTime currentTime.c \
xml-rpc.c json.c stdsoap2.c soapC.c -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.c` and `xml-rpc.c` 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.c` implements the JSON API with CGI as follows:
#include "json.h"
int main()
{
struct soap *ctx = soap_new1(SOAP_C_UTFSTRING);
struct value request;
/* receive JSON request value */
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
{
struct value *response = new_value(ctx);
/* if the name matches: set response to time, else error */
if (is_string(&request) && !strcmp(*string_of(&request), "getCurrentTime"))
{
*dateTime_of(response) = soap_dateTime2s(ctx, time(0));
}
else
{
*string_of(value_at(response, "fault")) = "Wrong method";
*value_at(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_send_fault(ctx); /* or better use json_send_fault(ctx) */
}
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"
int serve_request(struct soap*);
int main(int argc, char **argv)
{
struct 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(struct soap* ctx)
{
/* HTTP keep-alive max number of iterations */
unsigned int k = ctx->max_keep_alive;
struct value *request = new_value(ctx);
int err;
THREAD_DETACH(THREAD_ID);
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
{
struct value *response = new_value(ctx);
if (is_string(request) && !strcmp(*string_of(request), "getCurrentTime"))
{
*dateTime_of(response) = soap_dateTime2s(ctx, time(0));
}
else
{
*string_of(value_at(response, "fault")) = "Wrong method";
*value_at(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_send_fault(ctx); /* or better use json_send_fault(ctx) */
}
/* close (keep-alive may keep socket open when client supports it) */
soap_closesock(ctx);
} while (ctx->keep_alive);
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 -c -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.c`, `json.h`, `json.c`, `xml-rpc.c` and the generated `soapStub.h`, `soapH.h`, and `soapC.c` files:
[command]
cc -DWITH_OPENSSL -DWITH_GZIP -o currentTimeServer currentTimeServer.c \
xml-rpc.c json.c stdsoap2.c soapC.c -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](#)