webapi: use t=mkdir-with-children instead of a children= arg to t=mkdir .

This is safer: in the earlier API, an old webapi server would silently ignore
the initial children, and clients trying to set them would have to fetch the
newly-created directory to discover the incompatibility. In the new API,
clients using t=mkdir-with-children against an old webapi server will get a
clear error.
This commit is contained in:
Brian Warner 2009-10-25 18:13:21 -07:00
parent 1273b5c233
commit 768c76aa5f
6 changed files with 101 additions and 97 deletions

View File

@ -345,15 +345,20 @@ PUT /uri
POST /uri?t=mkdir
PUT /uri?t=mkdir
Create a new directory (either empty or with some initial children) and
return its write-cap as the HTTP response body. This does not make the newly
created directory visible from the virtual drive. The "PUT" operation is
provided for backwards compatibility: new code should use POST.
Create a new empty directory and return its write-cap as the HTTP response
body. This does not make the newly created directory visible from the
virtual drive. The "PUT" operation is provided for backwards compatibility:
new code should use POST.
Initial children are provided in the "children" field of the POST form, or
as the request body of the PUT request. This is more efficient than doing
separate mkdir and add-children operations. If this value is empty, the new
directory will be empty.
POST /uri?t=mkdir-with-children
Create a new directory, populated with a set of child nodes, and return its
write-cap as the HTTP response body. The new directory is not attached to
any other directory: the returned write-cap is the only reference to it.
Initial children are provided in the "children" field of the POST form. This
is more efficient than doing separate mkdir and add-children operations. If
this value is empty, the new directory will be empty.
If not empty, it will be interpreted as a JSON-encoded dictionary of
children with which the new directory should be populated, using the same
@ -394,8 +399,7 @@ PUT /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
intermediate directories as necessary. If the named target directory already
exists, this will make no changes to it.
If a directory is created, it will be populated with initial children via
the PUT request body or POST 'children' form field, as described above.
If the final directory is created, it will be empty.
This will return an error if a blocking file is present at any of the parent
names, preventing the server from creating the necessary parent directory.
@ -403,17 +407,27 @@ PUT /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
The write-cap of the new directory will be returned as the HTTP response
body.
POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-with-children
Like above, but if the final directory is created, it will be populated with
initial children via the POST 'children' form field, as described above in
the /uri?t=mkdir-with-children operation.
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME
Create a new empty directory and attach it to the given existing directory.
This will create additional intermediate directories as necessary. The new
directory will be populated with initial children via the PUT request body
or POST 'children' form field, as described above.
This will create additional intermediate directories as necessary.
The URL of this form points to the parent of the bottom-most new directory,
whereas the previous form has a URL that points directly to the bottom-most
new directory.
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-with-children&name=NAME
As above, but the new directory will be populated with initial children via
the POST 'children' form field, as described in /uri?t=mkdir-with-children
above.
=== Get Information About A File Or Directory (as JSON) ===
GET /uri/$FILECAP?t=json
@ -767,7 +781,7 @@ GET /uri/$DIRCAP/[SUBDIRS../]FILENAME?t=info
POST /uri?t=mkdir
This creates a new directory, but does not attach it to the virtual
This creates a new empty directory, but does not attach it to the virtual
filesystem.
If a "redirect_to_result=true" argument is provided, then the HTTP response
@ -783,13 +797,10 @@ POST /uri?t=mkdir
"false"), then the HTTP response body will simply be the write-cap of the
new directory.
It accepts the same initial-children arguments as described in earlier
t=mkdir sections, but these are unlikely to be useful from a browser form.
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=CHILDNAME
This creates a new directory as a child of the designated SUBDIR. This will
create additional intermediate directories as necessary.
This creates a new empty directory as a child of the designated SUBDIR. This
will create additional intermediate directories as necessary.
If a "when_done=URL" argument is provided, the HTTP response will cause the
web browser to redirect to the given URL. This provides a convenient way to
@ -797,9 +808,6 @@ POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=CHILDNAME
when_done= argument, the HTTP response will simply contain the write-cap of
the directory that was just created.
It accepts the same initial-children arguments as described in earlier
t=mkdir sections, but these are unlikely to be useful from a browser form.
=== Uploading a File ===

View File

