Skip to content

Commit 71e50a7

Browse files
committed
Fix 204 responses, hangs and crashes with REST.
Remove Content-Type and make sure no content is returned by OPTIONS request in REST interface. In write_html set the Content-Length when response is not encoded/compressed (fixes hang due to missing content-length with unencoded data). In REST interface do not raise UsageError for invalid api version. Return json error with proper message. Fixes crash.
1 parent 01f34fd commit 71e50a7

File tree

4 files changed

+25
-20
lines changed

4 files changed

+25
-20
lines changed

roundup/cgi/client.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -645,8 +645,11 @@ def handle_rest(self):
645645

646646
# type header set by rest handler
647647
# self.setHeader("Content-Type", "text/xml")
648-
self.setHeader("Content-Length", str(len(output)))
649-
self.write(output)
648+
if self.response_code == 204: # no body with 204
649+
self.write("")
650+
else:
651+
self.setHeader("Content-Length", str(len(output)))
652+
self.write(output)
650653

651654
def add_ok_message(self, msg, escape=True):
652655
add_message(self._ok_message, msg, escape)
@@ -848,7 +851,7 @@ def inner_main(self):
848851
else:
849852
# in debug mode, only write error to screen.
850853
self.write_html(e.html)
851-
except:
854+
except Exception as e:
852855
# Something has gone badly wrong. Therefore, we should
853856
# make sure that the response code indicates failure.
854857
if self.response_code == http_.client.OK:
@@ -2196,6 +2199,8 @@ def write_html(self, content):
21962199
if 'Content-Type' not in self.additional_headers:
21972200
self.additional_headers['Content-Type'] = \
21982201
'text/html; charset=%s' % self.charset
2202+
if 'Content-Length' not in self.additional_headers:
2203+
self.additional_headers['Content-Length'] = len(content)
21992204
self.header()
22002205

22012206
if self.env['REQUEST_METHOD'] == 'HEAD':
@@ -2497,6 +2502,9 @@ def header(self, headers=None, response=None):
24972502
if headers.get('Content-Type', 'text/html') == 'text/html':
24982503
headers['Content-Type'] = 'text/html; charset=utf-8'
24992504

2505+
if response == 204: # has no body so no content-type
2506+
del(headers['Content-Type'])
2507+
25002508
headers = list(headers.items())
25012509

25022510
for ((path, name), (value, expire)) in self._cookies.items():

roundup/rest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2052,7 +2052,7 @@ def dispatch(self, method, uri, input):
20522052
# Use default if not specified for now.
20532053
self.api_version = self.__default_api_version
20542054
elif self.api_version not in self.__supported_api_versions:
2055-
raise UsageError(msg % self.api_version)
2055+
output = self.error_obj(400, msg % self.api_version)
20562056

20572057
# sadly del doesn't work on FieldStorage which can be the type of
20582058
# input. So we have to ignore keys starting with @ at other

test/rest_common.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2272,13 +2272,15 @@ def testAcceptHeaderParsing(self):
22722272
headers={"accept": "application/json; version=99"
22732273
}
22742274
self.headers=headers
2275-
with self.assertRaises(UsageError) as ctx:
2276-
results = self.server.dispatch('GET',
2277-
"/rest/data/status/1",
2278-
self.empty_form)
2275+
results = self.server.dispatch('GET',
2276+
"/rest/data/status/1",
2277+
self.empty_form)
22792278
print(results)
2280-
self.assertEqual(self.server.client.response_code, 200)
2281-
self.assertEqual(ctx.exception.args[0],
2279+
json_dict = json.loads(b2s(results))
2280+
self.assertEqual(self.server.client.response_code, 400)
2281+
self.assertEqual(self.server.client.additional_headers['Content-Type'],
2282+
"application/json")
2283+
self.assertEqual(json_dict['error']['msg'],
22822284
"Unrecognized version: 99. See /rest without "
22832285
"specifying version for supported versions.")
22842286

test/test_liveserver.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,7 @@ def test_rest_endpoint_root_options(self):
113113
print(f.headers)
114114

115115
self.assertEqual(f.status_code, 204)
116-
expected = { 'Content-Type': 'application/json',
117-
'Access-Control-Allow-Origin': '*',
116+
expected = { 'Access-Control-Allow-Origin': '*',
118117
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override',
119118
'Allow': 'OPTIONS, GET',
120119
'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH',
@@ -134,8 +133,7 @@ def test_rest_endpoint_data_options(self):
134133
print(f.headers)
135134

136135
self.assertEqual(f.status_code, 204)
137-
expected = { 'Content-Type': 'application/json',
138-
'Access-Control-Allow-Origin': '*',
136+
expected = { 'Access-Control-Allow-Origin': '*',
139137
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override',
140138
'Allow': 'OPTIONS, GET',
141139
'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH',
@@ -154,8 +152,7 @@ def test_rest_endpoint_collection_options(self):
154152
print(f.headers)
155153

156154
self.assertEqual(f.status_code, 204)
157-
expected = { 'Content-Type': 'application/json',
158-
'Access-Control-Allow-Origin': '*',
155+
expected = { 'Access-Control-Allow-Origin': '*',
159156
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override',
160157
'Allow': 'OPTIONS, GET, POST',
161158
'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH',
@@ -175,8 +172,7 @@ def test_rest_endpoint_item_options(self):
175172
print(f.headers)
176173

177174
self.assertEqual(f.status_code, 204)
178-
expected = { 'Content-Type': 'application/json',
179-
'Access-Control-Allow-Origin': '*',
175+
expected = { 'Access-Control-Allow-Origin': '*',
180176
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override',
181177
'Allow': 'OPTIONS, GET, PUT, DELETE, PATCH',
182178
'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH',
@@ -195,8 +191,7 @@ def test_rest_endpoint_attribute_options(self):
195191
print(f.headers)
196192

197193
self.assertEqual(f.status_code, 204)
198-
expected = { 'Content-Type': 'application/json',
199-
'Access-Control-Allow-Origin': '*',
194+
expected = { 'Access-Control-Allow-Origin': '*',
200195
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-HTTP-Method-Override',
201196
'Allow': 'OPTIONS, GET, PUT, DELETE, PATCH',
202197
'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET, PUT, DELETE, PATCH',

0 commit comments

Comments
 (0)