<commit_before>#!/usr/bin/python3
"""
Comet web server for Cinch game engine communication with clients.

TODO:   
        Add logging
        Change Access-Control-Allow-Origin to appropriate resource

"""
from http.server import HTTPServer,BaseHTTPRequestHandler  
from socketserver import ThreadingMixIn
from threading import Lock, Event
from urllib.parse import urlparse, parse_qs
from cgi import escape
import json
from collections import defaultdict, deque

from web import web_config as config
from web.channel import CommChannel
from web.message import Message


class CometServer(ThreadingMixIn, HTTPServer):    
    """Perform event handling for comet long polling using many threads."""
    def __init__(self, hostname, port):
        """Initialize server. Overrides inherited HTTPServer __init__."""
        super(HTTPServer, self).__init__((hostname, port), HttpHandler)

        max_connections = config.MAX_CLIENTS
        cache_size = config.CACHE_SIZE

        # For each new request, create a deque
        self.handler_queue = deque(maxlen=max_connections)

        # Message buffer        
        self.message_queue = defaultdict(list)
        
        self.lock = Lock()    # Thread control for server
        self.responders = []  # List of CommChannels for handling POST queries

    ####################
    # Server-focused methods
    ####################

    def run_server(self):
        try:
            self.serve_forever()
        except KeyboardInterrupt:   # Exit gracefully
            # Inhibit new handlers from connecting so server dies on time
            config.GUID_KEY = None # Causes all GETs to be ignored
            # Release all handler threads so server doesn't wait for timeout
            hq = list(self.handler_queue)
            for h in hq:
                self.release_handler(h)
                                
            print("Server halted with keyboard interrupt.\n")

    def add_announcer(self, channel):
        """Register channel as an announcer to the server.

        Announcer channel will call server's notify function when it needs
        to publish a message to the server. add_announcer() will give the
        channel a reference to the server's notify function.

        channel (CommChannel): object doing the announcing

        """
        assert isinstance(channel, CommChannel)

        # Create function for channel to call to server
        def handler(msg):
            assert isinstance(msg, Message)
            self.notify(msg)
            
        channel.add_announce_callback(handler)

    def add_responder(self, channel, signature):
        """Register responser for handling incoming data/requests.

        channel (CommChannel): external object that cares to handle messages
        signature (list): list of keys for JSON encoded dictionary of data
            that handler cares about

        """
        assert isinstance(channel, CommChannel)
        assert isinstance(signature, list)
        
        self.responders.append({'channel':channel, 'signature':signature})

    def matches_signature(self, channel, msg):
        """Determine if msg signature matches channel signature.

        channel (CommChannel): channel
        msg (Message): message

        returns: True or False

        """
        if (all (k in msg.data for k in channel['signature']) and
            all (k in channel['signature'] for k in msg.data)):
            return True

        else:
            return False
                
    def handle_msg(self, channel, msg):
        """Handle event with registered channel.

        msg is an incoming message which is handed to a channel for processing.

        channel (CommChannel): channel designated for handling this
            type of msg
        msg (Message): Message object message received by server

        return: Message response from channel, or None

        """
        assert isinstance(channel, CommChannel)
        assert isinstance(msg, Message)

        response = channel.respond(msg)

        assert (isinstance(response, (Message, dict)) or response is None)
        
        return response

    ####################
    # Client-focused methods
    ####################

    def capture_handler(self, http_handler):
        """Add http_handler to handler_queue and set event flags for later.
        
        Captures handler for later writing to by notify().
        
        http_handler (HttpHandler): request handler
        
        """
        # Check if handler for guid is already in queue. If so, close out all
        # old ones. Should only be at most one handler per guid, but loop just
        # in case.
        old_handler = self.retrieve_handler(http_handler.guid)
        while old_handler is not None:
            self.release_handler(old_handler)
            old_handler = self.retrieve_handler(http_handler.guid)    

        # If there are old messages in the queue, send them now
        output = self.get_old_messages(http_handler.guid)
        if len(output) > 0:
            # Return output for writing without further capture
            http_handler.send_message(output)
            return False

        else:
            # Set event() flag on handler
            http_handler.event = Event()
            http_handler.in_use = False
            
            # Add handler to list of active http handlers        
            self.handler_queue.append(http_handler)

            print("handler {0} captured".format(len(self.handler_queue)))
            
            return True  # Do http_handler.event.wait() from calling method

    def retrieve_handler(self, guid):<commit_msg>Removed testing print().<commit_after>#!/usr/bin/python3