@ -1122,42 +1122,10 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d.addCallback(self.failUnlessNodeKeysAre, [])
return d
def test_PUT_NEWDIRURL_initial_children(self):
(newkids, filecap1, filecap2, filecap3,
dircap) = self._create_initial_children()
d = self.PUT(self.public_url + "/foo/newdir?t=mkdir",
simplejson.dumps(newkids))
def _check(uri):
n = self.s.create_node_from_uri(uri.strip())
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-imm", filecap1))
d2.addCallback(lambda ign:
n.get_child_and_metadata_at_path(u"child-imm"))
d2.addCallback(lambda (c1, md1):
self.failUnlessEqual(md1["metakey1"], "metavalue1"))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable",
filecap2))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable-ro",
filecap3))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"dirchild", dircap))
return d2
d.addCallback(_check)
d.addCallback(lambda res:
self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
return d
def test_POST_NEWDIRURL_initial_children(self):
(newkids, filecap1, filecap2, filecap3,
dircap) = self._create_initial_children()
d = self.POST(self.public_url + "/foo/newdir?t=mkdir",
d = self.POST(self.public_url + "/foo/newdir?t=mkdir-with-children",
children=simplejson.dumps(newkids))
def _check(uri):
n = self.s.create_node_from_uri(uri.strip())
@ -1930,8 +1898,8 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
def test_POST_mkdir_initial_children(self):
newkids, filecap1, ign, ign, ign = self._create_initial_children()
d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir",
children=simplejson.dumps(newkids))
d = self.POST(self.public_url + "/foo", t="mkdir-with-children",
name="newdir", children=simplejson.dumps(newkids))
d.addCallback(lambda res:
self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
@ -1992,7 +1960,8 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
def test_POST_mkdir_no_parentdir_initial_children(self):
(newkids, filecap1, filecap2, filecap3,
dircap) = self._create_initial_children()
d = self.POST("/uri?t=mkdir", children=simplejson.dumps(newkids))
d = self.POST("/uri?t=mkdir-with-children",
children=simplejson.dumps(newkids))
def _after_mkdir(res):
self.failUnless(res.startswith("URI:DIR"), res)
n = self.s.create_node_from_uri(res)
@ -2011,6 +1980,19 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d.addCallback(_after_mkdir)
return d
def test_POST_mkdir_no_parentdir_unexpected_children(self):
# the regular /uri?t=mkdir operation is specified to ignore its body.
# Only t=mkdir-with-children pays attention to it.
(newkids, filecap1, filecap2, filecap3,
dircap) = self._create_initial_children()
d = self.shouldHTTPError("POST t=mkdir unexpected children",
400, "Bad Request",
"t=mkdir does not accept children=, "
"try t=mkdir-with-children instead",
self.POST, "/uri?t=mkdir", # without children
children=simplejson.dumps(newkids))
return d
def test_POST_noparent_bad(self):
d = self.shouldHTTPError("POST /uri?t=bogus", 400, "Bad Request",
"/uri accepts only PUT, PUT?t=mkdir, "
@ -2503,27 +2485,6 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d.addCallback(self.failUnlessIsEmptyJSON)
return d
def test_PUT_mkdir_initial_children(self):
(newkids, filecap1, filecap2, filecap3,
dircap) = self._create_initial_children()
d = self.PUT("/uri?t=mkdir", simplejson.dumps(newkids))
def _check(uri):
n = self.s.create_node_from_uri(uri.strip())
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-imm", filecap1))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable",
filecap2))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable-ro",
filecap3))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"dirchild", dircap))
return d2
d.addCallback(_check)
return d
def test_POST_check(self):
d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
def _done(res):

View File

@ -54,13 +54,13 @@ def get_arg(ctx_or_req, argname, default=None, multiple=False):
return results[0]
return default
def convert_initial_children_json(nodemaker, initial_children_json):
def convert_children_json(nodemaker, children_json):
"""I convert the JSON output of GET?t=json into the dict-of-nodes input
to both dirnode.create_subdirectory() and
client.create_directory(initial_children=)."""
initial_children = {}
if initial_children_json:
data = simplejson.loads(initial_children_json)
if children_json:
data = simplejson.loads(children_json)
for (name, (ctype, propdict)) in data.iteritems():
name = unicode(name)
writecap = propdict.get("rw_uri")

View File

