#!/usr/bin/python

# poll_server.py
#       --copyright--                   Copyright 2010 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       December 18, 2010       bar
#       December 24, 2010       bar
#       December 26, 2010       bar
#       December 27, 2010       bar     your name form element
#       March 23, 2011          bar     spin a_logger off to tz_server_logger.py
#       October 8, 2011         bar     put the log in the current dir
#       October 11, 2011        bar     and get rid of the .py in it
#       November 7, 2011        bar     close log in main
#       November 29, 2011       bar     pyflake cleanup
#       May 27, 2012            bar     doxygen namespace
#       March 8, 2015           bar     split out JAVASCRIPT
#       --eodstamps--
##      \file
#       \namespace              tzpython.poll_server
#
#
#       This is a web server for polling people about something.
#
#       Extend a_poll_server() with routines to fill in the main page and the poll pages.
#           And put the application logic in the routines that respond to hits from clients.
#
#       How it works:
#
#           There's a "main page" that first timers and unknown clients get.
#           The main page contains N iframes, each with a "poll page" containing the poll questions and buttons for responses (or something that causes a server hit when the user says something).
#           The main page iframes come up in display:none style - they don't show and don't take space.
#           When the poll pages are loaded, they call a routine in the main page to let the main page know they are ready.
#               If the main page isn't showing any poll page, then it shows the ready poll page. Otherwise the poll page is queued up to be shown later.
#           When a poll page button is pressed (that is, at <FORM onSubmit()> time) it tells the main page.
#               The main page makes the poll page invisible and shows an queued, available poll page if there is one.
#
#           Each client has a unique "name". (By default, a string version of a number.)
#           When the server gets a hit from a poll page, it gets the following:
#               (Note: At the appropriate time, tyhe server calls in to an optional a_counter() object with the poll page hit information.
#                      And, other server callbacks can be used for application logic.)
#               The client name                           (from a hidden <INPUT> element).
#               The poll page <DIV> name in the main page (from a hidden <INPUT> element).
#               The button name that was pushed (actually, all the <FORM> args).
#
#           There is a mechanism to handle server restart - the server forgetting all clients.
#               When this is sensed by the server, it sends a special poll page that tells the main page to reload.
#
#           There is an optional log mechanism for simple logging to a file and/or to the console.
#
#       This server can be put behind apache thru mod_proxy.
#           When it's behind the proxy, it's important that the urls used here are all relative so that the client's path is kept ok.
#           Note: Firefox's debug console fusses about an obscure SSL thing when this server is proxied by apache and the URLs are https://.
#
#
#       The minimum you'll need to override are:
#
#           a_poll_server.pre_main_page()           To put your own main page text.
#           a_poll_server.post_main_page()          If you don't want a couple <hr>'s at the bottom of the main page.
#           a_poll_server.poll_form()               To define the actual poll page form html (use A_FORM as template).
#
#       TODO:
#
#           ajax needed for many things
#           don't ask name on pre-loaded "pages" (needs ajax)
#           log name assignments
#           keep a name for someone who is new, but 'cause of a unknown iframe hit
#
#           Toss up -> "It's a toss-up"
#           So Toss-up doesn't sit in a gutter:
#               This[1]    TU[3]     This[1]   columns
#               choice[2] nbsp[1]  choice[2]
#               flush right This buttons and choices
#               center Toss-up
#
#

import  os
import  random
import  re
import  socket
import  time
import  urlparse

import  SocketServer
SocketServer.TCPServer.request_queue_size   = 150           # how many clients can hit us at the same time

import  strip_files
import  tz_http_server
import  tz_server_logger
import  tzlib


HTTP_PORT                   = 12555

LOG_FILE_NAME               = os.path.splitext(os.path.basename(__file__))[0] + ".log"


CI_ARG                      = 'ci'                          # FORM hidden input name for the client id
CN_ARG                      = 'cn'                          # FORM field for putting in a client name
FN_ARG                      = 'fn'                          # FORM hidden input name for the frame name
QN_ARG                      = 'qn'                          # FORM hidden input name of the query name
PRE_FN_ARG                  = "poll_form_"                  # goes in front of the frame number to make it a name
MAIN_CSS                    = 'main.css'                    # URL name of the main page css
POLL_CSS                    = 'poll.css'                    # URL name of the poll pages' css
POLL_HTM                    = 'poll.htm'                    # URL name of the poll pages - case-insensitive in this module's code

