diff --git a/src/allmydata/storage/http_client.py b/src/allmydata/storage/http_client.py index 5e964bfbe..697af91b5 100644 --- a/src/allmydata/storage/http_client.py +++ b/src/allmydata/storage/http_client.py @@ -198,7 +198,7 @@ class StorageClientImmutables(object): "/v1/immutable/{}/{}".format(_encode_si(storage_index), share_number) ) response = yield self._client._request( - "POST", + "PATCH", url, upload_secret=upload_secret, data=data, diff --git a/src/allmydata/storage/http_server.py b/src/allmydata/storage/http_server.py index 23f0d2f1c..28367752d 100644 --- a/src/allmydata/storage/http_server.py +++ b/src/allmydata/storage/http_server.py @@ -219,15 +219,35 @@ class HTTPServer(object): ) def write_share_data(self, request, authorization, storage_index, share_number): """Write data to an in-progress immutable upload.""" - # TODO parse the content-range header to get offset for writing - # TODO basic checks on validity of offset - # TODO basic check that body isn't infinite. require content-length? if so, needs t be in protocol spec. + storage_index = si_a2b(storage_index.encode("ascii")) + content_range = request.getHeader("content-range") + if content_range is None: + offset = 0 + else: + offset = int(content_range.split()[1].split("-")[0]) + + # TODO basic checks on validity of start, offset, and content-range in general. also of share_number. + # TODO basic check that body isn't infinite. require content-length? or maybe we should require content-range (it's optional now)? if so, needs to be rflected in protocol spec. + data = request.content.read() - # TODO write to bucket at that offset. + try: + bucket = self._uploads[storage_index].shares[share_number] + except (KeyError, IndexError): + # TODO return 404 + raise - # TODO check if it conflicts with existing data (probably underlying code already handles that) if so, CONFLICT. + finished = bucket.write(offset, data) - # TODO if it finished writing altogether, 201 CREATED. Otherwise 200 OK. + # TODO if raises ConflictingWriteError, return HTTP CONFLICT code. + + if finished: + request.setResponseCode(http.CREATED) + else: + request.setResponseCode(http.OK) + + # TODO spec says we should return missing ranges. but client doesn't + # actually use them? So is it actually useful? + return b"" @_authorized_route( _app, diff --git a/src/allmydata/storage/immutable.py b/src/allmydata/storage/immutable.py index da9aa473f..5878e254a 100644 --- a/src/allmydata/storage/immutable.py +++ b/src/allmydata/storage/immutable.py @@ -13,7 +13,7 @@ if PY2: import os, stat, struct, time -from collections_extended import RangeMap +from collections_extended import RangeMap, MappedRange from foolscap.api import Referenceable @@ -375,7 +375,10 @@ class BucketWriter(object): def allocated_size(self): return self._max_size - def write(self, offset, data): + def write(self, offset, data): # type: (int, bytes) -> bool + """ + Write data at given offset, return whether the upload is complete. + """ # Delay the timeout, since we received data: self._timeout.reset(30 * 60) start = self._clock.seconds() @@ -399,6 +402,10 @@ class BucketWriter(object): self.ss.add_latency("write", self._clock.seconds() - start) self.ss.count("write") + # Return whether the whole thing has been written. + # TODO needs property test + return self._already_written.ranges() == [MappedRange(0, self._max_size, True)] + def close(self): precondition(not self.closed) self._timeout.cancel()