mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-04-10 12:20:02 +00:00
web: more test work, now all tests pass, POST too, only XMLRPC left to implement
This commit is contained in:
parent
f35c9c6540
commit
464f25e5f2
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user