diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..94487b951
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/.project b/.project
new file mode 100644
index 000000000..1b1ea2cbe
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ netium
+
+
+
+
+
+ org.python.pydev.PyDevBuilder
+
+
+
+
+
+ org.python.pydev.pythonNature
+
+
diff --git a/.pydevproject b/.pydevproject
new file mode 100644
index 000000000..c8b3d370e
--- /dev/null
+++ b/.pydevproject
@@ -0,0 +1,8 @@
+
+
+
+/${PROJECT_DIR_NAME}/src
+
+python 2.6
+Default
+
diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 000000000..998922fa3
--- /dev/null
+++ b/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/=utf-8
diff --git a/setup.py b/setup.py
new file mode 100644
index 000000000..7d19923cb
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,65 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Hive Pingu System
+# Copyright (C) 2008-2012 Hive Solutions Lda.
+#
+# This file is part of Hive Pingu System.
+#
+# Hive Pingu System is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Hive Pingu System is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Hive Pingu System. If not, see .
+
+__author__ = "João Magalhães "
+""" The author(s) of the module """
+
+__version__ = "1.0.0"
+""" The version of the module """
+
+__revision__ = "$LastChangedRevision$"
+""" The revision number of the module """
+
+__date__ = "$LastChangedDate$"
+""" The last change date of the module """
+
+__copyright__ = "Copyright (c) 2008-2012 Hive Solutions Lda."
+""" The copyright for the module """
+
+__license__ = "GNU General Public License (GPL), Version 3"
+""" The license for the module """
+
+import os
+import glob
+import setuptools
+
+setuptools.setup(
+ name = "netium",
+ version = "0.1.0",
+ author = "Hive Solutions Lda.",
+ author_email = "development@hive.pt",
+ description = "Netium System",
+ license = "GNU General Public License (GPL), Version 3",
+ keywords = "netium net infrastructure",
+ url = "http://netium.com",
+ packages = [
+ "netium"
+ ],
+ classifiers = [
+ "Development Status :: 3 - Alpha",
+ "Topic :: Utilities",
+ "License :: OSI Approved :: GNU General Public License (GPL)",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 2.6",
+ "Programming Language :: Python :: 2.7"
+ ]
+)
diff --git a/src/netium/__init__.py b/src/netium/__init__.py
new file mode 100644
index 000000000..bfc73c930
--- /dev/null
+++ b/src/netium/__init__.py
@@ -0,0 +1,39 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Hive Netium System
+# Copyright (C) 2008-2012 Hive Solutions Lda.
+#
+# This file is part of Hive Netium System.
+#
+# Hive Netium System is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Hive Netium System is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Hive Netium System. If not, see .
+
+__version__ = "1.0.0"
+""" The version of the module """
+
+__revision__ = "$LastChangedRevision$"
+""" The revision number of the module """
+
+__date__ = "$LastChangedDate$"
+""" The last change date of the module """
+
+__copyright__ = "Copyright (c) 2008-2012 Hive Solutions Lda."
+""" The copyright for the module """
+
+__license__ = "GNU General Public License (GPL), Version 3"
+""" The license for the module """
+
+import base
+
+from base import *
diff --git a/src/netium/base/__init__.py b/src/netium/base/__init__.py
new file mode 100644
index 000000000..1171a8da0
--- /dev/null
+++ b/src/netium/base/__init__.py
@@ -0,0 +1,47 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Hive Netium System
+# Copyright (C) 2008-2012 Hive Solutions Lda.
+#
+# This file is part of Hive Netium System.
+#
+# Hive Netium System is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Hive Netium System is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Hive Netium System. If not, see .
+
+__version__ = "1.0.0"
+""" The version of the module """
+
+__revision__ = "$LastChangedRevision$"
+""" The revision number of the module """
+
+__date__ = "$LastChangedDate$"
+""" The last change date of the module """
+
+__copyright__ = "Copyright (c) 2008-2012 Hive Solutions Lda."
+""" The copyright for the module """
+
+__license__ = "GNU General Public License (GPL), Version 3"
+""" The license for the module """
+
+import client
+import common
+import conn
+import observer
+import server
+
+from client import *
+from common import *
+from conn import *
+from observer import *
+from server import *
diff --git a/src/netium/base/client.py b/src/netium/base/client.py
new file mode 100644
index 000000000..b867523a6
--- /dev/null
+++ b/src/netium/base/client.py
@@ -0,0 +1,241 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Hive Netium System
+# Copyright (C) 2008-2012 Hive Solutions Lda.
+#
+# This file is part of Hive Netium System.
+#
+# Hive Netium System is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Hive Netium System is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Hive Netium System. If not, see .
+
+__author__ = "João Magalhães joamag@hive.pt>"
+""" The author(s) of the module """
+
+__version__ = "1.0.0"
+""" The version of the module """
+
+__revision__ = "$LastChangedRevision$"
+""" The revision number of the module """
+
+__date__ = "$LastChangedDate$"
+""" The last change date of the module """
+
+__copyright__ = "Copyright (c) 2008-2012 Hive Solutions Lda."
+""" The copyright for the module """
+
+__license__ = "GNU General Public License (GPL), Version 3"
+""" The license for the module """
+
+import socket
+
+from common import * #@UnusedWildImport
+
+class Client(Base):
+
+ def __init__(self, name = None, handler = None, *args, **kwargs):
+ Base.__init__(self, name = name, hadler = handler, *args, **kwargs)
+ self.pendings = []
+ self._pending_lock = threading.RLock()
+
+ def ticks(self):
+ self.set_state(STATE_TICK)
+ if self.pendings: self._connects()
+
+ def reads(self, reads):
+ self.set_state(STATE_READ)
+ for read in reads:
+ self.on_read(read)
+
+ def writes(self, writes):
+ self.set_state(STATE_WRITE)
+ for write in writes:
+ self.on_write(write)
+
+ def errors(self, errors):
+ self.set_state(STATE_ERRROR)
+ for error in errors:
+ self.on_error(error)
+
+ def connect(self, host, port, ssl = False, key_file = None, cer_file = None):
+ key_file = key_file or SSL_KEY_PATH
+ cer_file = cer_file or SSL_CER_PATH
+
+ _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ _socket.setblocking(0)
+
+ if ssl: _socket = self._ssl_wrap(
+ _socket,
+ key_file = key_file,
+ cer_file = cer_file,
+ server = False
+ )
+
+ _socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ _socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ _socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+ hasattr(socket, "SO_REUSEPORT") and\
+ _socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) #@UndefinedVariable
+
+ address = (host, port)
+
+ connection = self.new_connection(_socket, address, ssl = ssl)
+ self.pendings.append(connection)
+
+ return connection
+
+ def on_read(self, _socket):
+ connection = self.connections_m.get(_socket, None)
+ if not connection: return
+ if not connection.status == OPEN: return
+
+ try:
+ # verifies if there's any pending operations in the
+ # socket (eg: ssl handshaking) and performs them trying
+ # to finish them, in they are still pending at the current
+ # state returns immediately (waits for next loop)
+ if self._pending(_socket): return
+
+ # iterates continuously trying to read as much data as possible
+ # when there's a failure to read more data it should raise an
+ # exception that should be handled properly
+ while True:
+ data = _socket.recv(CHUNK_SIZE)
+ if data: self.on_data(connection, data)
+ else: self.on_connection_d(connection); break
+ except ssl.SSLError, error:
+ error_v = error.args[0]
+ if not error_v in SSL_VALID_ERRORS:
+ self.info(error)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.debug(line)
+ self.on_connection_d(connection)
+ except socket.error, error:
+ error_v = error.args[0]
+ if not error_v in VALID_ERRORS:
+ self.info(error)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.debug(line)
+ self.on_connection_d(connection)
+ except BaseException, exception:
+ self.info(exception)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.debug(line)
+ self.on_connection_d(connection)
+
+ def on_write(self, socket):
+ connection = self.connections_m.get(socket, None)
+ if not connection: return
+ if not connection.status == OPEN: return
+
+ if connection.connecting:
+ if connection.ssl: self._ssl_handshake(connection.socket)
+ else: self.on_connect(connection)
+
+ try:
+ connection._send()
+ except ssl.SSLError, error:
+ error_v = error.args[0]
+ if not error_v in SSL_VALID_ERRORS:
+ self.info(error)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.debug(line)
+ self.on_connection_d(connection)
+ except socket.error, error:
+ error_v = error.args[0]
+ if not error_v in VALID_ERRORS:
+ self.info(error)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.debug(line)
+ self.on_connection_d(connection)
+ except BaseException, exception:
+ self.info(exception)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.debug(line)
+ self.on_connection_d(connection)
+
+ def on_error(self, socket):
+ connection = self.connections_m.get(socket, None)
+ if not connection: return
+ if not connection.status == OPEN: return
+
+ self.on_connection_d(connection)
+
+ def on_connect(self, connection):
+ connection.set_connected()
+
+ def on_data(self, connection, data):
+ pass
+
+ def on_connection_c(self, connection):
+ connection.open(connect = True)
+
+ def on_connection_d(self, connection):
+ connection.close()
+
+ def _connects(self):
+ self._pending_lock.acquire()
+ try:
+ while self.pendings:
+ connection = self.pendings.pop()
+ self._connect(connection)
+ finally:
+ self._pending_lock.release()
+
+ def _connect(self, connection):
+ # retrieves the socket associated with the connection
+ # and call the on connection created handler to set the
+ # connection ready for the connect operation
+ _socket = connection.socket
+ self.on_connection_c(connection)
+
+ # tries to run the non blocking connection it should
+ # fail and the connection should only be considered as
+ # open when a write event is raised for the connection
+ try: _socket.connect(connection.address)
+ except ssl.SSLError, error:
+ error_v = error.args[0]
+ if not error_v in SSL_VALID_ERRORS:
+ self.info(error)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.debug(line)
+ except socket.error, error:
+ error_v = error.args[0]
+ if not error_v in VALID_ERRORS:
+ self.info(error)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.debug(line)
+
+ # in case the connection is not of type ssl the method
+ # may returns as there's nothing left to be done, as the
+ # rest of the method is dedicated to ssl tricks
+ if not connection.ssl: return
+
+ # creates the ssl object for the socket as it may have been
+ # destroyed by the underlying ssl library (as an error) because
+ # the socket is of type non blocking and raises an error
+ _socket._sslobj = _socket._sslobj or ssl._ssl.sslwrap(
+ _socket._sock,
+ False,
+ _socket.keyfile,
+ _socket.certfile,
+ _socket.cert_reqs,
+ _socket.ssl_version,
+ _socket.ca_certs
+ )
+
+ def _ssl_handshake(self, _socket):
+ Base._ssl_handshake(self, _socket)
+ if _socket._pending: return
+ connection = self.connections_m.get(_socket, None)
+ connection and self.on_connect(connection)
diff --git a/src/netium/base/common.py b/src/netium/base/common.py
new file mode 100644
index 000000000..3eede48cb
--- /dev/null
+++ b/src/netium/base/common.py
@@ -0,0 +1,392 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Hive Netium System
+# Copyright (C) 2008-2012 Hive Solutions Lda.
+#
+# This file is part of Hive Netium System.
+#
+# Hive Netium System is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Hive Netium System is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Hive Netium System. If not, see .
+
+__author__ = "João Magalhães joamag@hive.pt>"
+""" The author(s) of the module """
+
+__version__ = "1.0.0"
+""" The version of the module """
+
+__revision__ = "$LastChangedRevision$"
+""" The revision number of the module """
+
+__date__ = "$LastChangedDate$"
+""" The last change date of the module """
+
+__copyright__ = "Copyright (c) 2008-2012 Hive Solutions Lda."
+""" The copyright for the module """
+
+__license__ = "GNU General Public License (GPL), Version 3"
+""" The license for the module """
+
+import os
+import ssl
+import time
+import types
+import errno
+import select
+import logging
+import traceback
+
+import observer
+
+from conn import * #@UnusedWildImport
+
+WSAEWOULDBLOCK = 10035
+""" The wsa would block error code meant to be used on
+windows environments as a replacement for the would block
+error code that indicates the failure to operate on a non
+blocking connection """
+
+VALID_ERRORS = (
+ errno.EWOULDBLOCK,
+ errno.EAGAIN,
+ errno.EPERM,
+ errno.ENOENT,
+ WSAEWOULDBLOCK
+)
+""" List containing the complete set of error that represent
+non ready operations in a non blocking socket """
+
+SSL_VALID_ERRORS = (
+ ssl.SSL_ERROR_WANT_READ,
+ ssl.SSL_ERROR_WANT_WRITE
+)
+""" The list containing the valid error in the handshake
+operation of the ssl connection establishment """
+
+STATE_STOP = 1
+""" The stop state value, this value is set when the service
+is either in the constructed stage or when the service has been
+stop normally or with an error """
+
+STATE_START = 2
+""" The start state set when the service is in the starting
+stage and running, normal state """
+
+STATE_CONFIG = 3
+""" The configuration state that is set when the service is
+preparing to become started and the configuration attributes
+are being set according to pre-determined indications """
+
+STATE_SELECT = 4
+""" State to be used when the service is in the select part
+of the loop, this is the most frequent state in an idle service
+as the service "spends" most of its time in it """
+
+STATE_TICK = 5
+""" Tick state representative of the situation where the loop
+tick operation is being started and all the pre tick handlers
+are going to be called for pre-operations """
+
+STATE_READ = 6
+""" Read state that is set when the connection are being read
+and the on data handlers are being called, this is the part
+where all the logic driven by incoming data is being called """
+
+STATE_WRITE = 7
+""" The write state that is set on the writing of data to the
+connections, this is a pretty "fast" state as no logic is
+associated with it """
+
+STATE_ERRROR = 8
+""" The error state to be used when the connection is processing
+any error state coming from its main select operation and associated
+with a certain connection (very rare) """
+
+STATE_STRINGS = (
+ "STOP",
+ "START",
+ "CONFIG",
+ "SELECT",
+ "TICK",
+ "READ",
+ "WRITE",
+ "ERROR"
+)
+""" Sequence that contains the various strings associated with
+the various states for the base service, this may be used to
+create an integer to string resolution mechanism """
+
+# initializes the various paths that are going to be used for
+# the base files configuration in the complete service infra
+# structure, these should include the ssl based files
+BASE_PATH = os.path.dirname(__file__)
+EXTRAS_PATH = os.path.join(BASE_PATH, "extras")
+SSL_KEY_PATH = os.path.join(EXTRAS_PATH, "net.key")
+SSL_CER_PATH = os.path.join(EXTRAS_PATH, "net.cer")
+
+class Base(observer.Observable):
+ """
+ Base network structure to be used by all the network
+ capable infra-structures (eg: servers and clients).
+
+ Should handle all the nonblocking event loop so that
+ the read and write operations are easy to handle.
+ """
+
+ def __init__(self, name = None, handler = None, *args, **kwargs):
+ observer.Observable.__init__(self, *args, **kwargs)
+ self.name = name or self.__class__.__name__
+ self.handler = handler
+ self.logger = None
+ self.read_l = []
+ self.write_l = []
+ self.error_l = []
+ self.connections = []
+ self.connections_m = {}
+ self._running = False
+ self._loaded = False
+ self.set_state(STATE_STOP);
+
+ def load(self):
+ if self._loaded: return
+
+ self.load_logging();
+ self._loaded = True
+
+ def load_logging(self, level = logging.DEBUG):
+ logging.basicConfig(format = "%(asctime)s [%(levelname)s] %(message)s")
+ self.logger = logging.getLogger(self.__class__.__name__)
+ self.logger.setLevel(level)
+ self.handler and self.logger.addHandler(self.handler)
+
+ def start(self):
+ # triggers the loading of the internal structures of
+ # the base structure in case the loading has already
+ # been done nothing is done (avoids duplicated load)
+ self.load()
+
+ # sets the running flag that controls the running of the
+ # main loop and then changes the current state to start
+ # as the main loop is going to start
+ self._running = True
+ self.set_state(STATE_START)
+
+ # enters the main loop operation printing a message
+ # to the logger indicating this start, this stage
+ # should block the thread until a stop call is made
+ self.info("Starting the service's \"loop\" stage")
+ try: self.loop()
+ except BaseException, exception:
+ self.error(exception)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.warning(line)
+ except:
+ self.critical("Critical level loop exception raised")
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.error(line)
+ finally:
+ self.info("Stopping the service's \"loop\" stage")
+ self.set_state(STATE_STOP)
+
+ def stop(self):
+ self._running = False
+
+ def is_empty(self):
+ return not self.read_l and not self.write_l and not self.error_l
+
+ def loop(self):
+ # iterates continuously while the running flag
+ # is set, once it becomes unset the loop breaks
+ # at the next execution cycle
+ while self._running:
+ # calls the base tick int handler indicating that a new
+ # tick loop iteration is going to be started, all the
+ # "in between loop" operation should be performed in this
+ # callback as this is the "space" they have for execution
+ self.ticks()
+
+ # updates the current state to select to indicate
+ # that the base service is selecting the connections
+ self.set_state(STATE_SELECT)
+
+ # verifies if the current selection list is empty
+ # in case it's sleeps for a while and then continues
+ # the loop (this avoids error in empty selection)
+ is_empty = self.is_empty()
+ if is_empty: time.sleep(0.25); continue
+
+ # runs the main selection operation on the current set
+ # of connection for each of the three operations returning
+ # the resulting active sets for the callbacks
+ reads, writes, errors = select.select(
+ self.read_l,
+ self.write_l,
+ self.error_l,
+ 0.25
+ )
+
+ # calls the various callbacks with the selections lists,
+ # these are the main entry points for the logic to be executed
+ # each of this methods should be implemented in the underlying
+ # class instances as no behavior is defined at this inheritance
+ # level (abstract class)
+ self.reads(reads)
+ self.writes(writes)
+ self.errors(errors)
+
+ def ticks(self):
+ self.set_state(STATE_TICK)
+
+ def reads(self, reads):
+ self.set_state(STATE_READ)
+
+ def writes(self, writes):
+ self.set_state(STATE_WRITE)
+
+ def errors(self, errors):
+ self.set_state(STATE_ERRROR)
+
+ def info_dict(self):
+ info = dict()
+ info["loaded"] = self._loaded
+ info["connections"] = len(self.connections)
+ info["state"] = self.get_state_s()
+ return info
+
+ def new_connection(self, socket, address, ssl = False):
+ """
+ Creates a new connection for the provided socket
+ object and string based address, the returned
+ value should be a workable object.
+
+ @type socket: Socket
+ @param socket: The socket object to be encapsulated
+ by the object to be created (connection).
+ @type address: String
+ @param address: The address as a string to be used to
+ describe the connection object to be created.
+ @type ssl: bool
+ @param ssl: If the connection to be created is meant to
+ be secured using the ssl framework for encryption.
+ @rtype: Connection
+ @return: The connection object that encapsulates the
+ provided socket and address values.
+ """
+
+ return Connection(self, socket, address, ssl = ssl)
+
+ def debug(self, object):
+ self.log(object, level = logging.DEBUG)
+
+ def info(self, object):
+ self.log(object, level = logging.INFO)
+
+ def warning(self, object):
+ self.log(object, level = logging.WARNING)
+
+ def error(self, object):
+ self.log(object, level = logging.ERROR)
+
+ def critical(self, object):
+ self.log(object, level = logging.CRITICAL)
+
+ def log(self, object, level = logging.INFO):
+ object_t = type(object)
+ message = unicode(object) if not object_t in types.StringTypes else object
+ self.logger.log(level, message)
+
+ def set_state(self, state):
+ self._state = state
+
+ def get_state_s(self, lower = True):
+ """
+ Retrieves a string describing the current state
+ of the system, this string should be as descriptive
+ as possible.
+
+ An optional parameter controls if the string should
+ be lower cased or not.
+
+ @type lower: bool
+ @param lower: If the returned string should be converted
+ into a lower cased version.
+ @rtype: String
+ @return: A string describing the current sate of the loop
+ system, should be as descriptive as possible.
+ """
+
+ state_s = STATE_STRINGS[self._state - 1]
+ state_s = state_s.lower() if lower else state_s
+ return state_s
+
+ def _pending(self, _socket):
+ """
+ Tries to perform the pending operations in the socket
+ and, these operations are set in the pending variable
+ of the socket structure.
+
+ The method returns if there are still pending operations
+ after this method tick.
+
+ @type _socket: Socket
+ @param _socket: The socket object to be checked for
+ pending operations and that is going to be used in the
+ performing of these operations.
+ @rtype: bool
+ @return: If there are still pending operations to be
+ performed in the provided socket.
+ """
+
+ # verifies if the pending attribute exists in the socket
+ # and that the value is valid, in case it's not there's
+ # no pending operation (method call) to be performed, and
+ # as such must return immediately with no pending value
+ if not hasattr(_socket, "_pending") or\
+ not _socket._pending: return False
+
+ # calls the pending callback method and verifies if the
+ # pending value still persists in the socket if that the
+ # case returns the is pending value to the caller method
+ _socket._pending(_socket)
+ is_pending = not _socket._pending == None
+ return is_pending
+
+ def _ssl_wrap(self, _socket, key_file = None, cer_file = None, server = True):
+ dir_path = os.path.dirname(__file__)
+ base_path = os.path.join(dir_path, "../../")
+ base_path = os.path.normpath(base_path)
+ extras_path = os.path.join(base_path, "extras")
+ ssl_path = os.path.join(extras_path, "ssl")
+
+ key_file = key_file or os.path.join(ssl_path, "server.key")
+ cer_file = cer_file or os.path.join(ssl_path, "server.cer")
+
+ socket_ssl = ssl.wrap_socket(
+ _socket,
+ keyfile = key_file,
+ certfile = cer_file,
+ server_side = server,
+ ssl_version = ssl.PROTOCOL_TLSv1,
+ do_handshake_on_connect = False
+ )
+ return socket_ssl
+
+ def _ssl_handshake(self, _socket):
+ try:
+ _socket.do_handshake()
+ _socket._pending = None
+ except ssl.SSLError, error:
+ error_v = error.args[0]
+ if error_v in SSL_VALID_ERRORS:
+ _socket._pending = self._ssl_handshake
+ else: raise
diff --git a/src/netium/base/conn.py b/src/netium/base/conn.py
new file mode 100644
index 000000000..7c226e012
--- /dev/null
+++ b/src/netium/base/conn.py
@@ -0,0 +1,200 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Hive Netium System
+# Copyright (C) 2008-2012 Hive Solutions Lda.
+#
+# This file is part of Hive Netium System.
+#
+# Hive Netium System is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Hive Netium System is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Hive Netium System. If not, see .
+
+__author__ = "João Magalhães joamag@hive.pt>"
+""" The author(s) of the module """
+
+__version__ = "1.0.0"
+""" The version of the module """
+
+__revision__ = "$LastChangedRevision$"
+""" The revision number of the module """
+
+__date__ = "$LastChangedDate$"
+""" The last change date of the module """
+
+__copyright__ = "Copyright (c) 2008-2012 Hive Solutions Lda."
+""" The copyright for the module """
+
+__license__ = "GNU General Public License (GPL), Version 3"
+""" The license for the module """
+
+import threading
+
+OPEN = 1
+""" The open status value, meant to be used in situations
+where the status of the entity is open (opposite of closed) """
+
+CLOSED = 2
+""" Closed status value to be used in entities which have
+no pending structured opened and operations are limited """
+
+CHUNK_SIZE = 4096
+""" The size of the chunk to be used while received
+data from the service socket """
+
+class Connection(object):
+ """
+ Abstract connection object that should encapsulate
+ a socket object enabling it to be accessed in much
+ more "protected" way avoiding possible sync problems.
+
+ It should also abstract the developer from all the
+ select associated complexities adding and removing the
+ underlying socket from the selecting mechanism for the
+ appropriate operations.
+ """
+
+ def __init__(self, owner, socket, address, ssl = False):
+ self.status = CLOSED
+ self.connecting = False
+ self.owner = owner
+ self.socket = socket
+ self.address = address
+ self.ssl = ssl
+ self.pending = []
+ self.pending_lock = threading.RLock()
+
+ def open(self, connect = False):
+ # in case the current status of the connection is not
+ # closed does not make sense to open it as it should
+ # already be open anyway (returns immediately)
+ if not self.status == CLOSED: return
+
+ # retrieves the reference to the owner object from the
+ # current instance to be used to add the socket to the
+ # proper pooling mechanisms (at least for reading)
+ owner = self.owner
+
+ # registers the socket for the proper reading mechanisms
+ # in the polling infra-structure of the owner
+ owner.read_l.append(self.socket)
+ owner.error_l.append(self.socket)
+
+ # adds the current connection object to the list of
+ # connections in the owner and the registers it in
+ # the map that associates the socket with the connection
+ owner.connections.append(self)
+ owner.connections_m[self.socket] = self
+
+ # sets the status of the current connection as open
+ # as all the internal structures have been correctly
+ # updated and not it's safe to perform operations
+ self.status = OPEN
+
+ # in case the connect flag is set must set the current
+ # connection as connecting indicating that some extra
+ # steps are still required to complete the connection
+ if connect: self.set_connecting()
+
+ def close(self):
+ # in case the current status of the connection is not open
+ # doen't make sense to close as it's already closed
+ if not self.status == OPEN: return
+
+ # immediately sets the status of the connection as closed
+ # so that no one else changed the current connection status
+ # this is relevant to avoid any erroneous situation
+ self.status = CLOSED
+
+ # retrieves the reference to the owner object from the
+ # current instance to be used to removed the socket from the
+ # proper pooling mechanisms (at least for reading)
+ owner = self.owner
+
+ # removes the socket from all the polling mechanisms so that
+ # interaction with it is no longer part of the selecting mechanism
+ if self.socket in owner.read_l: owner.read_l.remove(self.socket)
+ if self.socket in owner.write_l: owner.write_l.remove(self.socket)
+ if self.socket in owner.error_l: owner.error_l.remove(self.socket)
+
+ # removes the current connection from the list of connection in the
+ # owner and also from the map that associates the socket with the
+ # proper connection (also in the owner)
+ if self in owner.connections: owner.connections.remove(self)
+ if self.socket in owner.connections_m: del owner.connections_m[self.socket]
+
+ # closes the socket, using the proper gracefully way so that
+ # operations are no longer allowed in the socket, in case there's
+ # an error in the operation fails silently (un purpose)
+ try: self.socket.close()
+ except: pass
+
+ def set_connecting(self):
+ self.connecting = True
+ self.ensure_write()
+
+ def set_connected(self):
+ self.remove_write()
+ self.connecting = False
+
+ def ensure_write(self):
+ if not self.status == OPEN: return
+ if self.socket in self.owner.write_l: return
+ self.owner.write_l.append(self.socket)
+
+ def remove_write(self):
+ if not self.status == OPEN: return
+ if not self.socket in self.owner.write_l: return
+ self.owner.write_l.remove(self.socket)
+
+ def send(self, data):
+ """
+ The main send call to be used by a proxy connection and
+ from different threads.
+
+ Calling this method should be done with care as this can
+ create dead lock or socket corruption situations.
+
+ @type data: String
+ @param data: The buffer containing the data to be sent
+ through this connection to the other endpoint.
+ """
+
+ self.ensure_write()
+ self.pending_lock.acquire()
+ try: self.pending.insert(0, data)
+ finally: self.pending_lock.release()
+
+ def recv(self, size = CHUNK_SIZE):
+ return self._recv(size = size)
+
+ def is_open(self):
+ return self.status == OPEN
+
+ def is_closed(self):
+ return self.status == CLOSED
+
+ def _send(self):
+ self.pending_lock.acquire()
+ try:
+ while True:
+ if not self.pending: break
+ data = self.pending.pop()
+ try: self.socket.send(data)
+ except: self.pending.append(data)
+ finally:
+ self.pending_lock.release()
+
+ self.remove_write()
+
+ def _recv(self, size):
+ return self.socket.recv(size)
diff --git a/src/netium/base/observer.py b/src/netium/base/observer.py
new file mode 100644
index 000000000..e831ee9ad
--- /dev/null
+++ b/src/netium/base/observer.py
@@ -0,0 +1,57 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Hive Netium System
+# Copyright (C) 2008-2012 Hive Solutions Lda.
+#
+# This file is part of Hive Netium System.
+#
+# Hive Netium System is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Hive Netium System is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Hive Netium System. If not, see .
+
+__author__ = "João Magalhães joamag@hive.pt>"
+""" The author(s) of the module """
+
+__version__ = "1.0.0"
+""" The version of the module """
+
+__revision__ = "$LastChangedRevision$"
+""" The revision number of the module """
+
+__date__ = "$LastChangedDate$"
+""" The last change date of the module """
+
+__copyright__ = "Copyright (c) 2008-2012 Hive Solutions Lda."
+""" The copyright for the module """
+
+__license__ = "GNU General Public License (GPL), Version 3"
+""" The license for the module """
+
+class Observable(object):
+
+ def __init__(self, *args, **kwargs):
+ self.events = {}
+
+ def bind(self, name, method):
+ methods = self.events.get(name, [])
+ methods.append(method)
+ self.events[name] = methods
+
+ def unbind(self, name, method = None):
+ methods = self.events.get(name, [])
+ if method: methods.remove(method)
+ else: methods[:] = []
+
+ def trigger(self, name, *args, **kwargs):
+ methods = self.events.get(name, [])
+ for method in methods: method(*args, **kwargs)
diff --git a/src/netium/base/server.py b/src/netium/base/server.py
new file mode 100644
index 000000000..1030efa8a
--- /dev/null
+++ b/src/netium/base/server.py
@@ -0,0 +1,249 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Hive Netium System
+# Copyright (C) 2008-2012 Hive Solutions Lda.
+#
+# This file is part of Hive Netium System.
+#
+# Hive Netium System is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Hive Netium System is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Hive Netium System. If not, see .
+
+__author__ = "João Magalhães joamag@hive.pt>"
+""" The author(s) of the module """
+
+__version__ = "1.0.0"
+""" The version of the module """
+
+__revision__ = "$LastChangedRevision$"
+""" The revision number of the module """
+
+__date__ = "$LastChangedDate$"
+""" The last change date of the module """
+
+__copyright__ = "Copyright (c) 2008-2012 Hive Solutions Lda."
+""" The copyright for the module """
+
+__license__ = "GNU General Public License (GPL), Version 3"
+""" The license for the module """
+
+import socket
+
+from common import * #@UnusedWildImport
+
+class Server(Base):
+
+ def __init__(self, name = None, handler = None, *args, **kwargs):
+ Base.__init__(self, name = name, hadler = handler, *args, **kwargs)
+ self.socket = None
+ self.host = None
+ self.port = None
+ self.ssl = False
+
+ def reads(self, reads):
+ self.set_state(STATE_READ)
+ for read in reads:
+ if read == self.socket: self.on_read_s(read)
+ else: self.on_read(read)
+
+ def writes(self, writes):
+ self.set_state(STATE_WRITE)
+ for write in writes:
+ if write == self.socket: self.on_write_s(write)
+ else: self.on_write(write)
+
+ def errors(self, errors):
+ self.set_state(STATE_ERRROR)
+ for error in errors:
+ if error == self.socket: self.on_error_s(error)
+ else: self.on_error(error)
+
+ def info_dict(self):
+ info = Base.info_dict(self)
+ info["host"] = self.host
+ info["port"] = self.port
+ info["ssl"] = self.ssl
+ return info
+
+ def serve(self, host = "127.0.0.1", port = 9090, ssl = False, key_file = None, cer_file = None):
+ # updates the current service status to the configuration
+ # stage as the next steps is to configure the service socket
+ self.set_state(STATE_CONFIG)
+
+ # populates the basic information on the currently running
+ # server like the host the port and the (is) ssl flag to be
+ # used latter for reference operations
+ self.host = host
+ self.port = port
+ self.ssl = ssl
+
+ # defaults the provided ssl key and certificate paths to the
+ # ones statically defined (dummy certificates), please beware
+ # that using these certificates may create validation problems
+ key_file = key_file or SSL_KEY_PATH
+ cer_file = cer_file or SSL_CER_PATH
+
+ # creates the socket that it's going to be used for the listening
+ # of new connections (server socket) and sets it as non blocking
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.socket.setblocking(0)
+
+ # in case the server is meant to be used as ssl wraps the socket
+ # in suck fashion so that it becomes "secured"
+ if ssl: self.socket = self._ssl_wrap(
+ self.socket,
+ key_file = key_file,
+ cer_file = cer_file,
+ server = True
+ )
+
+ # sets the various options in the service socket so that it becomes
+ # ready for the operation with the highest possible performance, these
+ # options include the reuse address to be able to re-bind to the port
+ # and address and the keep alive that drops connections after some time
+ # avoiding the leak of connections (operative system managed)
+ self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+ hasattr(socket, "SO_REUSEPORT") and\
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) #@UndefinedVariable
+
+ # adds the socket to all of the pool lists so that it's ready to read
+ # write and handle error, this is the expected behavior of a service
+ # socket so that it can handle all of the expected operations
+ self.read_l.append(self.socket)
+ self.write_l.append(self.socket)
+ self.error_l.append(self.socket)
+
+ # binds the socket to the provided host and port and then start the
+ # listening in the socket with the maximum backlog as possible
+ self.socket.bind((host, port))
+ self.socket.listen(5)
+
+ # starts the base system so that the event loop gets started and the
+ # the servers gets ready to accept new connections (starts service)
+ self.start()
+
+ def on_read_s(self, _socket):
+ try:
+ socket_c, address = _socket.accept()
+ try: self.on_socket_c(socket_c, address)
+ except: socket_c.close(); raise
+ except BaseException, exception:
+ self.info(exception)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.debug(line)
+
+ def on_write_s(self, socket):
+ pass
+
+ def on_error_s(self, socket):
+ pass
+
+ def on_read(self, _socket):
+ connection = self.connections_m.get(_socket, None)
+ if not connection: return
+ if not connection.status == OPEN: return
+
+ try:
+ # verifies if there's any pending operations in the
+ # socket (eg: ssl handshaking) and performs them trying
+ # to finish them, in they are still pending at the current
+ # state returns immediately (waits for next loop)
+ if self._pending(_socket): return
+
+ # iterates continuously trying to read as much data as possible
+ # when there's a failure to read more data it should raise an
+ # exception that should be handled properly
+ while True:
+ data = _socket.recv(CHUNK_SIZE)
+ if data: self.on_data(connection, data)
+ else: self.on_connection_d(connection); break
+ except ssl.SSLError, error:
+ error_v = error.args[0]
+ if not error_v in SSL_VALID_ERRORS:
+ self.info(error)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.debug(line)
+ self.on_connection_d(connection)
+ except socket.error, error:
+ error_v = error.args[0]
+ if not error_v in VALID_ERRORS:
+ self.info(error)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.debug(line)
+ self.on_connection_d(connection)
+ except BaseException, exception:
+ self.info(exception)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.debug(line)
+ self.on_connection_d(connection)
+
+ def on_write(self, socket):
+ connection = self.connections_m.get(socket, None)
+ if not connection: return
+ if not connection.status == OPEN: return
+
+ try:
+ connection._send()
+ except ssl.SSLError, error:
+ error_v = error.args[0]
+ if not error_v in SSL_VALID_ERRORS:
+ self.info(error)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.debug(line)
+ self.on_connection_d(connection)
+ except socket.error, error:
+ error_v = error.args[0]
+ if not error_v in VALID_ERRORS:
+ self.info(error)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.debug(line)
+ self.on_connection_d(connection)
+ except BaseException, exception:
+ self.info(exception)
+ lines = traceback.format_exc().splitlines()
+ for line in lines: self.debug(line)
+ self.on_connection_d(connection)
+
+ def on_error(self, socket):
+ connection = self.connections_m.get(socket, None)
+ if not connection: return
+ if not connection.status == OPEN: return
+
+ self.on_connection_d(connection)
+
+ def on_data(self, connection, data):
+ pass
+
+ def on_socket_c(self, socket_c, address):
+ if self.ssl: socket_c.pending = None
+
+ socket_c.setblocking(0)
+ socket_c.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ socket_c.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+
+ if self.ssl: self._ssl_handshake(socket_c)
+
+ connection = self.new_connection(socket_c, address, ssl = self.ssl)
+ self.on_connection_c(connection)
+
+ def on_socket_d(self, socket_c):
+ connection = self.connections_m.get(socket_c, None)
+ if not connection: return
+
+ def on_connection_c(self, connection):
+ connection.open()
+
+ def on_connection_d(self, connection):
+ connection.close()
diff --git a/src/netium/clients/__init__.py b/src/netium/clients/__init__.py
new file mode 100644
index 000000000..7696cd943
--- /dev/null
+++ b/src/netium/clients/__init__.py
@@ -0,0 +1,41 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Hive Netium System
+# Copyright (C) 2008-2012 Hive Solutions Lda.
+#
+# This file is part of Hive Netium System.
+#
+# Hive Netium System is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Hive Netium System is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Hive Netium System. If not, see .
+
+__version__ = "1.0.0"
+""" The version of the module """
+
+__revision__ = "$LastChangedRevision$"
+""" The revision number of the module """
+
+__date__ = "$LastChangedDate$"
+""" The last change date of the module """
+
+__copyright__ = "Copyright (c) 2008-2012 Hive Solutions Lda."
+""" The copyright for the module """
+
+__license__ = "GNU General Public License (GPL), Version 3"
+""" The license for the module """
+
+import apn
+import http
+
+from apn import *
+from http import *
diff --git a/src/netium/clients/apn.py b/src/netium/clients/apn.py
new file mode 100644
index 000000000..5279b8a0e
--- /dev/null
+++ b/src/netium/clients/apn.py
@@ -0,0 +1,51 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Hive Netium System
+# Copyright (C) 2008-2012 Hive Solutions Lda.
+#
+# This file is part of Hive Netium System.
+#
+# Hive Netium System is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Hive Netium System is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Hive Netium System. If not, see .
+
+__author__ = "João Magalhães joamag@hive.pt>"
+""" The author(s) of the module """
+
+__version__ = "1.0.0"
+""" The version of the module """
+
+__revision__ = "$LastChangedRevision$"
+""" The revision number of the module """
+
+__date__ = "$LastChangedDate$"
+""" The last change date of the module """
+
+__copyright__ = "Copyright (c) 2008-2012 Hive Solutions Lda."
+""" The copyright for the module """
+
+__license__ = "GNU General Public License (GPL), Version 3"
+""" The license for the module """
+
+import netium
+
+TOKEN_STRING = "asdads"
+
+class ApnClient(netium.Client):
+
+ def send_message(self, token_string = TOKEN_STRING, *args, **kwargs):
+ message = kwargs.get("message", "Hello World")
+ sound = kwargs.get("sound", "default")
+ badge = kwargs.get("badge", 0)
+ sandbox = kwargs.get("sandbox", True)
+ wait = kwargs.get("wait", False)
diff --git a/src/netium/clients/http.py b/src/netium/clients/http.py
new file mode 100644
index 000000000..4b5b55a66
--- /dev/null
+++ b/src/netium/clients/http.py
@@ -0,0 +1,119 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Hive Netium System
+# Copyright (C) 2008-2012 Hive Solutions Lda.
+#
+# This file is part of Hive Netium System.
+#
+# Hive Netium System is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Hive Netium System is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Hive Netium System. If not, see .
+
+__author__ = "João Magalhães joamag@hive.pt>"
+""" The author(s) of the module """
+
+__version__ = "1.0.0"
+""" The version of the module """
+
+__revision__ = "$LastChangedRevision$"
+""" The revision number of the module """
+
+__date__ = "$LastChangedDate$"
+""" The last change date of the module """
+
+__copyright__ = "Copyright (c) 2008-2012 Hive Solutions Lda."
+""" The copyright for the module """
+
+__license__ = "GNU General Public License (GPL), Version 3"
+""" The license for the module """
+
+import urlparse
+
+import netium
+
+class HttpConnection(netium.Connection):
+
+ def __init__(self, owner, socket, address, ssl = False):
+ netium.Connection.__init__(self, owner, socket, address, ssl = ssl)
+ self.version = "HTTP/1.0"
+ self.method = "GET"
+ self.url = None
+ self.ssl = False
+ self.host = None
+ self.port = None
+ self.path = None
+
+ def set_http(
+ self,
+ version = "HTTP/1.0",
+ method = "GET",
+ url = None,
+ host = None,
+ port = None,
+ path = None,
+ ssl = False
+ ):
+ self.method = method.upper()
+ self.version = version
+ self.url = url
+ self.host = host
+ self.port = port
+ self.path = path
+ self.ssl = ssl
+
+class HttpClient(netium.Client):
+ """
+ Simple test of an http client
+ """
+
+ def get(self, url):
+ parsed = urlparse.urlparse(url)
+ ssl = parsed.scheme == "https"
+ host = parsed.hostname
+ port = parsed.port or (ssl and 443 or 80)
+ path = parsed.path
+
+ connection = self.connect(host, port, ssl = ssl)
+ connection.set_http(
+ version = "HTTP/1.0",
+ method = "GET",
+ url = url,
+ host = host,
+ port = port,
+ path = path,
+ ssl = ssl
+ )
+
+ def on_connect(self, connection):
+ netium.Client.on_connect(self, connection)
+
+ method = connection.method
+ path = connection.path
+ version = connection.version
+
+ connection.send("%s %s %s\r\n\r\n" % (method, path, version))
+
+ def on_data(self, connection, data):
+ netium.Client.on_data(self, connection, data)
+
+ headers, message = data.split("\r\n\r\n", 1)
+ self.on_data_http(headers, message)
+
+ def on_connection_d(self, connection):
+ netium.Client.on_connection_d(self, connection)
+
+ def new_connection(self, socket, address, ssl = False):
+ return HttpConnection(self, socket, address, ssl = ssl)
+
+ def on_data_http(self, headers, message):
+ print message
diff --git a/src/netium/servers/__init__.py b/src/netium/servers/__init__.py
new file mode 100644
index 000000000..3374cfce7
--- /dev/null
+++ b/src/netium/servers/__init__.py
@@ -0,0 +1,41 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Hive Netium System
+# Copyright (C) 2008-2012 Hive Solutions Lda.
+#
+# This file is part of Hive Netium System.
+#
+# Hive Netium System is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Hive Netium System is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Hive Netium System. If not, see .
+
+__version__ = "1.0.0"
+""" The version of the module """
+
+__revision__ = "$LastChangedRevision$"
+""" The revision number of the module """
+
+__date__ = "$LastChangedDate$"
+""" The last change date of the module """
+
+__copyright__ = "Copyright (c) 2008-2012 Hive Solutions Lda."
+""" The copyright for the module """
+
+__license__ = "GNU General Public License (GPL), Version 3"
+""" The license for the module """
+
+import echo
+import ws
+
+from echo import *
+from ws import *
diff --git a/src/netium/servers/echo.py b/src/netium/servers/echo.py
new file mode 100644
index 000000000..f343028f6
--- /dev/null
+++ b/src/netium/servers/echo.py
@@ -0,0 +1,50 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Hive Netium System
+# Copyright (C) 2008-2012 Hive Solutions Lda.
+#
+# This file is part of Hive Netium System.
+#
+# Hive Netium System is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Hive Netium System is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Hive Netium System. If not, see .
+
+__author__ = "João Magalhães joamag@hive.pt>"
+""" The author(s) of the module """
+
+__version__ = "1.0.0"
+""" The version of the module """
+
+__revision__ = "$LastChangedRevision$"
+""" The revision number of the module """
+
+__date__ = "$LastChangedDate$"
+""" The last change date of the module """
+
+__copyright__ = "Copyright (c) 2008-2012 Hive Solutions Lda."
+""" The copyright for the module """
+
+__license__ = "GNU General Public License (GPL), Version 3"
+""" The license for the module """
+
+import ws
+
+class EchoServer(ws.WSServer):
+
+ def on_data_ws(self, connection, data):
+ ws.WSServer.on_data_ws(self, connection, data)
+ connection.send_ws(data)
+
+if __name__ == "__main__":
+ server = EchoServer()
+ server.serve()
diff --git a/src/netium/servers/ws.py b/src/netium/servers/ws.py
new file mode 100644
index 000000000..92b30b330
--- /dev/null
+++ b/src/netium/servers/ws.py
@@ -0,0 +1,260 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Hive Netium System
+# Copyright (C) 2008-2012 Hive Solutions Lda.
+#
+# This file is part of Hive Netium System.
+#
+# Hive Netium System is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Hive Netium System is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Hive Netium System. If not, see .
+
+__author__ = "João Magalhães joamag@hive.pt>"
+""" The author(s) of the module """
+
+__version__ = "1.0.0"
+""" The version of the module """
+
+__revision__ = "$LastChangedRevision$"
+""" The revision number of the module """
+
+__date__ = "$LastChangedDate$"
+""" The last change date of the module """
+
+__copyright__ = "Copyright (c) 2008-2012 Hive Solutions Lda."
+""" The copyright for the module """
+
+__license__ = "GNU General Public License (GPL), Version 3"
+""" The license for the module """
+
+import base64
+import hashlib
+
+import netium
+
+class WSConnection(netium.Connection):
+
+ def __init__(self, owner, socket, address, ssl = False):
+ netium.Connection.__init__(self, owner, socket, address, ssl = ssl)
+ self.handshake = False
+ self.method = None
+ self.path = None
+ self.version = None
+ self.buffer_l = []
+ self.headers = {}
+
+ def send_ws(self, data):
+ encoded = self._encode(data)
+ return self.send(encoded)
+
+ def recv_ws(self, size = netium.CHUNK_SIZE):
+ data = self.recv(size = size)
+ decoded = self._decode(data)
+ return decoded
+
+ def add_buffer(self, data):
+ self.buffer_l.append(data)
+
+ def do_handshake(self):
+ if self.handshake:
+ raise RuntimeError("Handshake already done")
+
+ buffer = "".join(self.buffer_l)
+ if not buffer[-4:] == "\r\n\r\n":
+ raise RuntimeError("Missing data for handshake")
+
+ lines = buffer.split("\r\n")
+ for line in lines[1:]:
+ values = line.split(":")
+ values_l = len(values)
+ if not values_l == 2: continue
+
+ key, value = values
+ key = key.strip()
+ value = value.strip()
+ self.headers[key] = value
+
+ first = lines[0]
+ self.method, self.path, self.version = first.split(" ")
+
+ del self.buffer_l[:]
+ self.handshake = True
+
+ def accept_key(self):
+ socket_key = self.headers.get("Sec-WebSocket-Key", None)
+ if not socket_key:
+ raise RuntimeError("No socket key found in headers")
+
+ hash = hashlib.sha1(socket_key + WSServer.MAGIC_VALUE)
+ hash_digest = hash.digest()
+ accept_key = base64.b64encode(hash_digest)
+ return accept_key
+
+ def get_buffer(self, delete = True):
+ if not self.buffer_l: return ""
+ buffer = "".join(self.buffer_l)
+ if delete: del self.buffer_l[:]
+ return buffer
+
+ def _encode(self, data):
+ data_l = len(data)
+ encoded_l = list()
+
+ encoded_l.append(chr(129))
+
+ if data_l <= 125:
+ encoded_l.append(chr(data_l))
+
+ elif data_l >= 126 and data_l <= 65535:
+ encoded_l.append(chr(126))
+ encoded_l.append(chr((data_l >> 8) & 255))
+ encoded_l.append(chr(data_l & 255))
+
+ else:
+ encoded_l.append(chr(127))
+ encoded_l.append(chr((data_l >> 56) & 255))
+ encoded_l.append(chr((data_l >> 48) & 255))
+ encoded_l.append(chr((data_l >> 40) & 255))
+ encoded_l.append(chr((data_l >> 32) & 255))
+ encoded_l.append(chr((data_l >> 24) & 255))
+ encoded_l.append(chr((data_l >> 16) & 255))
+ encoded_l.append(chr((data_l >> 8) & 255))
+ encoded_l.append(chr(data_l & 255))
+
+ encoded_l.append(data)
+ encoded = "".join(encoded_l)
+ return encoded
+
+ def _decode(self, data):
+ second_byte = data[1]
+
+ length = ord(second_byte) & 127
+
+ index_mask_f = 2
+
+ if length == 126:
+ length = 0
+ length += ord(data[2]) << 8
+ length += ord(data[3])
+ index_mask_f = 4
+
+ elif length == 127:
+ length = 0
+ length += ord(data[2]) << 56
+ length += ord(data[3]) << 48
+ length += ord(data[4]) << 40
+ length += ord(data[5]) << 32
+ length += ord(data[6]) << 24
+ length += ord(data[7]) << 16
+ length += ord(data[8]) << 8
+ length += ord(data[9])
+ index_mask_f = 10
+
+ # calculates the size of the raw data part of the message and
+ # in case its smaller than the defined length of the data returns
+ # immediately indicating that there's not enough data to complete
+ # the decoding of the data (should be re-trying again latter)
+ raw_size = len(data) - index_mask_f - 4
+ if raw_size < length:
+ raise RuntimeError("Not enough data available for parsing")
+
+ # retrieves the masks part of the data that are going to be
+ # used in the decoding part of the process
+ masks = data[index_mask_f:index_mask_f + 4]
+
+ # allocates the array that is going to be used
+ # for the decoding of the data with the length
+ # that was computed as the data length
+ decoded_a = bytearray(length)
+
+ # starts the initial data index and then iterates over the
+ # range of decoded length applying the mask to the data
+ # (decoding it consequently) to the created decoded array
+ i = index_mask_f + 4
+ for j in range(length):
+ decoded_a[j] = chr(ord(data[i]) ^ ord(masks[j % 4]))
+ i += 1
+
+ # converts the decoded array of data into a string and
+ # and returns the "partial" string containing the data that
+ # remained pending to be parsed
+ decoded = str(decoded_a)
+ return decoded, data[i:]
+
+class WSServer(netium.Server):
+ """
+ Base class for the creation of websocket server, should
+ handle both the upgrading/handshaking of the connection
+ and together with the associated connection class the
+ encoding and decoding of the frames.
+ """
+
+ MAGIC_VALUE = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+ """ The magic value used by the websocket protocol as part
+ of the key generation process in the handshake """
+
+ def on_connection_c(self, connection):
+ netium.Server.on_connection_c(self, connection)
+
+ def on_data(self, connection, data):
+ netium.Server.on_data(self, connection, data)
+
+ if connection.handshake:
+ while data:
+ buffer = connection.get_buffer()
+ try: decoded, data = connection._decode(buffer + data)
+ except: self.add_buffer(data); break
+ self.on_data_ws(connection, decoded)
+
+ else:
+ connection.add_buffer(data)
+ connection.do_handshake()
+ accept_key = connection.accept_key()
+ response = self._handshake_response(accept_key)
+ connection.send(response)
+ self.on_handshake(connection)
+
+ def new_connection(self, socket, address, ssl = False):
+ return WSConnection(self, socket, address, ssl = ssl)
+
+ def send_ws(self, connection, data):
+ encoded = self._encode(data)
+ connection.send(encoded)
+
+ def on_data_ws(self, connection, data):
+ pass
+
+ def on_handshake(self, connection):
+ pass
+
+ def _handshake_response(self, accept_key):
+ """
+ Returns the response contents of the handshake operation for
+ the provided accept key.
+
+ The key value should already be calculated according to the
+ specification.
+
+ @type accept_key: String
+ @param accept_key: The accept key to be used in the creation
+ of the response message.
+ @rtype: String
+ @return: The response message contents generated according to
+ the specification and the provided accept key.
+ """
+
+ data = "HTTP/1.1 101 Switching Protocols\r\n" +\
+ "Upgrade: websocket\r\n" +\
+ "Connection: Upgrade\r\n" +\
+ "Sec-WebSocket-Accept: %s\r\n\r\n" % accept_key
+ return data