Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gcs/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ def rest_media(self, request, delay=time.sleep):
)
# Return 416 if the requested range cannot be satisfied.
if range_header is not None and begin >= length:
testbench.error.range_not_satisfiable()
testbench.error.range_not_satisfiable(length=length)

headers = {}
content_range = "bytes %d-%d/%d" % (begin, end - 1, length)
Expand Down
22 changes: 16 additions & 6 deletions testbench/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,24 @@


class RestException(Exception):
def __init__(self, msg, code):
def __init__(self, msg, code, headers=None):
super().__init__()
self.msg = msg
self.code = code
self.headers = headers or {}

def as_response(self):
# Include both code and message so we follow the schema outlined in
# https://cloud.google.com/apis/design/errors#error_model and some
# clients depend on code being specified, otherwise behavior is
# undefined.
return flask.make_response(
response = flask.make_response(
flask.jsonify(error={"code": self.code, "message": self.msg}),
self.code,
)
for key, value in self.headers.items():
response.headers[key] = value
return response

@staticmethod
def handler(ex):
Expand All @@ -53,12 +57,12 @@ def _simple_json_error(msg):
return json.dumps({"error": {"errors": [{"domain": "global", "message": msg}]}})


def generic(msg, rest_code, grpc_code, context):
def generic(msg, rest_code, grpc_code, context, headers=None):
"""Generate the appropriate error for REST or gRPC handlers."""
if context is not None:
context.abort(grpc_code, msg)
else:
raise RestException(msg, rest_code)
raise RestException(msg, rest_code, headers=headers)


def csek(context, rest_code=400, grpc_code=grpc.StatusCode.INVALID_ARGUMENT):
Expand Down Expand Up @@ -150,14 +154,20 @@ def already_exists(context=None):


def range_not_satisfiable(
context=None, rest_code=416, grpc_code=grpc.StatusCode.OUT_OF_RANGE
length=None, context=None, rest_code=416, grpc_code=grpc.StatusCode.OUT_OF_RANGE
):
"""Error returned when request range is not satisfiable."""
"""Error returned when request range is not satisfiable.

Includes a Content-Range: bytes */<length> header when length is provided,
matching real GCS behavior per RFC 7233.
"""
headers = {"Content-Range": "bytes */%d" % length} if length is not None else None
generic(
_simple_json_error("request range not satisfiable"),
rest_code,
grpc_code,
context,
headers=headers,
)


Expand Down
4 changes: 3 additions & 1 deletion testbench/grpc_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,9 @@ def ReadObject(self, request, context):
start = request.read_offset
read_end = len(blob.media)
if start > read_end:
return testbench.error.range_not_satisfiable(context)
return testbench.error.range_not_satisfiable(
length=read_end, context=context
)
if request.read_limit > 0:
read_end = min(read_end, start + request.read_limit)
content_range = None
Expand Down
Loading