Vettrasoft
Z Directory > Tech Support > examples


5 example programs, with full source code.


This web page contains 5 examples of software programs in C++ that use the Z Directory toolkit library. The source code listed below can also be found in the 'readme' page provided by the installer. Each example covers a different part of the Z Directory and contains a short, complete, working program. You can copy and paste them into source code files, build them, and run them. The 4th example has two separate programs (client & server).


EXAMPLE 1: time and date operations.

The following program demonstrates time & date operations, basic string functionality, and using random numbers with time. Cut and paste the following code snippet into a Visual Studio console application:


#include "stdafx.h"
#include "z_stime.h"
#include "z_timespan.h"
#include "z_rng.h"

#define FINAL_VERSION 0

int main (int argc, char *argv[])
{
    count_t nb = 0;
    int i, ie = 0;
    char buf[80];

    z_start();

    string_o s ("Jan 13, 2021 19:20:59");
    stime_o today, next_time, eoyear ("Dec 31, 2012"), expires(s);
    timespan_o tsec ("1 year 2 months 17 days 55 seconds");
    tsec.reset();                       // clear out example time-span value

    today.now();
    if (expires <= today)
        std::cout << "The year is at least 2021 - TIME EXPIRED!\n";

    next_time = today;                  // set 'next time' to now

    RNG_o r;                            // set up a random # generator
    r.set_range (1000.0, 10000.0);      // range is [~16 min .. 2.8 hrs]

    for (nb = 0, s = ""; next_time < eoyear; )
    {
        i = (int) r.random_number();    // no: get another # seconds
        tsec.set_seconds(i);            // to sleep (wait); # is random
        next_time += tsec;              // add the # seconds to the time object

        z_int_to_str (i, buf);          // format the # sec as a string
        s = string_o("(") + string_o(buf) + string_o(") ");
        std::cout << s << std::flush;   // print # seconds like so: "(440) "
        nb += s.size();                 // track size of line
        if (nb > 72)                    // if long enough, tack on 
        {
            std::cout << std::endl;     // (eg,  or "New Line")
            nb = 0;
        }

#if FINAL_VERSION
        z_sleep (i);                    // and go to sleep
#endif
    }

    std::cout << "\nTIME'S UP. ALL DONE!\n";
    z_finish();
    return 0;
}
Example 1 - some talking points:

  • the "RNG" object is in layer 10, so a lot of Z Directory libraries need to be included ('0' thru '10'). If a lightweight application is required, you can use simpler (layer 0) subroutines to generate a ramdom number [eg, 'z_big_random_number()'].


  • 'stime_o' is used exclusively instead of 'time_o' in this code. This is because often you need a string representation of the time (/date). It's a lot easier to use the more powerful object (stime) and not have to worry about converting 'time' to 'stime'.


  • the timespan_o instance-variable 'tsec' is initialized to this:
    "1 year, 2 months, 17 days, 55 seconds"


    This value is not used in this program, but serves to demonstrate the power and simplicity of a typical Z Directory object. You can add this span of time to the current date to find out exactly what will be the date 1 year, 2 months, 17 days, 1 hour and 55 seconds from now, like so:
            stime_o t;
            string_o s ("1 year 2 months 17 days 55 1 hour seconds");
            timespan_o tx (s);
            t.now();
            t += tx;
            std::cout << s << " from now will be: " << t.print() << "\n";
    


  • the program "goes to sleep" a random number of seconds (between 1,000 and 10,000 seconds, or beetween 16 minutes and 2.8 hours). This is done with 'z_sleep()'. You should use Vettrasoft's "wrapper" function instead of 'Sleep()' because when you move the program to a unix system, you won't have to change to the function to 'sleep()' (or vice-versa). Note that we have "turned off" the macro FINAL_VERSION. This is so you won't be sitting there wondering why the program is not doing anything for long periods of time.


  • to make the desired format, "([n]) " {where [n] is the random number of seconds}, we need to use explicit casts of string objects:
    s = string_o("(") + string_o(buf) + string_o(") ");
    we would have loved to write something like s = "(" + buf + ") "; but c++ doesn't work that way, that would be interpreted as pointer addition [(char *) + (char *)].


  • the first line of the program is:
    #include "stdafx.h"

    for those unfamiliar with Microsoft's ways, this line is mandatory if the default compiler setting is "Use Precompiled Header (/Yu)". We won't go into it here - simply put, for compiling on a normal unix machine, delete this line.





