webapi: use t=mkdir-with-children instead of a children= arg to t=mkdir .
authorBrian Warner <warner@lothar.com>
Mon, 26 Oct 2009 01:13:21 +0000 (18:13 -0700)
committerBrian Warner <warner@lothar.com>
Mon, 26 Oct 2009 01:13:21 +0000 (18:13 -0700)
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.

docs/frontends/webapi.txt
src/allmydata/test/test_web.py
src/allmydata/web/common.py
src/allmydata/web/directory.py
src/allmydata/web/root.py
src/allmydata/web/unlinked.py

index 28b4fd441d77458efb6212a11b26b405362e8aac..7a6abbb959d8ea8c13360248dbe72b630acd04df 100644 (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 ===
 
index 967de384cc9a19bfa0771e42736ebafcd22256e4..c25aecd04cd874148eb5c694fcabf9b5dc4db2fe 100644 (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):
index 60a7e3778035b47b9341127c8bc9cb1eda9caadd..5089367f246d20618d7dcfe2cb54bc05dcde7e8d 100644 (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")
index 81f26e597706f1c9828b19539d5544e6de27e56b..8f81085d0fe161728a0dc971af93328f56985b6f 100644 (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
index 3598e813f684cf93758c5c0c36f81fc47b59c7a5..97773bd6b2c009e06e56004f94bc88406198d593 100644 (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)
index bfad1185c7d498f84bb43edc79e31712a7cc18e1..71472da3ca0c7459110e919a623b6dcb8fe7496d 100644 (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):