web: more test work, now all tests pass, POST too, only XMLRPC left to implement

This commit is contained in:
Brian Warner 2007-07-07 20:06:58 -07:00
parent f35c9c6540
commit 464f25e5f2
3 changed files with 263 additions and 42 deletions

View File

@ -194,7 +194,10 @@ for files and directories which do not yet exist.
== POST Forms ==
POST DIRURL?t=upload-form
POST DIRURL
t=upload
name=childname (optional)
file=newfile
This instructs the client to upload a file into the given dirnode. We need
this because forms are the only way for a web browser to upload a file
@ -202,18 +205,25 @@ for files and directories which do not yet exist.
new child name will be included in the form's arguments. This can only be
used to upload a single file at a time.
POST DIRURL?t=mkdir-form
POST DIRURL
t=mkdir
name=childname
This instructs the client to create a new empty directory. The name of the
new child directory will be included in the form's arguments.
POST DIRURL?t=put-uri-form
POST DIRURL
t=uri
name=childname
uri=newuri
This instructs the client to attach a child that is referenced by URI (just
like the PUT NEWFILEURL?t=uri method). The name and URI of the new child
will be included in the form's arguments.
POST DIRURL?t=delete-form
POST DIRURL
t=delete
name=childname
This instructs the client to delete a file from the given dirnode. The name
of the child to be deleted will be included in the form's arguments.
@ -232,8 +242,17 @@ for files and directories which do not yet exist.
GET http://localhost:8011/uri/$URI
would retrieve the contents of the file. If the URI corresponds to a
directory, then:
would retrieve the contents of the file. Since files accessed this way do
not have a naturally-occurring filename (from which a MIME-type can be
derived), one can be specified using a 'filename=' query argument. This
filename is also the one used if the 'save=true' argument is set, which
adds a 'Content-Disposition: attachment' header to prompt most web browsers
to save the file to disk rather than attempting to display it:
GET http://localhost:8011/uri/$URI?filename=foo.jpg
GET http://localhost:8011/uri/$URI?filename=foo.jpg&save=true
If the URI corresponds to a directory, then:
PUT http://localhost:8011/uri/$URI/subdir/newfile?localfile=$FILENAME
@ -247,6 +266,17 @@ for files and directories which do not yet exist.
can be used to attach a shared directory to the vdrive. Intermediate
directories are created on-demand just like with the regular PUT command.
GET http://localhost:8011/uri?uri=$URI
This causes a redirect to /uri/$URI, and retains any additional query
arguments (like filename= or save=). This is for the convenience of web
forms which allow the user to paste in a URI (obtained through some
out-of-band channel, like IM or email).
Note that this form only redirects to the specific node indicated by the
URI: unlike the GET /uri/$URI form, you cannot traverse to child nodes by
appending additional path segments to the URL.
== XMLRPC ==
http://localhost:8011/xmlrpc

View File