@ -22,7 +22,7 @@ from allmydata.web.common import text_plain, WebError, \
IOpHandleTable, NeedOperationHandleError, \
boolean_of_arg, get_arg, get_root, parse_replace_arg, \
should_create_intermediate_directories, \
getxmlfile, RenderMixin, humanize_failure, convert_initial_children_json
getxmlfile, RenderMixin, humanize_failure, convert_children_json
from allmydata.web.filenode import ReplaceMeMixin, \
FileNodeHandler, PlaceHolderNodeHandler
from allmydata.web.check_results import CheckResults, \
@ -93,16 +93,15 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
else:
if DEBUG: print " terminal"
# terminal node
if (method,t) in [ ("POST","mkdir"), ("PUT","mkdir") ]:
if (method,t) in [ ("POST","mkdir"), ("PUT","mkdir"),
("POST", "mkdir-with-children") ]:
if DEBUG: print " making final directory"
# final directory
if method == "POST":
kids = {}
if (method,t) == ("POST", "mkdir-with-children"):
kids_json = get_arg(req, "children", "")
else:
req.content.seek(0)
kids_json = req.content.read()
kids = convert_initial_children_json(self.client.nodemaker,
kids_json)
kids = convert_children_json(self.client.nodemaker,
kids_json)
d = self.node.create_subdirectory(name, kids)
d.addCallback(make_handler_for,
self.client, self.node, name)
@ -185,6 +184,8 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
if t == "mkdir":
d = self._POST_mkdir(req)
elif t == "mkdir-with-children":
d = self._POST_mkdir_with_children(req)
elif t == "mkdir-p":
# TODO: docs, tests
d = self._POST_mkdir_p(req)
@ -221,6 +222,19 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
return d
def _POST_mkdir(self, req):
name = get_arg(req, "name", "")
if not name:
# our job is done, it was handled by the code in got_child
# which created the final directory (i.e. us)
return defer.succeed(self.node.get_uri()) # TODO: urlencode
name = name.decode("utf-8")
replace = boolean_of_arg(get_arg(req, "replace", "true"))
kids = {}
d = self.node.create_subdirectory(name, kids, overwrite=replace)
d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
return d
def _POST_mkdir_with_children(self, req):
name = get_arg(req, "name", "")
if not name:
# our job is done, it was handled by the code in got_child
@ -229,7 +243,7 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
name = name.decode("utf-8")
replace = boolean_of_arg(get_arg(req, "replace", "true"))
kids_json = get_arg(req, "children", "")
kids = convert_initial_children_json(self.client.nodemaker, kids_json)
kids = convert_children_json(self.client.nodemaker, kids_json)
d = self.node.create_subdirectory(name, kids, overwrite=replace)
d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
return d

View File

@ -70,6 +70,9 @@ class URIHandler(RenderMixin, rend.Page):
return unlinked.POSTUnlinkedCHK(req, self.client)
if t == "mkdir":
return unlinked.POSTUnlinkedCreateDirectory(req, self.client)
elif t == "mkdir-with-children":
return unlinked.POSTUnlinkedCreateDirectoryWithChildren(req,
self.client)
errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
"and POST?t=mkdir")
raise WebError(errmsg, http.BAD_REQUEST)

View File

@ -5,7 +5,7 @@ from twisted.internet import defer
from nevow import rend, url, tags as T
from allmydata.immutable.upload import FileHandle
from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
convert_initial_children_json
convert_children_json, WebError
from allmydata.web import status
def PUTUnlinkedCHK(req, client):
@ -26,10 +26,7 @@ def PUTUnlinkedSSK(req, client):
def PUTUnlinkedCreateDirectory(req, client):
# "PUT /uri?t=mkdir", to create an unlinked directory.
req.content.seek(0)
kids_json = req.content.read()
kids = convert_initial_children_json(client.nodemaker, kids_json)
d = client.create_dirnode(initial_children=kids)
d = client.create_dirnode()
d.addCallback(lambda dirnode: dirnode.get_uri())
# XXX add redirect_to_result
return d
@ -93,9 +90,30 @@ def POSTUnlinkedSSK(req, client):
return d
def POSTUnlinkedCreateDirectory(req, client):
# "POST /uri?t=mkdir", to create an unlinked directory.
kids_json = get_arg(req, "children", None)
if kids_json is not None:
raise WebError("t=mkdir does not accept children=, "
"try t=mkdir-with-children instead",
http.BAD_REQUEST)
d = client.create_dirnode()
redirect = get_arg(req, "redirect_to_result", "false")
if boolean_of_arg(redirect):
def _then_redir(res):
new_url = "uri/" + urllib.quote(res.get_uri())
req.setResponseCode(http.SEE_OTHER) # 303
req.setHeader('location', new_url)
req.finish()
return ''
d.addCallback(_then_redir)
else:
d.addCallback(lambda dirnode: dirnode.get_uri())
return d
def POSTUnlinkedCreateDirectoryWithChildren(req, client):
# "POST /uri?t=mkdir", to create an unlinked directory.
kids_json = get_arg(req, "children", "")
kids = convert_initial_children_json(client.nodemaker, kids_json)
kids = convert_children_json(client.nodemaker, kids_json)
d = client.create_dirnode(initial_children=kids)
redirect = get_arg(req, "redirect_to_result", "false")
if boolean_of_arg(redirect):