Source code for pyrsslocal.custom_server.aserver

"""
This modules contains a class which implements a simple server.


:githublink:`%|py|5`
"""

import sys
import os
import urllib
import datetime
from http.server import HTTPServer
from socketserver import ThreadingMixIn

from pyquickhelper.loghelper import fLOG
from pyensae.sql.database_main import Database
from ..simple_server.simple_server_custom import SimpleHandler, ThreadServer


[docs]class CustomDBServerHandler(SimpleHandler): """ The server proposes a simple way to create one server on your own. It includes an access to a SQLlite3 database. :githublink:`%|py|23` """
[docs] def __init__(self, request, client_address, server): """ Regular constructor, an instance is created for each request, do not store any data for a longer time than a request. :githublink:`%|py|29` """ SimpleHandler.__init__(self, request, client_address, server)
#self.m_database = server._my_database #self.m_main_page = server._my_main_page #self.m_root = server._my_root
[docs] def main_page(self): """ returns the main page (case the server is called with no path) :return: default page :githublink:`%|py|39` """ return self.server._my_main_page
[docs] def get_javascript_paths(self): """ returns all the location where the server should look for a java script :return: list of paths :githublink:`%|py|46` """ return [self.server._my_root, SimpleHandler.javascript_path]
[docs] def interpret_parameter_as_list_int(self, ps): """ interpret a list of parameters, each of them is a list of integer separated by , :param ps: something like ``params.get("blog_selected")`` :return: list of int :githublink:`%|py|56` """ res = [] for ins in ps: spl = ins.split(",") ii = [int(_) for _ in spl] res.extend(ii) return res
[docs] def process_event(self, st): """ process an event, and log it :param st: string to process :githublink:`%|py|69` """ self.server.process_event(st)
[docs] def serve_content_web(self, path, method, params): """ functions to overload (executed after serve_content) :param path: ParseResult :param method: GET or POST :param params: params parsed from the url + others :githublink:`%|py|79` """ if path.path.startswith("/logs/"): url = path.path[6:] targ = urllib.parse.unquote(url) self.process_event(targ) self.send_response(200) self.send_headers("") else: url = path.path htype, ftype = self.get_ftype(url) for p in self.server._my_root: local = os.path.join(p, url.lstrip("/")) if os.path.exists(local): break if htype == "text/html": if os.path.exists(local): content = self.get_file_content(local, ftype) self.send_response(200) self.send_headers(path.path) # context params["db"] = self.server._my_database params["page"] = url params[ "website"] = "http://%s:%d/" % self.server.server_address self.feed(content, True, params) else: self.send_response(200) self.send_headers("") self.feed( "unable to find (CustomServerHanlder): " + path.geturl() + "\nlocal file:" + local + "\n") self.send_error(404) elif os.path.exists(local): content = self.get_file_content(local, ftype) self.send_response(200) self.send_headers(url) self.feed(content, False, params) else: self.send_response(200) self.send_headers("") self.feed( "unable to find (CustomServerHanlder): " + path.geturl() + "\nlocal file:" + local + "\n") self.send_error(404)
[docs]class CustomDBServer (ThreadingMixIn, HTTPServer): """ defines a custom server which includes an access to a database, this database will contain de table to store the clicks .. exref:: :title: create a custom local server The following code creates an instance of a local server. The server expects to find its content in the same folder. :: from pyensae import Database db = Database(dbfile) df = pandas.DataFrame ( [ {"name":"xavier", "module":"pyrsslocal"} ] ) db.connect() db.import_dataframe(df, "example") db.close() url = "http://localhost:%d/p_aserver.html" % port webbrowser.open(url) CustomDBServer.run_server(None, dbfile, port = port, extra_path = os.path.join(".")) The main page is the following one and it can contains a Python script which will be interpreter by the server. It gives access to a variable ``db`` which is a local database in SQLlite. :: <?xml version="1.0" encoding="utf-8"?> <html> <head> <link type="text/css" href="/p_aserver.css" rel="stylesheet"/> <title>Custom DB Server</title> <meta content="dupre, pyrsslocal, custom server" name="keywords"/> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> <link rel="shortcut icon" href="p_aserver.ico" /> <meta content="CustomServer from pyrsslocal" name="description" /> <script type="text/javascript" src="/p_aserver.js"></script> <script src="/js/run_prettify.js" type="text/javascript"></script> </head> <body onload="setPositions(['divtable', ])" class="mymainbody"> <div class="divtop"> <h1>Custom DB Server unittest</h1> </div> <div class="divtable" id="divfiles" onscroll="savePosition('divtable')"> <h2>Content of table example</h2> <script type="text/python"> print("<table>") db.connect() for row in db.execute_view("SELECT * FROM example") : srow = [ str(_) for _ in row ] print( "<tr><td>{0}</td></tr>".format("</td><td>".join(srow) ) ) db.close() print("</table>") </script> <p>end.</p> </div> </body> </html> :githublink:`%|py|209` """
[docs] @staticmethod def schema_table(table): """ returns the schema for a specific table :param table: name (in ["stats", "event"]) :return: dictionary :githublink:`%|py|218` """ if table == "stats": return {0: ("id_post", int), 1: ("dtime", datetime.datetime), 2: ("status", str), 3: ("rate", int), 4: ("comment", str), } elif table == "event": return {-1: ("id_event", int, "PRIMARYKEY", "AUTOINCREMENT"), 0: ("dtime", datetime.datetime), 1: ("uuid", str), 2: ("type1", str), 3: ("type2", str), 4: ("args", str), } else: raise Exception("unexpected table name")
[docs] def __init__(self, server_address, dbfile, RequestHandlerClass=CustomDBServerHandler, main_page="index.html", root=None, logfile=None ): """ constructor :param server_address: addess of the server :param RequestHandlerClass: it should be :class:`CustomServerHandler` :param dbfile: database filename (SQLlite format) :param main_page: main page for the service (when requested with no specific file) :param root: folder or list of folders where the server will look into for files such as the main page :githublink:`%|py|253` """ HTTPServer.__init__(self, server_address, RequestHandlerClass) self._my_database = Database(dbfile, LOG=fLOG) self._my_database_ev = Database(dbfile, LOG=fLOG) this = os.path.abspath(os.path.split(__file__)[0]) if root is None: root = [this] elif isinstance(root, str): root = [root, this] elif isinstance(root, list): root = root + [this] else: raise TypeError("unable to interpret root: " + str(root)) self._my_root = root self._my_main_page = main_page self._my_address = server_address fLOG("CustomServer.init: root=", root) fLOG("CustomServer.init: db=", dbfile) self.table_event = "cs_events" self.table_stats = "cs_stats" self.logfile = logfile if self.logfile is not None: if self.logfile == "stdout": self.flog = sys.stdout elif isinstance(self.logfile, str): self.flog = open(self.logfile, "a", encoding="utf8") else: self.flog = self.logfile else: self.flog = None self._my_database_ev.connect() if not self._my_database_ev.has_table(self.table_stats): schema = CustomDBServer.schema_table("stats") self._my_database_ev.create_table(self.table_stats, schema) self._my_database_ev.commit() self._my_database_ev.create_index( "id_post_" + self.table_stats, self.table_stats, "id_post", False) self._my_database_ev.commit() if not self._my_database_ev.has_table(self.table_event): schema = CustomDBServer.schema_table("event") self._my_database_ev.create_table(self.table_event, schema) self._my_database_ev.commit() self._my_database_ev.close()
[docs] def __enter__(self): """ What to do when creating the class. :githublink:`%|py|310` """ return self
[docs] def __exit__(self, exc_type, exc_value, traceback): # pylint: disable=W0221 """ What to do when removing the instance (close the log file). :githublink:`%|py|316` """ if self.flog is not None and self.logfile != "stdout": self.flog.close()
[docs] def process_event(self, event): """ Processes an event, it expects a format like the following: :: type1/uuid/type2/args :param event: string to log :githublink:`%|py|329` """ now = datetime.datetime.now() if self.flog is not None: self.flog.write(str(now) + " " + event) self.flog.write("\n") self.flog.flush() info = event.split("/") status = None if len(info) >= 4 and info[2] == "status": status = {"status": info[4], "id_post": int(info[3]), "dtime": now, "rate": -1, "comment": ""} if len(info) > 4: info[3:] = ["/".join(info[3:])] if len(info) < 4: raise OSError("unable to log event: " + event) values = {"type1": info[0], "uuid": info[1], "type2": info[2], "dtime": now, "args": info[3]} # to avoid database to collide iscon = self._my_database_ev.is_connected() if iscon: if self.flog is not None: self.flog.write("unable to connect the database") if status is not None: self.flog.write("unable to update status: " + str(status)) return self._my_database_ev.connect() self._my_database_ev.insert(self.table_event, values) if status is not None: self._my_database_ev.insert(self.table_stats, status) self._my_database_ev.commit() self._my_database_ev.close()
[docs] @staticmethod def run_server(server, dbfile, thread=False, port=8080, logfile=None, extra_path=None): """ start the server :param server: if None, it becomes ``CustomServer(dbfile, ('localhost', 8080), CustomServerHandler)`` :param dbfile: file to the RSS database (SQLite) :param thread: if True, the server is run in a thread and the function returns right away, otherwite, it runs the server. :param port: port to use :param logfile: file for the log or "stdout" for the standard output :param extra_path: additional path the server should look into to find a page :return: server if thread is False, the thread otherwise (the thread is started) .. warning:: If you kill the python program while the thread is still running, python interpreter might be closed completely. :githublink:`%|py|391` """ if server is None: server = CustomDBServer( ('localhost', port), dbfile, CustomDBServerHandler, logfile=logfile, root=extra_path) if thread: th = ThreadServer(server) th.start() return th else: server.serve_forever() return server