@ -1,11 +1,11 @@
import re, os.path
import re, os.path, urllib
from zope.interface import implements
from twisted.application import service
from twisted.trial import unittest
from twisted.internet import defer
from twisted.web import client, error
from twisted.python import failure
from twisted.python import failure, log
from allmydata import webish, interfaces, dirnode, uri
from allmydata.encode import NotEnoughPeersError
from allmydata.util import fileutil
@ -125,6 +125,8 @@ class MyVirtualDrive(service.Service):
name = "vdrive"
public_root = None
private_root = None
def __init__(self, nodes):
self._my_nodes = nodes
def have_public_root(self):
return bool(self.public_root)
def have_private_root(self):
@ -134,6 +136,11 @@ class MyVirtualDrive(service.Service):
def get_private_root(self):
return defer.succeed(self.private_root)
def get_node(self, uri):
def _try():
return self._my_nodes[uri]
return defer.maybeDeferred(_try)
class Web(unittest.TestCase):
def setUp(self):
self.s = MyClient()
@ -143,11 +150,12 @@ class Web(unittest.TestCase):
port = s.listener._port.getHost().port
self.webish_url = "http://localhost:%d" % port
v = MyVirtualDrive()
v.setServiceParent(self.s)
self.nodes = {} # maps URI to node
self.files = {} # maps file URI to contents
v = MyVirtualDrive(self.nodes)
v.setServiceParent(self.s)
dl = MyDownloader(self.files)
dl.setServiceParent(self.s)
ul = MyUploader(self.files)
@ -210,9 +218,15 @@ class Web(unittest.TestCase):
def failUnlessIsBarDotTxt(self, res):
self.failUnlessEqual(res, self.BAR_CONTENTS)
def GET(self, urlpath):
def failUnlessIsFooJSON(self, res):
self.failUnless("JSONny stuff here" in res)
self.failUnless("name=bar.txt, child_uri=%s" % self._bar_txt_uri
in res)
self.failUnless("name=blockingfile" in res)
def GET(self, urlpath, followRedirect=False):
url = self.webish_url + urlpath
return client.getPage(url, method="GET")
return client.getPage(url, method="GET", followRedirect=followRedirect)
def PUT(self, urlpath, data):
url = self.webish_url + urlpath
@ -222,10 +236,33 @@ class Web(unittest.TestCase):
url = self.webish_url + urlpath
return client.getPage(url, method="DELETE")
def POST(self, urlpath, data):
raise unittest.SkipTest("not yet")
def POST(self, urlpath, **fields):
url = self.webish_url + urlpath
return client.getPage(url, method="POST", postdata=data)
sepbase = "boogabooga"
sep = "--" + sepbase
form = []
form.append(sep)
form.append('Content-Disposition: form-data; name="_charset"')
form.append('')
form.append('UTF-8')
form.append(sep)
for name, value in fields.iteritems():
if isinstance(value, tuple):
filename, value = value
form.append('Content-Disposition: form-data; name="%s"; '
'filename="%s"' % (name, filename))
else:
form.append('Content-Disposition: form-data; name="%s"' % name)
form.append('')
form.append(value)
form.append(sep)
form[-1] += "--"
body = "\r\n".join(form) + "\r\n"
headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
}
print "BODY", body
return client.getPage(url, method="POST", postdata=body,
headers=headers, followRedirect=False)
def shouldFail(self, res, expected_failure, which, substring=None):
print "SHOULDFAIL", res
@ -434,7 +471,8 @@ class Web(unittest.TestCase):
return d
def test_GET_DIRURL(self): # YES
d = self.GET("/vdrive/global/foo")
# the addSlash means we get a redirect here
d = self.GET("/vdrive/global/foo", followRedirect=True)
def _check(res):
self.failUnless(re.search(r'<td><a href="bar.txt">bar.txt</a></td>'
'\s+<td>FILE</td>'
@ -635,34 +673,123 @@ class Web(unittest.TestCase):
d.addCallback(_check)
return d
def test_POST_upload(self):
form = "TODO"
d = self.POST("/vdrive/global/foo", form)
def test_POST_upload(self): # YES
d = self.POST("/vdrive/global/foo", t="upload",
file=("new.txt", self.NEWFILE_CONTENTS))
def _check(res):
self.failUnless("new.txt" in self._foo_node.children)
new_uri = self._foo_node.children["new.txt"]
new_contents = self.files[new_uri]
self.failUnlessEqual(new_contents, self.NEWFILE_CONTENTS)
self.failUnlessEqual(res.strip(), new_uri)
d.addCallback(_check)
return d
def test_POST_mkdir(self):
form = "TODO"
d = self.POST("/vdrive/global/foo", form)
def test_POST_upload_named(self): # YES
d = self.POST("/vdrive/global/foo", t="upload",
name="new.txt", file=self.NEWFILE_CONTENTS)
def _check(res):
self.failUnless("new.txt" in self._foo_node.children)
new_uri = self._foo_node.children["new.txt"]
new_contents = self.files[new_uri]
self.failUnlessEqual(new_contents, self.NEWFILE_CONTENTS)
self.failUnlessEqual(res.strip(), new_uri)
d.addCallback(_check)
return d
def test_POST_put_uri(self):
form = "TODO"
d = self.POST("/vdrive/global/foo", form)
def test_POST_mkdir(self): # YES, return value?
d = self.POST("/vdrive/global/foo", t="mkdir", name="newdir")
def _check(res):
self.failUnless("newdir" in self._foo_node.children)
newdir_uri = self._foo_node.children["newdir"]
newdir_node = self.nodes[newdir_uri]
self.failIf(newdir_node.children)
d.addCallback(_check)
return d
def test_POST_delete(self):
form = "TODO, bar.txt"
d = self.POST("/vdrive/global/foo", form)
def test_POST_put_uri(self): # YES
newuri = self.makefile(8)
contents = self.files[newuri]
d = self.POST("/vdrive/global/foo", t="uri", name="new.txt", uri=newuri)
def _check(res):
self.failUnless("new.txt" in self._foo_node.children)
new_uri = self._foo_node.children["new.txt"]
new_contents = self.files[new_uri]
self.failUnlessEqual(new_contents, contents)
self.failUnlessEqual(res.strip(), new_uri)
d.addCallback(_check)
return d
def test_URI_GET(self):
raise unittest.SkipTest("not yet")
d = self.GET("/uri/%s/bar.txt" % foo_uri)
def test_POST_delete(self): # yes
d = self.POST("/vdrive/global/foo", t="delete", name="bar.txt")
def _check(res):
self.failIf("bar.txt" in self._foo_node.children)
d.addCallback(_check)
return d
def test_PUT_NEWFILEURL_uri(self):
raise unittest.SkipTest("not yet")
d = self.PUT("/vdrive/global/foo/new.txt?uri", new_uri)
def shouldRedirect(self, res, target):
if not isinstance(res, failure.Failure):
self.fail("we were expecting to get redirected to %s, not get an"
" actual page: %s" % (target, res))
res.trap(error.PageRedirect)
# the PageRedirect does not seem to capture the uri= query arg
# properly, so we can't check for it.
print "location:", res.value.location
realtarget = self.webish_url + target
self.failUnlessEqual(res.value.location, realtarget)
def test_GET_URI_form(self): # YES
base = "/uri?uri=%s" % self._bar_txt_uri
# this is supposed to give us a redirect to /uri/$URI, plus arguments
targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
d = self.GET(base)
d.addBoth(self.shouldRedirect, targetbase)
d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
d.addCallback(lambda res: self.GET(base+"&t=json"))
d.addBoth(self.shouldRedirect, targetbase+"?t=json")
d.addCallback(self.log, "about to get file by uri")
d.addCallback(lambda res: self.GET(base, followRedirect=True))
d.addCallback(self.failUnlessIsBarDotTxt)
d.addCallback(self.log, "got file by uri, about to get dir by uri")
d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
followRedirect=True))
d.addCallback(self.failUnlessIsFooJSON)
d.addCallback(self.log, "got dir by uri")
return d
def log(self, res, msg):
print "MSG: %s RES: %s" % (msg, res)
log.msg(msg)
return res
def test_GET_URI_URL(self): # YES
base = "/uri/%s" % self._bar_txt_uri
d = self.GET(base)
d.addCallback(self.failUnlessIsBarDotTxt)
d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
d.addCallback(self.failUnlessIsBarDotTxt)
d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
d.addCallback(self.failUnlessIsBarDotTxt)
return d
def test_GET_URI_URL_dir(self): # YES
base = "/uri/%s?t=json" % self._foo_uri
d = self.GET(base)
d.addCallback(self.failUnlessIsFooJSON)
return d
def test_PUT_NEWFILEURL_uri(self): # YES
new_uri = self.makefile(8)
d = self.PUT("/vdrive/global/foo/new.txt?t=uri", new_uri)
def _check(res):
self.failUnless("new.txt" in self._foo_node.children)
new_uri = self._foo_node.children["new.txt"]
new_contents = self.files[new_uri]
self.failUnlessEqual(new_contents, self.files[new_uri])
self.failUnlessEqual(res.strip(), new_uri)
d.addCallback(_check)
return d
def test_XMLRPC(self):