PRE_FETCH_COUNT             = 4                             # how many poll forms are to be queued up


COUNTER_ARG_NAME            = "pcount_"                     # for testing


#
#
#           Create your poll page <form> element by putting your stuff in %s here.
#
#
A_FORM      = """
<form name="poll_server_form" id="poll_server_form" action='""" + POLL_HTM + """' method="post" onsubmit="hit_it();">
<input type="hidden" name='""" + CI_ARG + """' value="%s">
<input type="hidden" name='""" + FN_ARG + """' value="%s">
<input type="hidden" name='set_by_js' value='set_by_js' id='key_submit'>
%s
</form>
"""


YOUR_NAME   = """
<p>Please tell me who you are:
<input type="text"   name='""" + CN_ARG + """' value="" size="32" ><p>
"""

YOUR_NAME_KNOWN = """
<input type="hidden" name='""" + CN_ARG + """' value="%s"><p>
"""


A_FORM_QN   = """<input type="hidden" name='""" + QN_ARG + """' value="%s">\n"""


#
#           Used in place of A_FORM to test with.
#
TEST_FORM   = """
Well, how about it?
<p>
<input type="submit" name='""" + COUNTER_ARG_NAME + """no'      value="No"      >&nbsp;
<input type="submit" name='""" + COUNTER_ARG_NAME + """maybe'   value="Maybe"   >&nbsp;
<input type="submit" name='""" + COUNTER_ARG_NAME + """yes'     value="Yes"     >&nbsp;
</form>
<p>
%s  <!-- Place to show we're working -->
"""



#
#
#           Put the <form> and all else in %s here.
#
#
POLL_PAGE   = """
<html>
<head>
    <link rel="stylesheet" type="text/css"  href='""" + POLL_CSS + """'>
    <script language="javascript">
        function    hit_it()
        {
            parent.stop_poll('%s');
            return(true);
        };
    </script>
</head>
<body>
%s
<script language="javascript">
<!--
    parent.start_poll('%s');
-->
</script>
</body>
</html>
"""



#
#
#           Goes in the main page PRE_FETCH_COUNT times.
#
#
POLL_IFRAME = """
<div id='""" + PRE_FN_ARG + """%d' style="display:none;">
<iframe height="800" width="1000" frameborder='0' src='""" + POLL_HTM + """?""" + CI_ARG + """=%s&""" + FN_ARG + """=""" + PRE_FN_ARG + """%d'></iframe>
</div>
"""



#
#
#           Sent to the poll page client when the client name isn't know.
#
#
RELOAD_PAGE = """
<html>
<head>
    <script language="javascript">
        parent.main_poll_refresh_page();
    </script>
</head>
<body>
Sorry. Please refresh this web page and make sure Javascript is enabled.
</body>
</html>
"""



#
#
#           Helper strings. Are put in to the main page's <head> if they should be (the callbacks return something).
#
#
FICO_MAIN_PAGE  = """
    <link rel="icon"       type="image/png" href="favicon.ico">
"""

FPNG_MAIN_PAGE  = """
    <link rel="icon"       type="image/png" href="favicon.png">
"""


#
#
#       The working javascript to put in the main page's <head>.
#
#
JAVASCRIPT      = """
function    main_poll_refresh_page()
{
    window.location.replace(window.location.href);          /* A client who we don't know sent a poll reply. We toss it and get him to reload the main page. (note: apache ssl FF warning for reload and assign. refresh does not work.) */
}


var avail_fns   = new Array();

function    hide_poll(fn)
{
    if (fn)
    {
        for (var i = 0; i < avail_fns.length; ++i)
        {
            if (avail_fns[i] == fn)
            {
                avail_fns.splice(i, 1);
                break;
            }
        }
        var el  = document.getElementById(fn);
        if (el)
        {
            el.style.display    = "none";
        }
    }
}

function    show_next_poll()
{
    var fn = avail_fns[0];
    if (fn)
    {
        var el  = document.getElementById(fn);
        if (el)
        {
            el.style.display    = "";
            el.focus();
        }
    }
}


function    start_poll(fn)
{
    if (fn)
    {
        hide_poll(fn);
        avail_fns.push(fn);
        show_next_poll();
    }
}

function    stop_poll(fn)
{
    hide_poll(fn);
    show_next_poll();
}

"""