EXAMPLE 2: files and directories.

This program creates some files and directories. Besides basic file-system operations, it succinctly demonstrates how to encrypt and decrypt data (using the most advanced cipher algorithms currently available).

#include "stdafx.h"
#include "z_func.h"
#include "z_mathsubs.h"
#include "z_multicrypt.h"
#include "z_file.h"
#include "z_directory.h"


#define MY_TEMPDIR      "C:\\temp"
#define MY_SECRET_MSG   "A thousand leagues, a thousand leagues...\n(TEST DATA)\nAmen.\n"
#define MY_MAXFILEs      1000
#define MY_AESKEY        "AES cipher key size is 32 bytes."
#define MY_DESKEY        ">Ideally, the key used for this DES implementation has 64 bytes<"


//----------------------------------------------------------------------
int main (int argc, char *argv[])
{
    int i, ie, ie2;
    char buf[800];

    z_start();
    string_o s, s2, s3, sfull, s_fname("ZDirFTest-000.txt");

    z_env("TEMP", buf, 800, &ie2);
    if (z_directory_exists(buf, &ie2))
        std::cout << buf << ": this directory exists.\n";

    directory_o d(MY_TEMPDIR);
    if (!d.is_valid())
    {
       if (d.create(&ie2))
          { std::cerr << "cannot make dir '" << MY_TEMPDIR << "'\n"; exit(1); }
    }

    file_o f;
    f.set_directory (MY_TEMPDIR);       // "C:/temp"
    f.set_name (s_fname);               // "C:/temp/ZDirFileTest.txt"

    s2 = f.base_name();                 // get "ZDirFileTest000"
    z_strcpy (buf, s2.data());          // keep it in 'buf'

    for (i = 0; i < MY_MAXFILEs; i++)   // search for a new file name
    {
        s_fname = string_o(buf) + ".txt";
        f.set_name (s_fname);
        if (!f.exists())                // got file? (eg "ZDirFileTest001.txt")
            break;

         z_str_incnumbers(buf);         // "000" -> "001"; "001" -> "002"..
    }

    if (i >= MY_MAXFILEs)
        { std::cerr << "CANDIDATE FILE NAMES EXHAUSTED!n"; exit (1); }

    s = MY_SECRET_MSG;                  // copy the text to encrypt
    f.store (s);                        // write contents of "s" to file

    size_t nb = s.size();
    nb = z_ilum(nb, 32);                // make string length multiple of 32
    z_strcpy (buf, s.data());           // make "s" length multiple of 16/32/64
    z_pad_blanks_right(buf, nb+1);      // pad with spaces to multiple of 32
    s = buf;                            // put the text back into 's'

    multicrypt_o cipher;                // set up a multi-crypto cipher device
    ie = cipher.add_crypto ("aes", MY_AESKEY, "", &ie2);
    ie = cipher.add_crypto ("des", MY_DESKEY, "", &ie2);
    nb = cipher.encrypt (s, s2, &ie2);  // encrypting with AES + DES
    if (ie2) exit(1);

    nb = s2.size();
    f.set_name ("ZDirFTest.k");         // Vettrasoft: '.k' for encrypted files
    f.put_bytes(s2, &nb, &ie2);         // automatically opens & closes

    s2.setmode_binary();                // round-trip: read the encrypted file
    ie = f.get_bytes (s2, &nb, &ie2);   // put contents into a binary string obj
    if (ie2) exit(1);                   // always check for errors
    nb = cipher.decrypt (s2, s3, &ie2); // decrypt, using AES + DES
    if (ie2) exit(1);

    if (s3 == s)                        // compare R.T. output to original
    {
        std::cout << "SUCCESS: s -> encrypt() -> [file] -> decrypt() ";
        std::cout << "comes back the same.\n";
    }
    else
        std::cout << "ERROR IN ENCRPYT-DECRYPT\n";

    z_finish();
    return 0;
}
Example 2 - some talking points:

  • notice that both "unix style" and "Microsoft style" paths can be used, even with the lowest-layer subroutines (such as "z_directory_exists()"); that is, both "C:/temp" and "C:\\TEMP" work. Microsoft's concept of hard disk device prefixes (logical disk drives A-Z) are written in the normal way: letter + ":", ie, "G:".


  • we look for 'temporary directories' here by looking for the environment variables "TEMP" and "TMP". We can also check in the registry, like so:
    boolean ans = z_registry_entry_exists("HKCU/Environment/TEMP", buf, &ie);


  • SIMPLICITY: given a file object ('f') and a string with text ('s'), you can write the text to the file with the statement "f.store(s);"


  • ENCRYPTION: with the multicrypt object, you can apply multiple encryption algorithms on a string (either binary or text). There are approximately 7 ciphers available in this object (see the reference section "security" ). Some encryptors have specific requirements - here, we use AES (Advanced Encryption System). For AES, the string length must be a multiple of 32.


  • A LITTLE MATH: Note the use of the function "z_ilum()" (in the math group of the Z Directory), which computes the lowest multiple of 32 that exceeds the current string length. Thus, a if the string contains "Hello there!" (12 bytes), and we want to round up to the nearest 8 bytes, ""z_ilum(12, 8)" returns 16. The string is padded with blanks using a low-level C function "z_pad_blanks_right()", to satisfy the AES encryption length requirement.


  • WRITING BINARY DATA TO A FILE: notice also the simplicity of doing this: "f.put_bytes(s2, &nb, &ie2);" automatically creates, opens, and closes the file, in addition to writing out the data.


  • DECRYPTION: Once again, a large task is accomplished in 1 short line of C++ code, "cipher.decrypt (s2, s3, &ie2);". One must know that, with multicrypt_o, the order of the ciphers for decryption must be exactly the same as in encryption. You might think they need to be presented in reverse order, but the object is clever enough to go backwards for decryption. Also, you must present the correct passwords for decryption. There are 2 notable exceptions to this: RSA, which requires a 2nd (private) key for decryption, and algorithms that use no password (yes, such do exist).

    It has been written that re-encrypting an encrypted block does not provide more protection. We are not convinced, and welcome anyone to crack a block of text encrypted with our multicrypt object using 4 or more ciphers (ie, RSA, AES, DES, and Vettrasoft's own).


  • SEAMLESS INTEGRATION: notice how low-level "C" language subroutines [z_env(), z_strcpy(), z_pad_blanks_right(), z_ilum()] work in conjunction with more powerful, higher-layer objects [file_o, directory_o, multicrypt_o]. In many cases, the string_o class does not have all the functions you need for various string-based operations. You need to know how the layer-0 functions inter-operate with the string-based objects [string_o, textstring_o, ..]. Typically, a pointer to the internal buffer must be passed to the layer-0 function ("s.data()"), and then the data can be returned by a simple assignment statement ("s = buf;"). One approach would be to move all the layer-0 functions into the string object as member functions - that would make the object's list of functions huge. We wouldn't punish you with such a daunting learning curve.





EXAMPLE 3: containers for variables; "name-value pairs".

A "name-value pair", or "key-value pair", is a fancy term for describing the characteristics of a simple variable: it has a name, and a value. Given that you use string objects to hold the name and value, here are some of the ways you put them into containers of various types in the Z Directory.

This example illustrates the many Z Directory objects available to maintain key-value pairs. Each section below is labeled by a Roman numeral (E3.i, E3.ii, E3.iii, E3.iv, ...) and can be combined into a single large, monolithic program. The main driver can look like this:


#include "z_nameval.h"
 
int main (int argc, char *argv[])
{
    z_start();
    use_nvp();
    use_textstring();
    use_paramstring();
    use_dbag();
    use_kw();
    use_ini();
    z_finish();
    return 0;
}
Each of the sub-sections below are complete, and independent of the other units of this program, and are called individually in the main driver (above).

(E3.i) in a name-value pair object, or [Layer-6] namevalue_pair_o Aggregations of these are placed in a 'namevalue_set_o' instance-variable:


#include "stdafx.h"
#include "z_nameval.h"

void use_nvp ()
{
    int i, err;
    namevalue_pair_o nvp;
    namevalue_set_o nv_set ("RESULT=FAIL;REASON=\"NETWORK ERROR\";");
    nvp.set_name("WHEN");
    nvp.set_value("01/17/2012");
    nv_set.add (nvp, &err);
    if (err)
        std::cerr << "error in adding element to set\n";

    for (i = 0; ; i++)
    {
        const namevalue_pair_o &my_item = nv_set.get (i, &err);
        if (err)
            break;
        std::cout << "[" << i << "]: NAME: \"" << my_item.name() << "\"";
        std::cout << "\tVALUE: \"" << my_item.value() << "\"\n";
    }
}



(E3.ii) in a "textstring object" ([L2] textstring_o)

#include "z_txtstring.h"

void use_textstring ()
{
    int i, j; textstring_o ts, chunk, word, name, valu;
    ts  = "And now, the RESULT: FAIL. The REASON?: \"UNIT DESTROYED!\" ";
    ts += "WHEN did it happen? on \"09/11/2012\"";

    for (i = 0; ts.size() > 0; i++)
    {
        ts.pre_trim();
        chunk = ts.yank_bigword(TRUE); chunk.trim();
        word  = chunk.yank_word();     word.trim();

        if (word == "RESULT")
            valu = ts.yank_word();
        else if (word == "REASON")
            valu = (ts[0] == '"') ? ts.yank_quote(TRUE) : ts.yank_bigword(TRUE);
        else if (word == "WHEN")
        {
            for (j = 0; j < 4; j++)
                (VOID) ts.yank_bigword(TRUE);

            valu = (ts[0] == '"') ? ts.yank_quote(TRUE) : ts.yank_bigword(TRUE);
        }
        else
            continue;

        std::cout << "[" << i << "]: NAME: \"" << word << "\"";
        std::cout << "\tVALUE: \"" << valu << "\"\n";
    }
}



(E3.iii) in a parameter string object ( [L3] paramstring_o )


#include "z_paramstring.h"

void use_paramstring ()
{
    int i;
    string_o s ("RESULT=SUCCESS REASON=\"EVERYTHING WORKS\" WHEN=01/31/2013");
    paramstring_o ps (s);

    for (i = 0; ps.more(); i++)
    {
        ps.eat();
        if (ps.ok())
        {
          std::cout << "[" << i << "]: NAME: \"" << ps.current_name() << "\"";
          std::cout << "\tVALUE: \"" << ps.current_value() << "\"\n";
        }
    }
}
the parameter string object is a bit dated, and in many cases has been replaced by namevalue_set_o. It was intended to be used for "symbolic programming" paradigms (see James Coplien's purple book, section 9.2)

(E3.iv) in a "data-bag" ([L3] list_dbag_o, array_dbag_o)


#include "z_dbag_recurs.h"

char my_dbag_example[] =
"namevalue \
( \
    RESULT NOTSET \
    REASON NOTSET \
    WHEN NOTSET \
)";

void use_dbag ()
{
    int err;
    rec_dbag_o master_bag (my_dbag_example);
    master_bag.put ("RESULT", "DELAYED", &err);
    master_bag.put ("REASON", "INCLEMENT WEATHER", &err);
    master_bag.put ("WHEN",    "Dec 31, 1979");

    std::cout << "THE DATA, IN A DATABAG: \n\"" << master_bag.print() << "\"\n";
}

this example, though it satisfies the example's requirements, does no justice to the power, flexibility, and usefulness of databags (also: "data-bags"). There are several types of databags, such as list, matrix, array, and recursive. Most databag types can contain databags of other types. Recursive databags are no doubt the most interesting type, allowing one to design most any type data structure as a databag.

(E3.v) in a keyword parser object ([L4] kw_parser_o)


#include "z_keyword.h"

const string_o NV_Schema =
"NVKW \
( \
    options\
    (\
        keyword_delim     \":	\" \
        word_delim        \", \" \
        line_start      \"	\" \
        block_start     \"	\" \
        block_line_start   \"		\" \
        special_start   \"		\" \
        comment_start   \"#\" \
    )\
    keywords\
    (\
        RESULT (category zkw_Line_Item) \
        REASON (category zkw_Line_Item) \
        WHEN   (category zkw_Line_Item) \
    )\
)";

char NV_KWdata[] =
"RESULT:	INCOMPLETE\n\
REASON:	DEVICE FULL\n\
WHEN:	July 04, 1999\n";

void use_kw ()
{
    int i, ie;
    kw_parser_o kwp ("NVexample", rec_dbag_o(NV_Schema), FALSE, 9);
    rec_dbag_o result = kwp.parse(NV_KWdata, i);
    std::cout << "THE DATA (IN DATABAG FORMAT):\n" << result.print() << "\n";
}

The keyword parser object might appear to be a databag or a 'namevalue_set_o' in disguise, but it is actually a way to parse text data (typically in files) and put them into a data bag.

The large amount of char[] data required to initialize a kw_parser_o is mainly due to the large number of configurable options that describe how the text is formatted. For beginners, it is best to just copy and paste the "options" block of the keyword parser's metadata string ("NV_Schema").



(E3.vi) in an INI file ([L10] inifile_o)

For this, put the following block of text in a file called "nvkw.ini", in the directory "C:\TEMP" (or edit the #define FILENAME line in the source code below to reflect the file path):

[NETWORK]
RESULT=DENIED
REASON=INVALID PASSWORD
WHEN=01/17/2012
The corresponding source code:
#define FILENAME "C:/TEMP/nvkw.ini"

void use_ini ()
{
    int i, ie;
    const char *ptr;
    string_o s;

    inifile_o my_ini;
    my_ini.set_name (FILENAME);
    ie = my_ini.load();
    if (ie) { std::cerr << "error in loading INI file\n"; exit(1); }

    for (i = 0; i < 3; i++)
    {
        switch (i)
        {
        case 0: ptr = "*/RESULT";       break;
        case 1: ptr = "NETWORK/REASON"; break;
        case 2: ptr = "*/WHEN";         break;
        }

        s = my_ini.value (ptr, &ie);
        if (ie) { std::cerr << "error getting item: " << ptr << "\n"; return; }

        std::cout << "[" << i << "]: NAME: \"" << ptr << "\"";
        std::cout << "\tVALUE: \"" << s << "\"\n";
    }
}
notice that there are really only 4 "power" lines in the code above:
        inifile_o my_ini;
        my_ini.set_name (FILENAME);
        ie = my_ini.load();
        s = my_ini.value (ptr, &ie);
    
The INI file object, inifile_o, does a lot in a little code. It is a child class of file_o.

(E3.vii) in a Z Directory [template-based] container object - there are many, including:
        aordset_o ........: ordered set (array-based)
        aset_o ............ [unordered] set (array-based)
        llset_o  .........: set (linked-list-based)
        llordlist_o ......: ordered list (uses linked-lists)
there are several other types.
[CODE IS COMING]




EXAMPLE 4: clients & servers.

The following code is for two programs: the client and the server. It is the bare-bones minimum amount of code for a client and a server. The client here does a simple, synchronous, round-trip send-and-wait. The server opens port 5005 up, waits for input, and sends back the time. You might consider this program to be a "time server". Remember to start the server before running the client. First, the server is presented, then the client:
//==============(SERVER PROGRAM)====================================

#include "stdafx.h"
#include "z_stime.h"
#include "z_server.h"

class Time_Server : public server_o
{
public:
    int  process_message    (const msgtrans_addr_o &, string_o &);
    int  process_disconnect (msgtrans_addr_o &);
    msgtrans_sockaddr_o my_sa;
};

int main (int argc, char *argv[])
{
    int ie;
    z_start();
    Time_Server TS;                     // create a server instance
    msgtrans_sockaddr_o sa;             // this object for setting up sockets
    sa.set_host ("localhost");          // remote servers: use "0.0.0.0"
    sa.set_port (5005);                 // change the port # to taste
    timespan_o ts;                      // timespan obj used to set the
    ts.set_seconds(20);                 // 'sleep' time (waiting on input)
    TS.my_sa = TS.add_channel(sa, &ie); // create bi-directional server port
    TS.set_max_sleep(ts);               // tell the server the wait time
    ie = TS.run();                      // go wait for client connections
    z_finish();
    return ((!ie) ? 0 : 1);             // hopefully "run()" returns 0
}

int Time_Server::process_message (const msgtrans_addr_o &mta, string_o &s)
{
    stime_o t;
    t.now();
    std::cout << "GOT THIS INPUT: \"" << s << "\"\n";
    s  = "THE TIME IS: ";
    s += t;
    return send_message (mta, s);
}

int Time_Server::process_disconnect (msgtrans_addr_o &e_bsa)
{
    msgtrans_sockaddr_o &bsa = (msgtrans_sockaddr_o &) e_bsa;
    std::cout << "(SERVER MESSAGE) - DISCONNECT; port #" << bsa.port() << "\n";
    return (server_o::process_disconnect (e_bsa));
}

//==============(CLIENT CODE)====================================

#include "stdafx.h"
#include "z_mtsocket.h"

#define ERR_CONNECT 1
#define ERR_SEND    2
#define ERR_GET     3
#define ERR_DISCONN 4

void bad(int);

int main (int argc, char *argv[])
{
    int ie, ie2;
    string_o s;                         // holds incoming/outgoing msgs
    msgtrans_o mt;                      // client program's main object
    msgtrans_sockaddr_o addr;           // same object type as server uses
    addr.set_port (5005);               // match port # to server port #
    addr.set_host ("localhost");        // set your computer name here
    mt.set_address (addr, &ie);

    ie = mt.connect();                  // connect to server
    if (ie) bad(ERR_CONNECT);

    s = "WHAT TIME IS IT?\n";
    size_t nb = s.size();               // set up any text message
    ie = mt.put (s, &ie2, nb);          // send the query-message
    if (ie) bad(ERR_SEND);

    nb = s.size();
    ie = mt.get (s, &ie2, &nb);         // wait for, get reply
    if (!ie)
        std::cout << "SERVER SAYS: \"" << s << "\"\n";
    else
        bad(ERR_GET);

    ie = mt.disconnect();               // final step: disconnect
    if (ie) bad(ERR_DISCONN);
    exit(0);
    return 0;
}


void bad (int code)
{
    switch (code)
    {
case ERR_CONNECT: std::cout << "FAILED TO CONNECT (server running?)\n"; break;
case ERR_SEND:    std::cout << "FAILED TO SEND TO SERVER\n"; break;
case ERR_GET:     std::cout << "FAILED TO GET A REPLY FROM SERVER\n"; break;
case ERR_DISCONN: std::cout << "PROBLEM DISCONNECTING FROM SERVER\n"; break;
default: std::cout << "UNKOWN ERROR\n"; break;
    }

    exit(1);
}


Example 4 - some talking points:

  • in order to make a server, you must subclass from server_o and implement the virtual member functions process_message() and process_disconnect() (at the bare minimum). A server can have multiple ports (we call them channels), or even none at all. The channels can be directed - that is, be for input-only, output-only, or bidirectional. There are different types of channels. Channels can be for data formatted into databags, or raw data. The data can be binary - as you can see from the function signiture of process_message(), the message comes packaged into a string_o, which can hold either [readable] text or a binary data block. There is also a facility for accepting non-packetized data, typically for client programs that do not use the Z Directory (ie via raw socket code).


  • The "message transport" object (msgtrans_o) and "message transport socket address" object (msgtrans_sockaddr_o) are foot soldiers in the job of moving data between 2 points. They should be used on both sides of the client - server circuit/channel. The server has internal packetization (not currently documented as of this writing), so that the input text recieved into process_message() is exactly what was sent by the client.


  • Clients and servers currently only support sockets as the mode of transportation. This should be a non-issue for the bulk of the world, but Vettrasoft has plans to expand the implementions to a wide range of methods (shared memory; SVR4 TLI; more).


  • Vettrasoft has used the server object in the past to create servers that run under a master web server umbrella. There is a "generic CGI client program" that is destined to become an object soon. This program is a server that takes input the web server gives it, parsing the data and reformatting it into a databag, then forwarding the data to whatever server is supposed to recieve it. It parses FORM data the way any typical CGI program would. Applying this technique with the Z Directory objects results in huge, orders-of-magnitude performance gains over traditional web server programs written in interpreted languages, such as perl or PHP (One such program, originally prototyped in perl, ran approximately 25-50 times faster than its perl counterpart). The following code snippets serve as simple, partial examples of how the Z Directory can be used (GET method, in this case) for web sites:



#include "z_server.h"

static char *envvars[] =
{
    "DOCUMENT_ROOT",
    "GATEWAY_INTERFACE",
    "HTTP_ACCEPT",
    "HTTP_CONNECTION",
    "HTTP_HOST",
    "HTTP_REFERER",
    NULL
};

//----------------------------------------------------------------------
void add_misc_cgi_envvars (server_dbag_o &bag)
{
    int i, ie;
    simple_dbag_o workbag;
    char buf[2048];

    for (i = 0; envvars[i] != NULL; i ++)
    {
        ie = z_env(envvars[i], buf, 2048);      // get the next env var
        if (ie) continue;                       // skip it, if error

        workbag.set_name (envvars[i]);          // envvar[i] -> databag name
        workbag.put      (buf);                 // sets the databag's value
        bag.add_dbag ("", workbag);             // adds curr item to "bag"
    }
}

static const char *header_string =
    "Content-type: text/html\n\n<HTML>\n    <BODY>\n";
static const char *trailer_string = "    </BODY>\n</HTML>\n";
//----------------------------------------------------------------------
static void dump_timeout_message ()
{
  std::cout << header_string;
  std::cout << "        <CENTER>\n";
  std::cout << "        <FONT size=+3>\n";
  std::cout << "            ERROR! Could not contact the back-end server.\n";
  std::cout << "            <P><FONT size=-1>(CGI program timed-out)</FONT>\n";
  std::cout << "            <P>Goodbye!\n";
  std::cout << "        </FONT>\n";
  std::cout << trailer_string;
}






EXAMPLE 5: server program - alternate example.

The following code is a fully functional "intermediary" web server. It takes an http request coming in on port 1080, gets the home page for www.vettrasoft.com, and returns it. You should be able to get the web page in a web browser with http://localhost:1080 (if not, substitute the host computer's name instead of "localhost"). The parameters can be set in the #define section near the top of the code, if you wish to try other port numbers and/or web sites. . Also, if the program is run on Thursday afternoon, it will replace the title of the web page with "HAH, MY REDIRECT!". This program shows how to use several Z Directory objects not used in earlier examples, such as msgtrans_sockaddr_o, regex_o and http_o.
//======================================================================
// fake_webserver.cpp: example of intermediary, client <-> web server
//======================================================================


#include "stdafx.h"
#include "z_server.h"
#include "z_regex.h"
#include "z_http.h"

#define THE_WEBSITE "www.vettrasoft.com"
#define THE_PORT    1080
#define NEW_TITLE   "<TITLE>HAH, MY REDIRECT!</TITLE>"

class Echo_Server : public server_o
{
public:
    Echo_Server (const string_o &);
    int process_message (const msgtrans_addr_o &, string_o &);
    void reset() { got_get = got_host = got_nl = FALSE; }
private:
    http_o h;
    boolean got_get, got_host, got_nl;
};

Echo_Server::Echo_Server(const string_o &website)
{
    reset();
    h.set_host(website);
    h.set_url(website);
}

int Echo_Server::process_message (const msgtrans_addr_o &mta, string_o &s)
{
    int ie1, ie2, j;
    textstring_o s_match, line, ts(s);
    while (ts.size() > 0)               // read HTTP lines
    {
        line = ts.yank_line(TRUE);      // process individual lines
        line.trim();                    // kill any [CR][LF]
        line.tolower();                 // simplify: all -> lower-case
        if (!line.cmp_nchars("get"))    // is it like "GET /index.htm HTTP/1.1"?
            got_get = TRUE;
        else if (!line.cmp_nchars("host:"))
            got_host = TRUE;            // got: "host: www.vettrasoft.com"

        if (line == "" && got_get && got_host)
        {
            reset();                    // got the blank line, e-o-headers
            h.do_request(0, &ie2);      // go fetch the real web page
            ts = h.entire_response();

            time_o t;
            t.now();                    // get current time & date

            if (t.dow() == 4 && t.hour() >= 12 && t.hour() <= 18)
            {                           // time is between noon & 6pm
                regex_o R;              // do a regular expression search
                R.set_pattern ("<TITLE>.+</TITLE>");
                j = R.search (ts, &s_match, &ie1);
                if (!ie1)               // found a match on <TITLE>...
  ie1 = ts.search_replace (s_match, NEW_TITLE, string_o::z_Find_First);
            }

            return send_message(mta, ts); // forward all to client (browser)
        }
    }

    return 0;
}

//--------------------------------------------------------------
int main (int argc, const char *argv[])
{
    int ie;
    Echo_Server ES(THE_WEBSITE);                // set your favorite web site

    msgtrans_sockaddr_o sa;                     // define a msg transport port
    sa.set_port (THE_PORT);                     // set your custom port #
    const msgtrans_addr_o &rmta = ES.add_channel (sa, &ie, TRUE);
    ES.packetize_channel (rmta, "none", &ie);   // don't packetize - raw text
    timespan_o ts ("72 hours");                 // define a span of time
    ES.set_maxlife(ts);                         // set server lifespan
    return ES.run();                            // GO DO IT
}
Example 5 - some talking points:

  • the http_o class can fetch a web page over the internet and requires about 5 lines of code:
                http_o h;
                h.set_host(website);
                h.set_url(website);
                h.do_request(0, &ie2);
                string_o s = h.entire_response();
            
    The set_url() call is done in cases where a specific page within the web site is desired. If a web site's home page is desired, the argument to this call would be the same as that of the call to set_host(). For example, to fetch this page:
                h.set_host("www.vettrasoft.com");
                h.set_url ("www.vettrasoft.com/zd_quickexamples.html");
            
  • Normally, the Z Directory server object uses text coming over a socket connection ("channel", in Z Directory parlance) that has been put into packets using the packetbuff_o class. To turn off this feature, the server port (/channel) packetization is switched off with this code:
                const msgtrans_addr_o &rmta = ES.add_channel (sa, &ie, TRUE);
                ES.packetize_channel (rmta, "none", &ie);
            
  • Here the textstring_o class is preferred over the string_o class. This is so as to be able to use textstring class member functions such as yank_line() and cmp_nchars(). The latter is based on the common, lowly strncmp() of unix origin in an easy fashion.

  • This example introduces the regular expression object. The implementation of regular expressions available here is rather simple, returning the first match in full for the given regex string. Unfortunately, more complex regex functionality such as returning multiple sub-match text within a given match is not available. The regex pattern matching is "greedy" (taking the maximum possible block of text that can match) and spans lines.