Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions tftp/httpbackend.py
Original file line number Diff line number Diff line change
@@ -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)