From 89a701349dc1fcac9bd7419e79d53cff6e32622a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20Fr=C3=A9cinaux?= Date: Wed, 16 Oct 2013 10:16:11 +0200 Subject: [PATCH] Add a toy HTTP backend. --- tftp/httpbackend.py | 100 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 tftp/httpbackend.py diff --git a/tftp/httpbackend.py b/tftp/httpbackend.py new file mode 100644 index 0000000..417abe8 --- /dev/null +++ b/tftp/httpbackend.py @@ -0,0 +1,100 @@ +from tftp.backend import IReader, IWriter, IBackend +from tftp.errors import Unsupported, FileNotFound +from tftp.util import deferred +from zope import interface +from cStringIO import StringIO +import requests + +class HTTPReader(object): + interface.implements(IReader) + + def __init__(self, url): + self.url = url + + self.r = requests.get(url) + + if self.r.status_code >= 300: + raise FileNotFound(self.url) + + self.offset = 0 + self.content_size = len(self.r.content) + self.state = 'active' + + @property + def size(self): + return self.content_size + + def read(self, size): + if self.state in ('eof', 'finished'): + return '' + + old_offset = self.offset + + self.offset = min(old_offset + size, self.content_size) + if self.offset == self.content_size: + self.state = 'eof' + + return self.r.content[old_offset:self.offset] + + def finish(self): + self.r = None + self.state = 'finished' + + +class HTTPWriter(object): + interface.implements(IWriter) + + def __init__(self, url): + self.url = url + self.data = StringIO() + self.state = 'active' + + def write(self, data): + self.data.write(data) + + def finish(self): + if self.state not in ('finished', 'cancelled'): + requests.put(self.url, data=self.data.getvalue()) + self.data.close() + self.data = None + self.state = 'finished' + + def cancel(self): + if self.state not in ('finished', 'cancelled'): + self.data.close() + self.data = None + self.state = 'cancelled' + + +class HTTPSynchronousBackend(object): + """ + Synchronous HTTP backend using the Requests library. + + The basic concept of this backend is to forward TFTP queries "as is" to an + HTTP server, as a simple way to allow dynamic files generated on the fly for + each TFTP request. + + RRQ are forwarded as GET requests. + WRQ are forwarded as PUT requests. + """ + + interface.implements(IBackend) + + def __init__(self, base_url, can_read=True, can_write=True): + self.base = base_url + if not self.base.endswith('/'): + self.base = self.base + '/' + + self.can_read, self.can_write = can_read, can_write + + @deferred + def get_reader(self, file_name): + if not self.can_read: + raise Unsupported("Reading not supported") + return HTTPReader(self.base + file_name) + + @deferred + def get_writer(self, file_name): + if not self.can_write: + raise Unsupported("Writing not supported") + return HTTPWriter(self.base + file_name)