def send_data(self, request): """ Handles sending data to host for PUT or PATCH. :param request: http request object :type request: webob.Request :return: http response object :rtype: webob.Response """ # For now we require range headers; we could lift this restriction # later. If so, be sure to add conditions to request.headers access # below. # Note that webob request.headers is case-insensitive. if 'Content-Range' not in request.headers: raise exc.HTTPBadRequest( "Content-Range header required for {} requests".format( request.method)) resource_id = self.get_resource_id(request) imaged_url = self.get_imaged_url(request) headers = self.get_default_headers(resource_id) headers['Content-Range'] = request.headers['Content-Range'] headers['Content-Length'] = request.headers['Content-Length'] max_transfer_bytes = int(headers['Content-Length']) body = web.CappedStream(request.body_file, max_transfer_bytes) stream = False logging.debug("Resource %s: transferring %d bytes to host", resource_id, max_transfer_bytes) imaged_response = self.make_imaged_request(request.method, imaged_url, headers, body, stream) response = server.response(imaged_response.status_code) response.headers['Cache-Control'] = 'no-cache, no-store' return response
def get(self, res_id): resource_id = self.get_resource_id(self.request) imaged_url = self.get_imaged_url(self.request) headers = self.get_default_headers(resource_id) # Note that webob request.headers is case-insensitive. if 'Range' in self.request.headers: headers['Range'] = self.request.headers['Range'] body = "" stream = True # Don't let Requests read entire body into memory imaged_response = self.make_imaged_request(self.request.method, imaged_url, headers, body, stream) response = server.response(imaged_response.status_code) response.headers['Cache-Control'] = 'no-cache, no-store' response.headers['Content-Range'] = \ imaged_response.headers.get('Content-Range', '') disposition = imaged_response.headers.get('Content-Disposition') if disposition is not None: response.headers['Content-Disposition'] = disposition max_transfer_bytes = int(imaged_response.headers.get('Content-Length')) response.body_file = web.CappedStream( RequestStreamAdapter(imaged_response.iter_content(4096, False)), max_transfer_bytes) response.headers['Content-Length'] = str(max_transfer_bytes) logging.debug("Resource %s: transferring %d bytes from host", resource_id, max_transfer_bytes) return response
def patch(self, res_id): """ Proxy PATCH request to daemon. """ if not self.request.content_length: raise exc.HTTPBadRequest("Content-Length is required") # Notes: # - PATCH response is not cachable, no need for cache-control. # - We cannot have read_timeout since PATCH can take unpredictable # time, depending on size of the modified byte range, and the storage # capabillties. res = self.make_imaged_request( "PATCH", self.get_imaged_url(self.ticket), self.request.headers, web.CappedStream(self.request.body_file, self.request.content_length), False, connection_timeout=self.config.imaged_connection_timeout_sec) # TODO: We expect empty response from the daemon. If we start to return # non-empty response, this must be changed to stream the daemon # response to the caller. return web.response(res.status_code)
def put(self, res_id): """ Handles sending data to host for PUT or PATCH. :param request: http request object :type request: webob.Request :return: http response object :rtype: webob.Response """ imaged_url = self.get_imaged_url(self.ticket) if "flush" in self.request.params: imaged_url += "?flush=" + self.request.params["flush"] headers = self.get_default_headers(res_id) if 'Content-Length' not in self.request.headers: raise exc.HTTPBadRequest("Content-Length header is required") headers['Content-Length'] = self.request.headers['Content-Length'] if 'Content-Range' in self.request.headers: headers['Content-Range'] = self.request.headers['Content-Range'] max_transfer_bytes = int(headers['Content-Length']) body = web.CappedStream(self.request.body_file, max_transfer_bytes) stream = False logging.debug("Resource %s: transferring %d bytes to host", res_id, max_transfer_bytes) imaged_response = self.make_imaged_request( self.request.method, imaged_url, headers, body, stream, connection_timeout=self.config.imaged_connection_timeout_sec, read_timeout=self.config.imaged_read_timeout_sec) response = web.response(imaged_response.status_code) response.headers['Cache-Control'] = 'no-cache, no-store' return response
def test_capped_stream_short_reads(): stream = ioutil.UnbufferedStream([b"1" * 123, b"2" * 456]) capped_stream = web.CappedStream(stream, 1024) assert capped_stream.read(1024) == b"1" * 123 assert capped_stream.read(1024) == b"2" * 456 assert capped_stream.read(1024) == b""
def test_capped_stream_read_size(): stream = io.BytesIO(b"x" * 1024) capped_stream = web.CappedStream(stream, 768) assert capped_stream.read(123) == b"x" * 123
def test_capped_stream_read_default(): stream = io.BytesIO(b"x" * 8192) buffer_size = 4096 capped_stream = web.CappedStream(stream, 5120, buffer_size=buffer_size) assert capped_stream.read() == b"x" * buffer_size assert capped_stream.read() == b"x" * 1024
def test_capped_stream_buffer_size(): stream = io.BytesIO(b"x" * 8192) buffer_size = 4096 capped_stream = web.CappedStream(stream, 5120, buffer_size=buffer_size) chunks = list(capped_stream) assert chunks == [b"x" * buffer_size, b"x" * 1024]
def test_capped_stream_iter(): stream = io.BytesIO(b"x" * 148 * 1024) max_bytes = 138 * 1024 capped_stream = web.CappedStream(stream, max_bytes, buffer_size=128 * 1024) data = b"".join(capped_stream) assert data == b"x" * max_bytes