========================= ================================
Revision Description
========================= ================================
-0.5 (July 21, 2011) Object update from another object's data.
+0.5 (July 22, 2011) Object update from another object's data.
\ Support object truncate.
\ Create object using a standard HTML form.
\ Purge container/object history.
\ List other accounts that share objects with a user.
\ List shared containers/objects.
\ Update implementation guidelines.
+\ Check preconditions when creating/updating objects.
0.4 (July 01, 2011) Object permissions and account groups.
\ Control versioning behavior and container quotas with container policy directives.
\ Support updating/deleting individual metadata with ``POST``.
HEAD
""""
+==================== ===========================
+Request Header Name Value
+==================== ===========================
+If-Modified-Since Retrieve if account has changed since provided timestamp
+If-Unmodified-Since Retrieve if account has not changed since provided timestamp
+==================== ===========================
+
+|
+
====================== ===================================
Request Parameter Name Value
====================== ===================================
POST
""""
-====================== ============================================
-Request Parameter Name Value
-====================== ============================================
-update Do not replace metadata/groups (no value parameter)
-====================== ============================================
-
-|
-
==================== ===========================
Request Header Name Value
==================== ===========================
X-Account-Meta-* Optional user defined metadata
==================== ===========================
+|
+
+====================== ============================================
+Request Parameter Name Value
+====================== ============================================
+update Do not replace metadata/groups (no value parameter)
+====================== ============================================
+
No reply content/headers.
The operation will overwrite all user defined metadata, except if ``update`` is defined.
HEAD
""""
+==================== ===========================
+Request Header Name Value
+==================== ===========================
+If-Modified-Since Retrieve if container has changed since provided timestamp
+If-Unmodified-Since Retrieve if container has not changed since provided timestamp
+==================== ===========================
+
+|
+
====================== ===================================
Request Parameter Name Value
====================== ===================================
POST
""""
-====================== ============================================
-Request Parameter Name Value
-====================== ============================================
-update Do not replace metadata/policy (no value parameter)
-====================== ============================================
-
-|
-
==================== ================================
Request Header Name Value
==================== ================================
X-Container-Meta-* Optional user defined metadata
==================== ================================
+|
+
+====================== ============================================
+Request Parameter Name Value
+====================== ============================================
+update Do not replace metadata/policy (no value parameter)
+====================== ============================================
+
No reply content/headers.
The operation will overwrite all user defined metadata, except if ``update`` is defined.
HEAD
""""
+==================== ================================
+Request Header Name Value
+==================== ================================
+If-Match Retrieve if ETags match
+If-None-Match Retrieve if ETags don't match
+If-Modified-Since Retrieve if object has changed since provided timestamp
+If-Unmodified-Since Retrieve if object has not changed since provided timestamp
+==================== ================================
+
+|
+
====================== ===================================
Request Parameter Name Value
====================== ===================================
==================== ================================
Request Header Name Value
==================== ================================
+If-Match Put if ETags match with current object
+If-None-Match Put if ETags don't match with current object
ETag The MD5 hash of the object (optional to check written data)
Content-Length The size of the data written
Content-Type The MIME content type of the object
==================== ================================
Request Header Name Value
==================== ================================
+If-Match Proceed if ETags match with object
+If-None-Match Proceed if ETags don't match with object
Destination The destination path in the form ``/<container>/<object>``
Content-Type The MIME content type of the object (optional)
Content-Encoding The encoding of the object (optional)
POST
""""
-====================== ============================================
-Request Parameter Name Value
-====================== ============================================
-update Do not replace metadata (no value parameter)
-====================== ============================================
-
-|
-
==================== ================================
Request Header Name Value
==================== ================================
+If-Match Proceed if ETags match with object
+If-None-Match Proceed if ETags don't match with object
Content-Length The size of the data written (optional, to update)
Content-Type The MIME content type of the object (optional, to update)
Content-Range The range of data supplied (optional, to update)
X-Object-Meta-* Optional user defined metadata
==================== ================================
+|
+
+====================== ============================================
+Request Parameter Name Value
+====================== ============================================
+update Do not replace metadata (no value parameter)
+====================== ============================================
+
The ``Content-Encoding``, ``Content-Disposition``, ``X-Object-Manifest`` and ``X-Object-Meta-*`` headers are considered to be user defined metadata. An operation without the ``update`` parameter will overwrite all previous values and remove any keys not supplied. When using ``update`` any metadata with an empty value will be deleted.
To change permissions, include an ``X-Object-Sharing`` header (as defined in ``PUT``). To publish, include an ``X-Object-Public`` header, with a value of ``true``. If no such headers are defined, no changes will be applied to sharing/public. Use empty values to remove permissions/unpublish (unpublishing also works with ``false`` as a header value). Sharing options are applied to the object - not its versions.
* Container policies to manage behavior and limits.
* Headers ``X-Container-Block-*`` at the container level, exposing the underlying storage characteristics.
* All metadata replies, at all levels, include latest modification information.
-* At all levels, a ``GET`` request may use ``If-Modified-Since`` and ``If-Unmodified-Since`` headers.
+* At all levels, a ``HEAD`` or ``GET`` request may use ``If-Modified-Since`` and ``If-Unmodified-Since`` headers.
* Container/object lists include all associated metadata if the reply is of type json/xml. Some names are kept to their OOS API equivalents for compatibility.
* Option to include only shared containers/objects in listings.
* Object metadata allowed, in addition to ``X-Object-Meta-*``: ``Content-Encoding``, ``Content-Disposition``, ``X-Object-Manifest``. These are all replaced with every update operation, except if using the ``update`` parameter (in which case individual keys can also be deleted). Deleting meta by providing empty values also works when copying/moving an object.
* Object create using ``POST`` to support standard HTML forms.
* Partial object updates through ``POST``, using the ``Content-Length``, ``Content-Type``, ``Content-Range`` and ``Transfer-Encoding`` headers. Use another object's data to update with ``X-Source-Object`` and ``X-Source-Version``. Truncate with ``X-Object-Bytes``.
* Object ``MOVE`` support.
+* Conditional object create/update operations, using ``If-Match`` and ``If-None-Match`` headers.
* Time-variant account/container listings via the ``until`` parameter.
* Object versions - parameter ``version`` in ``HEAD``/``GET`` (list versions with ``GET``), ``X-Object-Version-*`` meta in replies, ``X-Source-Version`` in ``PUT``/``COPY``.
* Sharing/publishing with ``X-Object-Sharing``, ``X-Object-Public`` at the object level. Cross-user operations are allowed - controlled by sharing directives. Permissions may include groups defined with ``X-Account-Group-*`` at the account level. These apply to the object - not its versions.
Implementation Guidelines
^^^^^^^^^^^^^^^^^^^^^^^^^
-Pithos clients should use the ``pithos`` and ``trash`` containers for active and inactive objects respectively. If any of these containers is not found, the client software should create it, without interrupting the user's workflow. The ``home`` element corresponds to ``pithos`` and the ``trash`` element to ``trash``. Use ``PUT`` with the ``X-Move-From`` header, or ``MOVE`` to transfer objects from one container to the other. Use ``DELETE`` to remove from ``pithos`` without trashing, or to remove from ``trash``. When moving objects, detect naming conflicts with the ``If-Match`` or ``If-None-Match`` headers (**TBD**). Such conflicts should be resolved by the user.
+Pithos clients should use the ``pithos`` and ``trash`` containers for active and inactive objects respectively. If any of these containers is not found, the client software should create it, without interrupting the user's workflow. The ``home`` element corresponds to ``pithos`` and the ``trash`` element to ``trash``. Use ``PUT`` with the ``X-Move-From`` header, or ``MOVE`` to transfer objects from one container to the other. Use ``DELETE`` to remove from ``pithos`` without trashing, or to remove from ``trash``. When moving objects, detect naming conflicts with the ``If-Match`` or ``If-None-Match`` headers. Such conflicts should be resolved by the user.
Object names should use the ``/`` delimiter to impose a hierarchy of folders and files.
except NotAllowedError:
raise Unauthorized('Access denied')
+ validate_modification_preconditions(request, meta)
+
response = HttpResponse(status=204)
put_account_headers(response, meta, groups)
return response
except NameError:
raise ItemNotFound('Container does not exist')
+ validate_modification_preconditions(request, meta)
+
response = HttpResponse(status=204)
put_container_headers(response, meta, policy)
return response
ret = 201
except NotAllowedError:
raise Unauthorized('Access denied')
+ except ValueError:
+ raise BadRequest('Invalid policy header')
except NameError:
ret = 202
update_sharing_meta(permissions, v_account, v_container, v_object, meta)
update_public_meta(public, meta)
+ # Evaluate conditions.
+ validate_modification_preconditions(request, meta)
+ try:
+ validate_matching_preconditions(request, meta)
+ except NotModified:
+ response = HttpResponse(status=304)
+ response['ETag'] = meta['hash']
+ return response
+
response = HttpResponse(status=200)
put_object_headers(response, meta)
return response
if not request.GET.get('format'):
request.serialization = 'text'
+ # Evaluate conditions.
+ if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
+ try:
+ meta = backend.get_object_meta(request.user, v_account, v_container, v_object)
+ except NotAllowedError:
+ raise Unauthorized('Access denied')
+ except NameError:
+ meta = {}
+ validate_matching_preconditions(request, meta)
+
copy_from = request.META.get('HTTP_X_COPY_FROM')
move_from = request.META.get('HTTP_X_MOVE_FROM')
if copy_from or move_from:
dest_container, dest_name = split_container_object_string(dest_path)
except ValueError:
raise BadRequest('Invalid Destination header')
+
+ # Evaluate conditions.
+ if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
+ src_version = request.META.get('HTTP_X_SOURCE_VERSION')
+ try:
+ meta = backend.get_object_meta(request.user, v_account, v_container, v_object, src_version)
+ except NotAllowedError:
+ raise Unauthorized('Access denied')
+ except (NameError, IndexError):
+ raise ItemNotFound('Container or object does not exist')
+ validate_matching_preconditions(request, meta)
+
copy_or_move_object(request, v_account, v_container, v_object, dest_container, dest_name, move=False)
return HttpResponse(status=201)
dest_container, dest_name = split_container_object_string(dest_path)
except ValueError:
raise BadRequest('Invalid Destination header')
+
+ # Evaluate conditions.
+ if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
+ try:
+ meta = backend.get_object_meta(request.user, v_account, v_container, v_object)
+ except NotAllowedError:
+ raise Unauthorized('Access denied')
+ except NameError:
+ raise ItemNotFound('Container or object does not exist')
+ validate_matching_preconditions(request, meta)
+
copy_or_move_object(request, v_account, v_container, v_object, dest_container, dest_name, move=True)
return HttpResponse(status=201)
raise Unauthorized('Access denied')
except NameError:
raise ItemNotFound('Object does not exist')
+
+ # Evaluate conditions.
+ if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
+ validate_matching_preconditions(request, prev_meta)
+
# If replacing, keep previous values of 'Content-Type' and 'hash'.
replace = True
if 'update' in request.GET:
def validate_matching_preconditions(request, meta):
"""Check that the ETag conforms with the preconditions set."""
- if 'hash' not in meta:
- return # TODO: Always return?
+ hash = meta.get('hash', None)
if_match = request.META.get('HTTP_IF_MATCH')
- if if_match is not None and if_match != '*':
- if meta['hash'] not in [x.lower() for x in parse_etags(if_match)]:
- raise PreconditionFailed('Resource Etag does not match')
+ if if_match is not None:
+ if hash is None:
+ raise PreconditionFailed('Resource does not exist')
+ if if_match != '*' and hash not in [x.lower() for x in parse_etags(if_match)]:
+ raise PreconditionFailed('Resource ETag does not match')
if_none_match = request.META.get('HTTP_IF_NONE_MATCH')
if if_none_match is not None:
- if if_none_match == '*' or meta['hash'] in [x.lower() for x in parse_etags(if_none_match)]:
- raise NotModified('Resource Etag matches')
+ # TODO: If this passes, must ignore If-Modified-Since header.
+ if hash is not None:
+ if if_none_match == '*' or hash in [x.lower() for x in parse_etags(if_none_match)]:
+ # TODO: Continue if an If-Modified-Since header is present.
+ if request.method in ('HEAD', 'GET'):
+ raise NotModified('Resource ETag matches')
+ raise PreconditionFailed('Resource exists or ETag matches')
def split_container_object_string(s):
if not len(s) > 0 or s[0] != '/':
backend.copy_object(request.user, v_account, src_container, src_name, dest_container, dest_name, meta, False, permissions, src_version)
except NotAllowedError:
raise Unauthorized('Access denied')
- except NameError, IndexError:
+ except (NameError, IndexError):
raise ItemNotFound('Container or object does not exist')
except ValueError:
raise BadRequest('Invalid sharing header')