MAIN_PAGE_HEAD  = """
    <link rel="stylesheet" type="text/css" href='""" + MAIN_CSS + """'>
    <script language="javascript">""" + strip_files.strip_curly_string(JAVASCRIPT)[0] + """
    </script>
"""


#
#
#           HTML to put at the top of the main page.
#
#
PRE_MAIN_PAGE  = """
<h1>What is the answer?
<p>
You decide.
</h1>
"""

#
#
#           HTML to put at the bottom of the main page.
#
#
POST_MAIN_PAGE  = """<p><hr><hr><p>"""


#
#
#           The main page.
#
#
MAIN_PAGE   = """
<html>
    <head>
        %s
    </head>
    <body>
        %s
        <div id="forms">
            %s
        </div>
        %s
        <noscript>
            I require Javascript to do my work.
            Your browser program (e.g. Internet Explorer, FireFix, Safari, Chrome, "The Google", etc.) has a preference setting to allow Javascript to run on given web sites.
            Allow Javascript for this web site.
        </noscript>
    </body>
</html>
"""



class   a_client(object) :
    def __init__(me, cid, name = "") :
        me.cid  = cid
        me.name = name
    @staticmethod
    def new_cid() :
        return((random.randint(1, 0x7fffFFFF) * 0x80000000) + random.randint(1, 0x7fffFFFF))
    #   a_client


class   a_hit(object) :
    def __init__(me, server, httpd, handler, args, is_head) :
        me.server   = server
        me.httpd    = httpd
        me.handler  = handler
        me.args     = args
        me.is_head  = is_head
        me.cl       = None
        me.ci       = ""
        me.cn       = ""
        me.fn       = ""
        me.qn       = ""
        if  CI_ARG in args :
            me.ci   = server.server.valid_client_number(args[CI_ARG][0])
        if  CN_ARG in args :
            me.cn   = server.server.valid_client_name(  args[CN_ARG][0])
        if  FN_ARG in args :
            fn      = args[FN_ARG][0].replace(PRE_FN_ARG, "")
            try :
                fn  = int(fn)
                if  not (0 <= fn < server.server.pre_fetch_count) :
                    fn  = ""
                me.fn   = PRE_FN_ARG + str(fn)
            except ( ValueError, TypeError ) :
                pass
            pass
        if  QN_ARG in args :
            me.qn   = args[QN_ARG][0]
        pass
    #   a_hit



class   a_counter(object) :
    """ For testing, track the number of times someone has hit a countable button. """

    def __init__(me, names = []) :
        me.names    = names
        me.cnts     = tzlib.make_dictionary(me.names, 0)

    def count_args(me, hit) :
        if  not len(me.cnts) :
            me.cnts = tzlib.make_dictionary([ nm.replace(COUNTER_ARG_NAME) for nm in hit.args.keys() if nm.startswith(COUNTER_ARG_NAME) ], 0)
        for arg in hit.args.keys() :
            arg     = arg.replace(COUNTER_ARG_NAME, "")
            if  arg in me.cnts :
                me.cnts[arg]   += 1
            pass
        pass

    def __str__(me) :
        return(" &nbsp;&nbsp; ".join([ "%s:%u" % ( nm, me.cnts[nm] ) for nm in me.names ]))

    #   a_counter



