@@ -2042,9 +2042,6 @@ def dispatch(self, method, uri, input):
20422042 output = None
20432043
20442044 # Before we do anything has the user hit the rate limit.
2045- # This should (but doesn't at the moment) bypass
2046- # all other processing to minimize load of badly
2047- # behaving client.
20482045
20492046 # Get the limit here and not in the init() routine to allow
20502047 # for a different rate limit per user.
@@ -2076,32 +2073,40 @@ def dispatch(self, method, uri, input):
20762073 if reject :
20772074 for header , value in limitStatus .items ():
20782075 self .client .setHeader (header , value )
2079- # User exceeded limits: tell humans how long to wait
2080- # Headers above will do the right thing for api
2081- # aware clients.
2082- try :
2083- retry_after = limitStatus ['Retry-After' ]
2084- except KeyError :
2085- # handle race condition. If the time between
2086- # the call to grca.update and grca.status
2087- # is sufficient to reload the bucket by 1
2088- # item, Retry-After will be missing from
2089- # limitStatus. So report a 1 second delay back
2090- # to the client. We treat update as sole
2091- # source of truth for exceeded rate limits.
2092- retry_after = 1
2093- self .client .setHeader ('Retry-After' , '1' )
2094-
2095- msg = _ ("Api rate limits exceeded. Please wait: %s seconds." ) % retry_after
2096- output = self .error_obj (429 , msg , source = "ApiRateLimiter" )
2097- else :
2098- for header , value in limitStatus .items ():
2099- # Retry-After will be 0 because
2100- # user still has quota available.
2101- # Don't put out the header.
2102- if header in ('Retry-After' ,):
2103- continue
2104- self .client .setHeader (header , value )
2076+ # User exceeded limits: tell humans how long to wait
2077+ # Headers above will do the right thing for api
2078+ # aware clients.
2079+ try :
2080+ retry_after = limitStatus ['Retry-After' ]
2081+ except KeyError :
2082+ # handle race condition. If the time between
2083+ # the call to grca.update and grca.status
2084+ # is sufficient to reload the bucket by 1
2085+ # item, Retry-After will be missing from
2086+ # limitStatus. So report a 1 second delay back
2087+ # to the client. We treat update as sole
2088+ # source of truth for exceeded rate limits.
2089+ retry_after = 1
2090+ self .client .setHeader ('Retry-After' , '1' )
2091+
2092+ msg = _ ("Api rate limits exceeded. Please wait: %s seconds." ) % retry_after
2093+ output = self .error_obj (429 , msg , source = "ApiRateLimiter" )
2094+
2095+ return self .format_dispatch_output (
2096+ self .__default_accept_type ,
2097+ output ,
2098+ True # pretty print for this error case as a
2099+ # human may read it
2100+ )
2101+
2102+
2103+ for header , value in limitStatus .items ():
2104+ # Retry-After will be 0 because
2105+ # user still has quota available.
2106+ # Don't put out the header.
2107+ if header in ('Retry-After' ,):
2108+ continue
2109+ self .client .setHeader (header , value )
21052110
21062111 # if X-HTTP-Method-Override is set, follow the override method
21072112 headers = self .client .request .headers
@@ -2121,76 +2126,6 @@ def dispatch(self, method, uri, input):
21212126 'Ignoring X-HTTP-Method-Override using %s request on %s' ,
21222127 method .upper (), uri )
21232128
2124- # parse Accept header and get the content type
2125- # Acceptable types ordered with preferred one first
2126- # in list.
2127- try :
2128- accept_header = parse_accept_header (headers .get ('Accept' ))
2129- except UsageError as e :
2130- output = self .error_obj (406 , _ ("Unable to parse Accept Header. %(error)s. "
2131- "Acceptable types: %(acceptable_types)s" ) % {
2132- 'error' : e .args [0 ],
2133- 'acceptable_types' : " " .join (sorted (self .__accepted_content_type .keys ()))})
2134- accept_header = []
2135-
2136- if not accept_header :
2137- accept_type = self .__default_accept_type
2138- else :
2139- accept_type = None
2140- for part in accept_header :
2141- if accept_type :
2142- # we accepted the best match, stop searching for
2143- # lower quality matches.
2144- break
2145- if part [0 ] in self .__accepted_content_type :
2146- accept_type = self .__accepted_content_type [part [0 ]]
2147- # Version order:
2148- # 1) accept header version=X specifier
2149- # application/vnd.x.y; version=1
2150- # 2) from type in accept-header type/subtype-vX
2151- # application/vnd.x.y-v1
2152- # 3) from @apiver in query string to make browser
2153- # use easy
2154- # This code handles 1 and 2. Set api_version to none
2155- # to trigger @apiver parsing below
2156- # Places that need the api_version info should
2157- # use default if version = None
2158- try :
2159- self .api_version = int (part [1 ]['version' ])
2160- except KeyError :
2161- self .api_version = None
2162- except (ValueError , TypeError ):
2163- # TypeError if int(None)
2164- msg = ("Unrecognized api version: %s. "
2165- "See /rest without specifying api version "
2166- "for supported versions." % (
2167- part [1 ]['version' ]))
2168- output = self .error_obj (400 , msg )
2169-
2170- # get the request format for response
2171- # priority : extension from uri (/rest/data/issue.json),
2172- # header (Accept: application/json, application/xml)
2173- # default (application/json)
2174- ext_type = os .path .splitext (urlparse (uri ).path )[1 ][1 :]
2175-
2176- # Check to see if the length of the extension is less than 6.
2177- # this allows use of .vcard for a future use in downloading
2178- # user info. It also allows passing through larger items like
2179- # JWT that has a final component > 6 items. This method also
2180- # allow detection of mistyped types like jon for json.
2181- if ext_type and (len (ext_type ) < 6 ):
2182- # strip extension so uri make sense
2183- # .../issue.json -> .../issue
2184- uri = uri [:- (len (ext_type ) + 1 )]
2185- else :
2186- ext_type = None
2187-
2188- # headers.get('Accept') is never empty if called here.
2189- # accept_type will be set to json if there is no Accept header
2190- # accept_type wil be empty only if there is an Accept header
2191- # with invalid values.
2192- data_type = ext_type or accept_type or headers .get ('Accept' ) or "invalid"
2193-
21942129 if method .upper () == 'OPTIONS' :
21952130 # add access-control-allow-* access-control-max-age to support
21962131 # CORS preflight
@@ -2349,15 +2284,18 @@ def dispatch(self, method, uri, input):
23492284 output = self .error_obj (405 , msg .args [0 ])
23502285 self .client .setHeader ("Allow" , msg .args [1 ])
23512286
2287+ return self .format_dispatch_output (data_type , output , pretty_output )
2288+
2289+ def format_dispatch_output (self , accept_mime_type , output , pretty_print ):
23522290 # Format the content type
2353- if data_type .lower () == "json" :
2291+ if accept_mime_type .lower () == "json" :
23542292 self .client .setHeader ("Content-Type" , "application/json" )
2355- if pretty_output :
2293+ if pretty_print :
23562294 indent = 4
23572295 else :
23582296 indent = None
23592297 output = RoundupJSONEncoder (indent = indent ).encode (output )
2360- elif data_type .lower () == "xml" and dicttoxml :
2298+ elif accept_mime_type .lower () == "xml" and dicttoxml :
23612299 self .client .setHeader ("Content-Type" , "application/xml" )
23622300 if 'error' in output :
23632301 # capture values in error with types unsupported
@@ -2390,7 +2328,7 @@ def dispatch(self, method, uri, input):
23902328 # display acceptable output.
23912329 self .client .response_code = 406
23922330 output = ("Requested content type '%s' is not available.\n "
2393- "Acceptable types: %s" % (data_type ,
2331+ "Acceptable types: %s" % (accept_mime_type ,
23942332 ", " .join (sorted (self .__accepted_content_type .keys ()))))
23952333
23962334 # Make output json end in a newline to
0 commit comments