source file: /home/buildslave/tahoe/edgy/build/src/allmydata/web/common.py
file stats: 101 lines, 98 executed: 97.0% covered
   1. 
   2. from twisted.web import http, server
   3. from zope.interface import Interface
   4. from nevow import loaders, appserver
   5. from nevow.inevow import IRequest
   6. from nevow.util import resource_filename
   7. from allmydata.interfaces import ExistingChildError, FileTooLargeError
   8. 
   9. class IClient(Interface):
  10.     pass
  11. 
  12. 
  13. def getxmlfile(name):
  14.     return loaders.xmlfile(resource_filename('allmydata.web', '%s' % name))
  15. 
  16. def boolean_of_arg(arg):
  17.     # TODO: ""
  18.     assert arg.lower() in ("true", "t", "1", "false", "f", "0", "on", "off")
  19.     return arg.lower() in ("true", "t", "1", "on")
  20. 
  21. def get_arg(req, argname, default=None, multiple=False):
  22.     """Extract an argument from either the query args (req.args) or the form
  23.     body fields (req.fields). If multiple=False, this returns a single value
  24.     (or the default, which defaults to None), and the query args take
  25.     precedence. If multiple=True, this returns a tuple of arguments (possibly
  26.     empty), starting with all those in the query args.
  27.     """
  28.     results = []
  29.     if argname in req.args:
  30.         results.extend(req.args[argname])
  31.     if req.fields and argname in req.fields:
  32.         results.append(req.fields[argname].value)
  33.     if multiple:
  34.         return tuple(results)
  35.     if results:
  36.         return results[0]
  37.     return default
  38. 
  39. def abbreviate_time(data):
  40.     # 1.23s, 790ms, 132us
  41.     if data is None:
  42.         return ""
  43.     s = float(data)
  44.     if s >= 1.0:
  45.         return "%.2fs" % s
  46.     if s >= 0.01:
  47.         return "%dms" % (1000*s)
  48.     if s >= 0.001:
  49.         return "%.1fms" % (1000*s)
  50.     return "%dus" % (1000000*s)
  51. 
  52. def abbreviate_rate(data):
  53.     # 21.8kBps, 554.4kBps 4.37MBps
  54.     if data is None:
  55.         return ""
  56.     r = float(data)
  57.     if r > 1000000:
  58.         return "%1.2fMBps" % (r/1000000)
  59.     if r > 1000:
  60.         return "%.1fkBps" % (r/1000)
  61.     return "%dBps" % r
  62. 
  63. def abbreviate_size(data):
  64.     # 21.8kB, 554.4kB 4.37MB
  65.     if data is None:
  66.         return ""
  67.     r = float(data)
  68.     if r > 1000000000:
  69.         return "%1.2fGB" % (r/1000000000)
  70.     if r > 1000000:
  71.         return "%1.2fMB" % (r/1000000)
  72.     if r > 1000:
  73.         return "%.1fkB" % (r/1000)
  74.     return "%dB" % r
  75. 
  76. def text_plain(text, ctx):
  77.     req = IRequest(ctx)
  78.     req.setHeader("content-type", "text/plain")
  79.     req.setHeader("content-length", len(text))
  80.     return text
  81. 
  82. class WebError(Exception):
  83.     def __init__(self, text, code=http.BAD_REQUEST):
  84.         self.text = text
  85.         self.code = code
  86. 
  87. # XXX: to make UnsupportedMethod return 501 NOT_IMPLEMENTED instead of 500
  88. # Internal Server Error, we either need to do that ICanHandleException trick,
  89. # or make sure that childFactory returns a WebErrorResource (and never an
  90. # actual exception). The latter is growing increasingly annoying.
  91. 
  92. def should_create_intermediate_directories(req):
  93.     t = get_arg(req, "t", "").strip()
  94.     return bool(req.method in ("PUT", "POST") and
  95.                 t not in ("delete", "rename", "rename-form", "check"))
  96. 
  97. 
  98. class MyExceptionHandler(appserver.DefaultExceptionHandler):
  99.     def simple(self, ctx, text, code=http.BAD_REQUEST):
 100.         req = IRequest(ctx)
 101.         req.setResponseCode(code)
 102.         req.setHeader("content-type", "text/plain;charset=utf-8")
 103.         if isinstance(text, unicode):
 104.             text = text.encode("utf-8")
 105.         req.write(text)
 106.         req.finishRequest(False)
 107. 
 108.     def renderHTTP_exception(self, ctx, f):
 109.         if f.check(ExistingChildError):
 110.             return self.simple(ctx,
 111.                                "There was already a child by that "
 112.                                "name, and you asked me to not "
 113.                                "replace it.",
 114.                                http.CONFLICT)
 115.         elif f.check(WebError):
 116.             return self.simple(ctx, f.value.text, f.value.code)
 117.         elif f.check(FileTooLargeError):
 118.             return self.simple(ctx, str(f.value), http.REQUEST_ENTITY_TOO_LARGE)
 119.         elif f.check(server.UnsupportedMethod):
 120.             # twisted.web.server.Request.render() has support for transforming
 121.             # this into an appropriate 501 NOT_IMPLEMENTED or 405 NOT_ALLOWED
 122.             # return code, but nevow does not.
 123.             req = IRequest(ctx)
 124.             method = req.method
 125.             return self.simple(ctx,
 126.                                "I don't know how to treat a %s request." % method,
 127.                                http.NOT_IMPLEMENTED)
 128.         super = appserver.DefaultExceptionHandler
 129.         return super.renderHTTP_exception(self, ctx, f)
 130. 
 131. class RenderMixin:
 132. 
 133.     def renderHTTP(self, ctx):
 134.         request = IRequest(ctx)
 135. 
 136.         # if we were using regular twisted.web Resources (and the regular
 137.         # twisted.web.server.Request object) then we could implement
 138.         # render_PUT and render_GET. But Nevow's request handler
 139.         # (NevowRequest.gotPageContext) goes directly to renderHTTP. Copy
 140.         # some code from the Resource.render method that Nevow bypasses, to
 141.         # do the same thing.
 142.         m = getattr(self, 'render_' + request.method, None)
 143.         if not m:
 144.             from twisted.web.server import UnsupportedMethod
 145.             raise UnsupportedMethod(getattr(self, 'allowedMethods', ()))
 146.         return m(ctx)