"""
Comet web server for Cinch game engine communication with clients.

TODO:   
        Add logging
        Change Access-Control-Allow-Origin to appropriate resource

"""
from http.server import HTTPServer,BaseHTTPRequestHandler  
from socketserver import ThreadingMixIn
from threading import Lock, Event
from urllib.parse import urlparse, parse_qs
from cgi import escape
import json
from collections import defaultdict, deque

from web import web_config as config
from web.channel import CommChannel
from web.message import Message


class CometServer(ThreadingMixIn, HTTPServer):    
    """Perform event handling for comet long polling using many threads."""
    def __init__(self, hostname, port):
        """Initialize server. Overrides inherited HTTPServer __init__."""
        super(HTTPServer, self).__init__((hostname, port), HttpHandler)

        max_connections = config.MAX_CLIENTS
        cache_size = config.CACHE_SIZE

        # For each new request, create a deque
        self.handler_queue = deque(maxlen=max_connections)

        # Message buffer        
        self.message_queue = defaultdict(list)
        
        self.lock = Lock()    # Thread control for server
        self.responders = []  # List of CommChannels for handling POST queries

    ####################
    # Server-focused methods
    ####################

    def run_server(self):
        try:
            self.serve_forever()
        except KeyboardInterrupt:   # Exit gracefully
            # Inhibit new handlers from connecting so server dies on time
            config.GUID_KEY = None # Causes all GETs to be ignored
            # Release all handler threads so server doesn't wait for timeout
            hq = list(self.handler_queue)
            for h in hq:
                self.release_handler(h)
                                
            print("Server halted with keyboard interrupt.\n")

    def add_announcer(self, channel):
        """Register channel as an announcer to the server.

        Announcer channel will call server's notify function when it needs
        to publish a message to the server. add_announcer() will give the
        channel a reference to the server's notify function.

        channel (CommChannel): object doing the announcing

        """
        assert isinstance(channel, CommChannel)

        # Create function for channel to call to server
        def handler(msg):
            assert isinstance(msg, Message)
            self.notify(msg)
            
        channel.add_announce_callback(handler)

    def add_responder(self, channel, signature):
        """Register responser for handling incoming data/requests.

        channel (CommChannel): external object that cares to handle messages
        signature (list): list of keys for JSON encoded dictionary of data
            that handler cares about

        """
        assert isinstance(channel, CommChannel)
        assert isinstance(signature, list)
        
        self.responders.append({'channel':channel, 'signature':signature})

    def matches_signature(self, channel, msg):
        """Determine if msg signature matches channel signature.

        channel (CommChannel): channel
        msg (Message): message

        returns: True or False

        """
        if (all (k in msg.data for k in channel['signature']) and
            all (k in channel['signature'] for k in msg.data)):
            return True

        else:
            return False
                
    def handle_msg(self, channel, msg):
        """Handle event with registered channel.

        msg is an incoming message which is handed to a channel for processing.

        channel (CommChannel): channel designated for handling this
            type of msg
        msg (Message): Message object message received by server

        return: Message response from channel, or None

        """
        assert isinstance(channel, CommChannel)
        assert isinstance(msg, Message)

        response = channel.respond(msg)

        assert (isinstance(response, (Message, dict)) or response is None)
        
        return response

    ####################
    # Client-focused methods
    ####################

    def capture_handler(self, http_handler):
        """Add http_handler to handler_queue and set event flags for later.
        
        Captures handler for later writing to by notify().
        
        http_handler (HttpHandler): request handler
        
        """
        # Check if handler for guid is already in queue. If so, close out all
        # old ones. Should only be at most one handler per guid, but loop just
        # in case.
        old_handler = self.retrieve_handler(http_handler.guid)
        while old_handler is not None:
            self.release_handler(old_handler)
            old_handler = self.retrieve_handler(http_handler.guid)    

        # If there are old messages in the queue, send them now
        output = self.get_old_messages(http_handler.guid)
        if len(output) > 0:
            # Return output for writing without further capture
            http_handler.send_message(output)
            return False

        else:
            # Set event() flag on handler
            http_handler.event = Event()
            http_handler.in_use = False
            
            # Add handler to list of active http handlers        
            self.handler_queue.append(http_handler)

            return True  # Do http_handler.event.wait() from calling method

    def retrieve_handler(self, guid):
{'fraction_non_alphanumeric': 0.07743598862019915, 'fraction_numerical': 0.0004445234708392603, 'mean_word_length': 2.2988269794721408, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5728638', 'n_tokens_mistral': 2739, 'n_tokens_neox': 2648, 'n_words': 1018}