Source code for pyrsslocal.rss.rss_simple_server
"""
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 .rss_database import DatabaseRSS
from ..simple_server.simple_server_custom import SimpleHandler, ThreadServer
[docs]class RSSSimpleHandler(SimpleHandler):
"""
You can read the blog post `RSS Reader
<http://www.xavierdupre.fr/blog/2013-07-28_nojs.html>`_.
Defines a simple handler used by HTTPServer.
Firefox works better for local files.
This server serves RSS content.
For every section containing a python script, the class will add a local
variable ``dbrss`` which gives access to the blogs database.
That's why the following section works:
::
<script type="text/python">
for blogs in dbrss.enumerate_blogs() :
print (blogs.html())
</script>
Whenever a url is preceded by ``/logs/click/```,
the class will log an event in the logs.
The final page will look like this:
.. image:: ../../images/page1.png
:align: center
.. faqref:
:title: The server is slow or does not respond to a click)
For some reason, it usually happens on Firefox.
Chrome works better. The script python embedded in the HTML page
and interpreted by the server make it slow to run.
It should be replaced by javascript. Maybe Firefox detects the answer is not
fast enough and it requires one or two more clicks to get the server to respond.
:githublink:`%|py|50`
"""
[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|56`
"""
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|66`
"""
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|73`
"""
return [self.server._my_root, SimpleHandler.javascript_path]
[docs] def interpret_parameter_as_list_int(self, ps):
"""
Interprets 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|83`
"""
res = []
for ins in ps:
spl = ins.split(",")
ii = [int(_) for _ in spl]
res.extend(ii)
return res
[docs] def process_event(self, st):
"""
Processes an event, and log it.
:param st: string to process
:githublink:`%|py|96`
"""
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|106`
"""
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:
if path.path.startswith("/rssfetchlocalexe/"):
url = path.path.replace("/rssfetchlocalexe/", "")
else:
url = path.path
htype, ftype = self.get_ftype(url)
local = os.path.join(self.server._my_root, url.lstrip("/"))
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["dbrss"] = self.server._my_database
params["main_page"] = url
params["blog_selected"] = self.interpret_parameter_as_list_int(
params.get(
"blog_selected",
[]))
params["post_selected"] = self.interpret_parameter_as_list_int(
params.get(
"post_selected",
[]))
params["search"] = params.get("search", [None])[0]
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 (RSSSimpleHandler): " +
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 (RSSSimpleHandler): " +
path.geturl() +
"\nlocal file:" +
local +
"\n")
self.send_error(404)
[docs]class RSSServer(ThreadingMixIn, HTTPServer):
"""
Defines a :epkg:`RSS` server dedicated to one specific database.
You can read the blog post `RSS Reader
<http://www.xavierdupre.fr/blog/2013-07-28_nojs.html>`_.
:githublink:`%|py|176`
"""
[docs] def __init__(self, server_address, dbfile,
RequestHandlerClass=RSSSimpleHandler,
main_page="rss_reader.html",
root=os.path.abspath(os.path.split(__file__)[0]),
logfile=None, fLOG=None):
"""
:param server_address: address of the server
:param RequestHandlerClass: it should be :class:`RSSSimpleHandler <pyrsslocal.rss.rss_simple_server.RSSSimpleHandler>`
:param dbfile: database filename (SQLlite format)
:param main_page: main page for the service (when requested with no specific file)
:param root: folder when the server will look into for files such as the main page
:param fLOG: logging function
:githublink:`%|py|190`
"""
if fLOG:
fLOG("RSSServer.init: begin server")
HTTPServer.__init__(self, server_address, RequestHandlerClass)
if fLOG:
fLOG("RSSServer.init: begin db rss")
self._my_database = DatabaseRSS(dbfile, LOG=fLOG)
if fLOG:
fLOG("RSSServer.init: begin db event")
self._my_database_ev = DatabaseRSS(dbfile, LOG=fLOG)
if fLOG:
fLOG("RSSServer.init: end db")
self._my_root = root
self._my_main_page = main_page
self._my_address = server_address
if SimpleHandler == RequestHandlerClass:
raise TypeError("this request handler should not be SimpleHandler")
if fLOG:
fLOG("RSSServer.init: root=", root)
fLOG("RSSServer.init: db=", dbfile)
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
[docs] def __enter__(self):
"""
What to do when creating the class.
:githublink:`%|py|225`
"""
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|231`
"""
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|244`
"""
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._my_database.table_event, values)
if status is not None:
self._my_database_ev.insert(self._my_database.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, fLOG=None):
"""
Starts the server.
:param server: None or string, see below
: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 fLOG: logging function
:return: server if thread is False, the thread otherwise (the thread is started)
About the parameter ``server``:
* ``None``, it becomes ``RSSServer(('localhost', 8080), dbfile, RSSSimpleHandler)``
* ``<server>``, it becomes ``RSSServer((server, 8080), dbfile, RSSSimpleHandler)``
.. warning:: If you kill the python program while the thread is still running,
python interpreter might be closed completely.
:githublink:`%|py|311`
"""
if server is None:
server = RSSServer(
('localhost',
port),
dbfile,
RSSSimpleHandler,
logfile=logfile,
fLOG=fLOG)
elif isinstance(server, str):
server = RSSServer(
(server,
port),
dbfile,
RSSSimpleHandler,
logfile=logfile,
fLOG=fLOG)
if thread:
th = ThreadServer(server)
th.start()
return th
else:
server.serve_forever()
return server