View File

@ -303,10 +303,10 @@ class TypedFile(static.File):
self.defaultType)
class FileDownloader(resource.Resource):
def __init__(self, name, filenode):
self._name = name
def __init__(self, filenode, name):
IFileNode(filenode)
self._filenode = filenode
self._name = name
def render(self, req):
gte = static.getTypeAndEncoding
@ -515,8 +515,57 @@ class DirectoryReadonlyURI(DirectoryJSONMetadata):
class POSTHandler(rend.Page):
def __init__(self, node):
self._node = node
# TODO: handler methods
def renderHTTP(self, ctx):
req = inevow.IRequest(ctx)
print "POST", req, req.args#, req.content.read()
#print " ", req.requestHeaders
#print req.__class__
#print req.fields
#print dir(req.fields)
print req.fields.keys()
t = req.fields["t"].value
if t == "mkdir":
name = req.fields["name"].value
print "CREATING DIR", name
d = self._node.create_empty_directory(name)
def _done(res):
return "directory created"
d.addCallback(_done)
return d
elif t == "uri":
name = req.fields["name"].value
uri = req.fields["uri"].value
d = self._node.set_uri(name, uri)
def _done(res):
return uri
d.addCallback(_done)
return d
elif t == "delete":
name = req.fields["name"].value
d = self._node.delete(name)
def _done(res):
return "thing deleted"
d.addCallback(_done)
return d
elif t == "upload":
contents = req.fields["file"]
name = contents.filename
print "filename", name
if "name" in req.fields:
name = req.fields["name"].value
print "NAME WAS", name
uploadable = upload.FileHandle(contents.file)
d = self._node.add_file(name, uploadable)
def _done(newnode):
print "UPLOAD DONW", name
return newnode.get_uri()
d.addCallback(_done)
return d
else:
print "BAD t=%s" % t
return "BAD t=%s" % t
return "nope"
class DELETEHandler(rend.Page):
def __init__(self, node, name):
@ -742,13 +791,18 @@ class VDrive(rend.Page):
d = self.get_child_at_path(path)
def file_or_dir(node):
if IFileNode.providedBy(node):
filename = "unknown"
if path:
filename = path[-1]
if "filename" in req.args:
filename = req.args["filename"][0]
if localfile:
# write contents to a local file
return LocalFileDownloader(node, localfile), ()
elif t == "":
# send contents as the result
print "FileDownloader"
return FileDownloader(path[-1], node), ()
return FileDownloader(node, filename), ()
elif t == "json":
print "Localfilejsonmetadata"
return FileJSONMetadata(node), ()
@ -820,6 +874,7 @@ class Root(rend.Page):
def locateChild(self, ctx, segments):
client = IClient(ctx)
req = inevow.IRequest(ctx)
vdrive = client.getServiceNamed("vdrive")
print "Root.locateChild", segments
@ -838,17 +893,26 @@ class Root(rend.Page):
d.addCallback(lambda vd: vd.locateChild(ctx, segments[2:]))
return d
elif segments[0] == "uri":
print "looking at /uri", segments, req.args
if len(segments) == 1:
if "uri" in req.args:
uri = req.args["uri"][0]
print "REDIRECTING"
there = url.URL.fromContext(ctx)
there = there.clear("uri")
there = there.child("uri").child(uri)
print " TO", there
return there, ()
if len(segments) < 2:
return rend.NotFound
uri = segments[1]
d = vdrive.get_node(uri)
d.addCallback(lambda node: VDrive(node), uri)
d.addCallback(lambda node: VDrive(node, "<from-uri>"))
d.addCallback(lambda vd: vd.locateChild(ctx, segments[2:]))
return d
elif segments[0] == "xmlrpc":
pass # TODO
elif segments[0] == "download_uri":
req = inevow.IRequest(ctx)
dl = get_downloader_service(ctx)
filename = "unknown_filename"
if "filename" in req.args:
@ -861,7 +925,7 @@ class Root(rend.Page):
uri = req.args["uri"][0]
else:
return rend.NotFound
child = FileDownloader(filename, FileNode(uri, IClient(ctx)))
child = FileDownloader(FileNode(uri, IClient(ctx)), filename)
return child, ()
return rend.Page.locateChild(self, ctx, segments)