class   a_poll_server(object) :
    """ This is what a_server's maker must pass to it as 'server'. """


    def __init__(me, root_dir = None, pre_fetch_count = PRE_FETCH_COUNT) :
        me.root_dir         = root_dir or "."
        me.pre_fetch_count  = PRE_FETCH_COUNT if ((pre_fetch_count or 0) <= 0) else pre_fetch_count
        me.clients          = {}


    def new_client_number(me)   :
        """ Generate a random, practical-matter-unique string. """

        while True  :
            ci      = a_client.new_cid()
            if  str(ci) not in me.clients : break
        return(str(ci))

    def valid_client_number(me, ci) :
        """ Protect this string from being dangerous in HTML, at the least. """

        try :
            ci  = str(int(ci))
            if  ci not in me.clients :
                ci  = ""
            pass
        except ( ValueError, TypeError ) :
            ci  = ""
        return(ci)

    def valid_client_name(me, cn) :
        """ Protect this string from being dangerous in HTML, at the least. """

        return(tzlib.printable(cn))


    def load_file(me, fn, ext) :
        """
            A bit of logic that allows the generic files to be given by extended logic as file names rather than as the file data.
            Saves the outsider some typing at the possible expense of reading the files over and over again from the disk (cache).
        """

        fd              = ""
        if  fn          :
            fn          = os.path.normpath(fn)
            if  not fn.startswith(me.root_dir) :
                fn      = os.path.join(me.root_dir, fn)
            fn          = fn.strip("./\\")
            if  fn.lower().endswith(ext) and os.path.isfile(fn) :
                try     :
                    fd  = tzlib.read_whole_binary_file(fn)
                except ( IOError, OSError ) :
                    fd  = ""                                        # don't accidently send the file name to the client when the disk is funny (network drive?)
                pass
            pass

        return(fd)


    def pre_process_hit(me, hit) :
        """ Update the hit with known information about the client, and our client data with information from the user. """

        if  hit.ci     in me.clients :
            hit.cl      = me.clients[hit.ci]
        if  hit.cl      :
            if  hit.cn  :
                if  hit.cl.name != hit.cn :
                    hit.server.log('Client_name "%s" was "%s" is "%s"' % ( hit.ci, hit.cl.name, hit.cn ) )
                    hit.cl.name  = hit.cn                       # learn a new name if he gives one, though the boiler plate doesn't allow it as it gives the form entry only if the name is unknown
                pass
            hit.cn               = hit.cl.name                  # and update the hit, itself, from memory
        pass


    def generic_hit(me, hit) :
        """ Override this to pump out the static resources. """

        if  hit.handler.file_only_lc == 'favicon.ico' :
            fd  = me.load_file(me.favicon_ico(hit), ".ico")
            if  fd  :
                if  hit.handler.send_http_headers(hit.is_head, content_type = "image/x-icon") :
                    hit.handler.write_data(fd)
                pass
            else    :
                hit.handler.send_404(hit.is_head, "No favicon.ico")
            return(True)

        if  hit.handler.file_only_lc == 'favicon.png' :
            fd  = me.load_file(me.favicon_png(hit), ".png")
            if  fd  :
                if  hit.handler.send_http_headers(hit.is_head, content_type = "image/png") :
                    hit.handler.write_data(fd)
                pass
            else    :
                hit.handler.send_404(hit.is_head, "No favicon.png")
            return(True)

        if  hit.handler.file_only_lc == 'main.css' :
            fd      = me.load_file(me.main_css(hit), ".css")
            if  not fd :
                fd  = me.load_file(me.poll_css(hit), ".css")
            if  fd  :
                if  hit.handler.send_http_headers(hit.is_head, content_type = "text/css") :
                    hit.handler.write_data(fd)
                pass
            else    :
                hit.handler.send_404(hit.is_head, "No main.css")
            return(True)

        if  hit.handler.file_only_lc == 'poll.css' :
            fd      = me.load_file(me.poll_css(hit), ".css")
            if  not fd :
                fd  = me.load_file(me.main_css(hit), ".css")
            if  fd  :
                if  hit.handler.send_http_headers(hit.is_head, content_type = "text/css") :
                    hit.handler.write_data(fd)
                pass
            else    :
                hit.handler.send_404(hit.is_head, "No poll.css")
            return(True)

        return(False)


    def new_client_hit(me, hit) :
        """ Our server thinks this is a new client hitting us for something. """

        if  hit.ci in me.clients :
            return(me.client_hit(hit))

        if  not hit.ci  :
            raise IndexError('new_client_hit() without hit.ci - poll server should have called new_client_number()!')

        if  hit.cl :
            raise IndexError('new_client_hit() with hit.cl - pre_process_hit() should only set hit.cl for known clients!')

        me.clients[hit.ci]  = a_client(hit.ci, hit.cn)          # learn a new client (probably without a name at this point - if he hit the generic link, but with a name if he has hit us after a server restart)
        if  hit.cn :
            me.server.log('Client_new "%s" "%s"' % ( hit.ci, hit.cn ) )

        if  hit.server.send_http_headers(hit.handler, is_head = hit.is_head) :
            me.write_new_client(hit)

        return(True)


    def client_hit(me, hit) :
        """ Our server thinks this is a known client hitting us for something. """

        if  hit.ci not in me.clients :
            return(me.new_client_hit(hit))

        if  hit.server.send_http_headers(hit.handler, is_head = hit.is_head) :
            me.write_hit(hit)
        return(True)


    def final_hit(me, hit) :
        """ The client hasn't been handled. Bail or something. """

        hit.handler.send_404(hit.is_head, """Final dummy server hit <p>Path: %s<p>Args: %s<p>ci: %s<hr>""" % ( str(hit.handler.path), str(hit.args), str(hit.ci), ) )
        return(True)


    def write_new_client(me, hit) :
        """ This is a new client's first hit for anything. Give 'em the main page."""

        if  hit.handler.file_only_lc == POLL_HTM.lower() :
            hit.handler.wfile.write(me.reload_page(hit))
        else :
            hit.handler.wfile.write(me.main_page(  hit))
        pass

    def write_hit(me, hit) :
        """ We know this client. Give him a main page, or give him one iframe page. """

        if  hit.handler.file_only_lc == POLL_HTM.lower() :
            if  hit.fn :
                hit.handler.wfile.write(me.poll_page(hit))
            else :
                hit.handler.wfile.write(me.reload_page(hit))
            pass
        else :
            hit.handler.wfile.write(me.main_page(hit))
        pass


    #
    #
    #   Usual-replace-land starts here
    #
    #
    def favicon_ico(me, hit) :
        return(None)
    def favicon_png(me, hit) :
        return(None)
    def main_css(me, hit) :
        return(None)
    def poll_css(me, hit) :
        return(None)
    def possible_file(me, hit) :
        return(os.path.normpath(os.path.join(me.root_dir, hit.handler.file_only)))

    def main_page(me, hit) :
        phtm    = ""
        for pi in xrange(me.pre_fetch_count) :
            phtm   += me.poll_iframe(hit, pi)
        return(MAIN_PAGE % (
                                me.head_main_page(hit),
                                me.pre_main_page(hit),
                                phtm,
                                me.post_main_page(hit),
                           )
              )
        pass

    def head_main_page(me, hit) :
        htm         = ""
        if  me.favicon_png(hit) :
            htm    += FPNG_MAIN_PAGE
        if  me.favicon_ico(hit) :
            htm    += FICO_MAIN_PAGE
        htm        += MAIN_PAGE_HEAD
        return(htm)

    def pre_main_page( me, hit) :
        return(PRE_MAIN_PAGE)

    def poll_iframe(me, hit, pi) :
        return(POLL_IFRAME % ( pi, hit.ci, pi ) )

    def post_main_page(me, hit) :
        return(POST_MAIN_PAGE)


    def poll_page(me, hit) :
        return(POLL_PAGE % ( hit.fn, me.poll_form(hit), hit.fn ) )


    def poll_form(me, hit) :
        frm     = A_FORM
        if  not hit.cn :
            frm = A_FORM % ( "%s", "%s", YOUR_NAME + "%s" )
        return(frm % ( hit.ci, hit.fn, "", ) )


    def reload_page(me, hit) :
        return(RELOAD_PAGE)


    #   a_poll_server



"""
function poll_hit(ci, button_name)
{
    if  (window.XMLHttpRequest)
    {
        xmlhttp = new XMLHttpRequest();
    }
    else
    {
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }

    xmlhttp.onreadystatechange  =   function()
    {
        if  ((xmlhttp.readyState == 4) && (xmlhttp.status == 200))
        {
            eval(xmlhttp.responseText);
        }
    };

    xmlhttp.open("POST", "poll.htm", true);
    xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xmlhttp.send('""" + CI_ARG + """=%s&""" + FN_ARG + """=%s&button=' + button_name + "&rn=" + Math.random());
    xmlhttp.send();
};
"""         # % ( hit.ci, hit.fn, )


class   a_server(object)    :

    def __init__(me, server = None, http_port = HTTP_PORT, logger = None, log_generic = False, show_exceptions = False) :

        me.server           = server            or a_poll_server()
        me.server.server    = me
        me.http_port        = http_port         or me.HTTP_PORT
        me.logger           = logger
        me.log_generic      = log_generic

        me._stop            = False

        try :
            me.http_server  = tz_http_server.a_threaded_http_server(host = "", port = me.http_port, do_http_args_rtn = me.do_http_args, show_exceptions = show_exceptions)
        except ( socket.error, socket.herror, socket.gaierror, socket.timeout ) :
            raise ValueError("I cannot create a server on port %u to communicate with the browser!\r\n" % ( me.http_port ))

        pass



    def start(me) :
        me.http_server.start()



    def stop(me)    :
        me._stop    = True
        me.http_server.stop()
        if  me.logger :
            me.logger.close_log()
        time.sleep(0.1)         # should be in tz_http_server to let the real guy shut down (better way?)


    def log(me, li = "", flush = False, quiet = False) :
        if  me.logger   :
            me.logger.log(li = li, flush = flush, quiet = quiet)
        pass


    @staticmethod
    def send_http_headers(handler, is_head = False, content_type = None, response = 200, hdrs = {}) :
        content_type    = content_type or "text/html"
        response        = response or 200
        try :
            handler.send_response(response)
            handler.send_header("Cache-control",    "no-store")
            handler.send_header("Content-type",     content_type)
            for hdr in hdrs.keys() :
                handler.send_header(hdr, hdrs[hdr])
            handler.end_headers()
        except ( socket.error, socket.herror, socket.gaierror, socket.timeout ) :           # can happen if he hits the refresh button
            is_head = True

        return(not is_head)


    def do_http_args(me, httpd, handler, args, is_head = False) :
        handler.close_connection = 1                                                        # for our purposes, override keep-alives (but it doesn't fix the linux problem)

        if  me._stop :
            handler.send_404(is_head, "Server is stopped")

            return(True)

        ip_adr  = handler.headers.getheader('X-Forwarded-For')                              # note: if we're not proxied by apache, then this can be spoofed. Maybe even anyway. Gotta check.
        if  not ip_adr :
            ip_adr              = handler.client_address[0]
        handler.client_address  = ( ip_adr, handler.client_address[1] )

        handler.path            = re.sub(r"/+", "/", handler.path)                          # !!!! should be done in tz_http_server (get double slashes behind apache proxy)

        uri                     = urlparse.urlparse(handler.path)
        handler.file_only       = re.sub(r".*/", "", uri.path.strip(' .\\/'))
        handler.file_only_lc    = handler.file_only.lower()

        hit             = a_hit(me, httpd, handler, args, is_head)
        me.server.pre_process_hit(hit)

        r               = me.server.generic_hit(hit)                                        # handle hits that don't need client information (static images and the like)
        if  r           :
            if  me.log_generic :
                me.log( "Generic %s [%s] %s" % ( handler.client_address[0], handler.path, str(args) ))
            pass
        else            :
            if  not hit.ci :
                hit.ci  = me.server.new_client_number()
                me.log( "New     %s#%s [%s] %s" % ( handler.client_address[0], hit.ci, hit.handler.path, str(hit.args) ))
                r       = me.server.new_client_hit(hit)
            else        :
                me.log( "Next    %s#%s [%s] %s" % ( handler.client_address[0], hit.ci, hit.handler.path, str(hit.args) ))
                r       = me.server.client_hit(hit)
            pass
        if  not r       :
            r           = me.server.final_hit(hit)

        return(r)


    #   a_server



if __name__ == '__main__' :

    import  TZKeyReady


    class   a_test_server(a_poll_server) :
        def __init__(me, *kargs, **kwargs) :
            super(a_test_server, me).__init__(*kargs, **kwargs)
            me.counter          = a_counter([ 'no', 'maybe', 'yes', ]  )        # for testing

        def poll_form(me, hit) :
            me.counter.count_args(hit)
            frm = TEST_FORM     % str(me.counter)
            return(A_FORM       % ( hit.ci, hit.fn, frm ) )
        #   a_test_server

    server  = a_test_server()

    logger  = tz_server_logger.a_logger(http_port = HTTP_PORT)     # , file_name = LOG_FILE_NAME)
    me      = a_server(server = server, logger = logger, show_exceptions = True)
    me.start()

    while True :
        k   = TZKeyReady.key_ready()
        if  k :
            break
        time.sleep(0.1)

    me.stop()
    logger.close()


#
#
#
# eof
