Package cherrypy :: Package test :: Module test_request_obj
[hide private]
[frames] | no frames]

Source Code for Module cherrypy.test.test_request_obj

  1  """Basic tests for the cherrypy.Request object.""" 
  2   
  3  import os 
  4  localDir = os.path.dirname(__file__) 
  5  import sys 
  6  import types 
  7  from cherrypy._cpcompat import IncompleteRead, ntob, ntou, unicodestr 
  8   
  9  import cherrypy 
 10  from cherrypy import _cptools, tools 
 11  from cherrypy.lib import httputil 
 12   
 13  defined_http_methods = ("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", 
 14                          "TRACE", "PROPFIND") 
 15   
 16   
 17  #                             Client-side code                             # 
 18   
 19  from cherrypy.test import helper 
 20   
21 -class RequestObjectTests(helper.CPWebCase):
22
23 - def setup_server():
24 class Root: 25 26 def index(self): 27 return "hello"
28 index.exposed = True 29 30 def scheme(self): 31 return cherrypy.request.scheme
32 scheme.exposed = True 33 34 root = Root() 35 36 37 class TestType(type): 38 """Metaclass which automatically exposes all functions in each subclass, 39 and adds an instance of the subclass as an attribute of root. 40 """ 41 def __init__(cls, name, bases, dct): 42 type.__init__(cls, name, bases, dct) 43 for value in dct.values(): 44 if isinstance(value, types.FunctionType): 45 value.exposed = True 46 setattr(root, name.lower(), cls()) 47 Test = TestType('Test', (object,), {}) 48 49 class PathInfo(Test): 50 51 def default(self, *args): 52 return cherrypy.request.path_info 53 54 class Params(Test): 55 56 def index(self, thing): 57 return repr(thing) 58 59 def ismap(self, x, y): 60 return "Coordinates: %s, %s" % (x, y) 61 62 def default(self, *args, **kwargs): 63 return "args: %s kwargs: %s" % (args, kwargs) 64 default._cp_config = {'request.query_string_encoding': 'latin1'} 65 66 67 class ParamErrorsCallable(object): 68 exposed = True 69 def __call__(self): 70 return "data" 71 72 class ParamErrors(Test): 73 74 def one_positional(self, param1): 75 return "data" 76 one_positional.exposed = True 77 78 def one_positional_args(self, param1, *args): 79 return "data" 80 one_positional_args.exposed = True 81 82 def one_positional_args_kwargs(self, param1, *args, **kwargs): 83 return "data" 84 one_positional_args_kwargs.exposed = True 85 86 def one_positional_kwargs(self, param1, **kwargs): 87 return "data" 88 one_positional_kwargs.exposed = True 89 90 def no_positional(self): 91 return "data" 92 no_positional.exposed = True 93 94 def no_positional_args(self, *args): 95 return "data" 96 no_positional_args.exposed = True 97 98 def no_positional_args_kwargs(self, *args, **kwargs): 99 return "data" 100 no_positional_args_kwargs.exposed = True 101 102 def no_positional_kwargs(self, **kwargs): 103 return "data" 104 no_positional_kwargs.exposed = True 105 106 callable_object = ParamErrorsCallable() 107 108 def raise_type_error(self, **kwargs): 109 raise TypeError("Client Error") 110 raise_type_error.exposed = True 111 112 def raise_type_error_with_default_param(self, x, y=None): 113 return '%d' % 'a' # throw an exception 114 raise_type_error_with_default_param.exposed = True 115 116 def callable_error_page(status, **kwargs): 117 return "Error %s - Well, I'm very sorry but you haven't paid!" % status 118 119 120 class Error(Test): 121 122 _cp_config = {'tools.log_tracebacks.on': True, 123 } 124 125 def reason_phrase(self): 126 raise cherrypy.HTTPError("410 Gone fishin'") 127 128 def custom(self, err='404'): 129 raise cherrypy.HTTPError(int(err), "No, <b>really</b>, not found!") 130 custom._cp_config = {'error_page.404': os.path.join(localDir, "static/index.html"), 131 'error_page.401': callable_error_page, 132 } 133 134 def custom_default(self): 135 return 1 + 'a' # raise an unexpected error 136 custom_default._cp_config = {'error_page.default': callable_error_page} 137 138 def noexist(self): 139 raise cherrypy.HTTPError(404, "No, <b>really</b>, not found!") 140 noexist._cp_config = {'error_page.404': "nonexistent.html"} 141 142 def page_method(self): 143 raise ValueError() 144 145 def page_yield(self): 146 yield "howdy" 147 raise ValueError() 148 149 def page_streamed(self): 150 yield "word up" 151 raise ValueError() 152 yield "very oops" 153 page_streamed._cp_config = {"response.stream": True} 154 155 def cause_err_in_finalize(self): 156 # Since status must start with an int, this should error. 157 cherrypy.response.status = "ZOO OK" 158 cause_err_in_finalize._cp_config = {'request.show_tracebacks': False} 159 160 def rethrow(self): 161 """Test that an error raised here will be thrown out to the server.""" 162 raise ValueError() 163 rethrow._cp_config = {'request.throw_errors': True} 164 165 166 class Expect(Test): 167 168 def expectation_failed(self): 169 expect = cherrypy.request.headers.elements("Expect") 170 if expect and expect[0].value != '100-continue': 171 raise cherrypy.HTTPError(400) 172 raise cherrypy.HTTPError(417, 'Expectation Failed') 173 174 class Headers(Test): 175 176 def default(self, headername): 177 """Spit back out the value for the requested header.""" 178 return cherrypy.request.headers[headername] 179 180 def doubledheaders(self): 181 # From http://www.cherrypy.org/ticket/165: 182 # "header field names should not be case sensitive sayes the rfc. 183 # if i set a headerfield in complete lowercase i end up with two 184 # header fields, one in lowercase, the other in mixed-case." 185 186 # Set the most common headers 187 hMap = cherrypy.response.headers 188 hMap['content-type'] = "text/html" 189 hMap['content-length'] = 18 190 hMap['server'] = 'CherryPy headertest' 191 hMap['location'] = ('%s://%s:%s/headers/' 192 % (cherrypy.request.local.ip, 193 cherrypy.request.local.port, 194 cherrypy.request.scheme)) 195 196 # Set a rare header for fun 197 hMap['Expires'] = 'Thu, 01 Dec 2194 16:00:00 GMT' 198 199 return "double header test" 200 201 def ifmatch(self): 202 val = cherrypy.request.headers['If-Match'] 203 assert isinstance(val, unicodestr) 204 cherrypy.response.headers['ETag'] = val 205 return val 206 207 208 class HeaderElements(Test): 209 210 def get_elements(self, headername): 211 e = cherrypy.request.headers.elements(headername) 212 return "\n".join([unicodestr(x) for x in e]) 213 214 215 class Method(Test): 216 217 def index(self): 218 m = cherrypy.request.method 219 if m in defined_http_methods or m == "CONNECT": 220 return m 221 222 if m == "LINK": 223 raise cherrypy.HTTPError(405) 224 else: 225 raise cherrypy.HTTPError(501) 226 227 def parameterized(self, data): 228 return data 229 230 def request_body(self): 231 # This should be a file object (temp file), 232 # which CP will just pipe back out if we tell it to. 233 return cherrypy.request.body 234 235 def reachable(self): 236 return "success" 237 238 class Divorce: 239 """HTTP Method handlers shouldn't collide with normal method names. 240 For example, a GET-handler shouldn't collide with a method named 'get'. 241 242 If you build HTTP method dispatching into CherryPy, rewrite this class 243 to use your new dispatch mechanism and make sure that: 244 "GET /divorce HTTP/1.1" maps to divorce.index() and 245 "GET /divorce/get?ID=13 HTTP/1.1" maps to divorce.get() 246 """ 247 248 documents = {} 249 250 def index(self): 251 yield "<h1>Choose your document</h1>\n" 252 yield "<ul>\n" 253 for id, contents in self.documents.items(): 254 yield (" <li><a href='/divorce/get?ID=%s'>%s</a>: %s</li>\n" 255 % (id, id, contents)) 256 yield "</ul>" 257 index.exposed = True 258 259 def get(self, ID): 260 return ("Divorce document %s: %s" % 261 (ID, self.documents.get(ID, "empty"))) 262 get.exposed = True 263 264 root.divorce = Divorce() 265 266 267 class ThreadLocal(Test): 268 269 def index(self): 270 existing = repr(getattr(cherrypy.request, "asdf", None)) 271 cherrypy.request.asdf = "rassfrassin" 272 return existing 273 274 appconf = { 275 '/method': {'request.methods_with_bodies': ("POST", "PUT", "PROPFIND")}, 276 } 277 cherrypy.tree.mount(root, config=appconf) 278 setup_server = staticmethod(setup_server) 279
280 - def test_scheme(self):
281 self.getPage("/scheme") 282 self.assertBody(self.scheme)
283
284 - def testRelativeURIPathInfo(self):
285 self.getPage("/pathinfo/foo/bar") 286 self.assertBody("/pathinfo/foo/bar")
287
288 - def testAbsoluteURIPathInfo(self):
289 # http://cherrypy.org/ticket/1061 290 self.getPage("http://localhost/pathinfo/foo/bar") 291 self.assertBody("/pathinfo/foo/bar")
292
293 - def testParams(self):
294 self.getPage("/params/?thing=a") 295 self.assertBody(repr(ntou("a"))) 296 297 self.getPage("/params/?thing=a&thing=b&thing=c") 298 self.assertBody(repr([ntou('a'), ntou('b'), ntou('c')])) 299 300 # Test friendly error message when given params are not accepted. 301 cherrypy.config.update({"request.show_mismatched_params": True}) 302 self.getPage("/params/?notathing=meeting") 303 self.assertInBody("Missing parameters: thing") 304 self.getPage("/params/?thing=meeting&notathing=meeting") 305 self.assertInBody("Unexpected query string parameters: notathing") 306 307 # Test ability to turn off friendly error messages 308 cherrypy.config.update({"request.show_mismatched_params": False}) 309 self.getPage("/params/?notathing=meeting") 310 self.assertInBody("Not Found") 311 self.getPage("/params/?thing=meeting&notathing=meeting") 312 self.assertInBody("Not Found") 313 314 # Test "% HEX HEX"-encoded URL, param keys, and values 315 self.getPage("/params/%d4%20%e3/cheese?Gruy%E8re=Bulgn%e9ville") 316 self.assertBody("args: %s kwargs: %s" % 317 (('\xd4 \xe3', 'cheese'), 318 {'Gruy\xe8re': ntou('Bulgn\xe9ville')})) 319 320 # Make sure that encoded = and & get parsed correctly 321 self.getPage("/params/code?url=http%3A//cherrypy.org/index%3Fa%3D1%26b%3D2") 322 self.assertBody("args: %s kwargs: %s" % 323 (('code',), 324 {'url': ntou('http://cherrypy.org/index?a=1&b=2')})) 325 326 # Test coordinates sent by <img ismap> 327 self.getPage("/params/ismap?223,114") 328 self.assertBody("Coordinates: 223, 114") 329 330 # Test "name[key]" dict-like params 331 self.getPage("/params/dictlike?a[1]=1&a[2]=2&b=foo&b[bar]=baz") 332 self.assertBody("args: %s kwargs: %s" % 333 (('dictlike',), 334 {'a[1]': ntou('1'), 'b[bar]': ntou('baz'), 335 'b': ntou('foo'), 'a[2]': ntou('2')}))
336
337 - def testParamErrors(self):
338 339 # test that all of the handlers work when given 340 # the correct parameters in order to ensure that the 341 # errors below aren't coming from some other source. 342 for uri in ( 343 '/paramerrors/one_positional?param1=foo', 344 '/paramerrors/one_positional_args?param1=foo', 345 '/paramerrors/one_positional_args/foo', 346 '/paramerrors/one_positional_args/foo/bar/baz', 347 '/paramerrors/one_positional_args_kwargs?param1=foo&param2=bar', 348 '/paramerrors/one_positional_args_kwargs/foo?param2=bar&param3=baz', 349 '/paramerrors/one_positional_args_kwargs/foo/bar/baz?param2=bar&param3=baz', 350 '/paramerrors/one_positional_kwargs?param1=foo&param2=bar&param3=baz', 351 '/paramerrors/one_positional_kwargs/foo?param4=foo&param2=bar&param3=baz', 352 '/paramerrors/no_positional', 353 '/paramerrors/no_positional_args/foo', 354 '/paramerrors/no_positional_args/foo/bar/baz', 355 '/paramerrors/no_positional_args_kwargs?param1=foo&param2=bar', 356 '/paramerrors/no_positional_args_kwargs/foo?param2=bar', 357 '/paramerrors/no_positional_args_kwargs/foo/bar/baz?param2=bar&param3=baz', 358 '/paramerrors/no_positional_kwargs?param1=foo&param2=bar', 359 '/paramerrors/callable_object', 360 ): 361 self.getPage(uri) 362 self.assertStatus(200) 363 364 # query string parameters are part of the URI, so if they are wrong 365 # for a particular handler, the status MUST be a 404. 366 error_msgs = [ 367 'Missing parameters', 368 'Nothing matches the given URI', 369 'Multiple values for parameters', 370 'Unexpected query string parameters', 371 'Unexpected body parameters', 372 ] 373 for uri, msg in ( 374 ('/paramerrors/one_positional', error_msgs[0]), 375 ('/paramerrors/one_positional?foo=foo', error_msgs[0]), 376 ('/paramerrors/one_positional/foo/bar/baz', error_msgs[1]), 377 ('/paramerrors/one_positional/foo?param1=foo', error_msgs[2]), 378 ('/paramerrors/one_positional/foo?param1=foo&param2=foo', error_msgs[2]), 379 ('/paramerrors/one_positional_args/foo?param1=foo&param2=foo', error_msgs[2]), 380 ('/paramerrors/one_positional_args/foo/bar/baz?param2=foo', error_msgs[3]), 381 ('/paramerrors/one_positional_args_kwargs/foo/bar/baz?param1=bar&param3=baz', error_msgs[2]), 382 ('/paramerrors/one_positional_kwargs/foo?param1=foo&param2=bar&param3=baz', error_msgs[2]), 383 ('/paramerrors/no_positional/boo', error_msgs[1]), 384 ('/paramerrors/no_positional?param1=foo', error_msgs[3]), 385 ('/paramerrors/no_positional_args/boo?param1=foo', error_msgs[3]), 386 ('/paramerrors/no_positional_kwargs/boo?param1=foo', error_msgs[1]), 387 ('/paramerrors/callable_object?param1=foo', error_msgs[3]), 388 ('/paramerrors/callable_object/boo', error_msgs[1]), 389 ): 390 for show_mismatched_params in (True, False): 391 cherrypy.config.update({'request.show_mismatched_params': show_mismatched_params}) 392 self.getPage(uri) 393 self.assertStatus(404) 394 if show_mismatched_params: 395 self.assertInBody(msg) 396 else: 397 self.assertInBody("Not Found") 398 399 # if body parameters are wrong, a 400 must be returned. 400 for uri, body, msg in ( 401 ('/paramerrors/one_positional/foo', 'param1=foo', error_msgs[2]), 402 ('/paramerrors/one_positional/foo', 'param1=foo&param2=foo', error_msgs[2]), 403 ('/paramerrors/one_positional_args/foo', 'param1=foo&param2=foo', error_msgs[2]), 404 ('/paramerrors/one_positional_args/foo/bar/baz', 'param2=foo', error_msgs[4]), 405 ('/paramerrors/one_positional_args_kwargs/foo/bar/baz', 'param1=bar&param3=baz', error_msgs[2]), 406 ('/paramerrors/one_positional_kwargs/foo', 'param1=foo&param2=bar&param3=baz', error_msgs[2]), 407 ('/paramerrors/no_positional', 'param1=foo', error_msgs[4]), 408 ('/paramerrors/no_positional_args/boo', 'param1=foo', error_msgs[4]), 409 ('/paramerrors/callable_object', 'param1=foo', error_msgs[4]), 410 ): 411 for show_mismatched_params in (True, False): 412 cherrypy.config.update({'request.show_mismatched_params': show_mismatched_params}) 413 self.getPage(uri, method='POST', body=body) 414 self.assertStatus(400) 415 if show_mismatched_params: 416 self.assertInBody(msg) 417 else: 418 self.assertInBody("400 Bad") 419 420 421 # even if body parameters are wrong, if we get the uri wrong, then 422 # it's a 404 423 for uri, body, msg in ( 424 ('/paramerrors/one_positional?param2=foo', 'param1=foo', error_msgs[3]), 425 ('/paramerrors/one_positional/foo/bar', 'param2=foo', error_msgs[1]), 426 ('/paramerrors/one_positional_args/foo/bar?param2=foo', 'param3=foo', error_msgs[3]), 427 ('/paramerrors/one_positional_kwargs/foo/bar', 'param2=bar&param3=baz', error_msgs[1]), 428 ('/paramerrors/no_positional?param1=foo', 'param2=foo', error_msgs[3]), 429 ('/paramerrors/no_positional_args/boo?param2=foo', 'param1=foo', error_msgs[3]), 430 ('/paramerrors/callable_object?param2=bar', 'param1=foo', error_msgs[3]), 431 ): 432 for show_mismatched_params in (True, False): 433 cherrypy.config.update({'request.show_mismatched_params': show_mismatched_params}) 434 self.getPage(uri, method='POST', body=body) 435 self.assertStatus(404) 436 if show_mismatched_params: 437 self.assertInBody(msg) 438 else: 439 self.assertInBody("Not Found") 440 441 # In the case that a handler raises a TypeError we should 442 # let that type error through. 443 for uri in ( 444 '/paramerrors/raise_type_error', 445 '/paramerrors/raise_type_error_with_default_param?x=0', 446 '/paramerrors/raise_type_error_with_default_param?x=0&y=0', 447 ): 448 self.getPage(uri, method='GET') 449 self.assertStatus(500) 450 self.assertTrue('Client Error', self.body)
451
452 - def testErrorHandling(self):
453 self.getPage("/error/missing") 454 self.assertStatus(404) 455 self.assertErrorPage(404, "The path '/error/missing' was not found.") 456 457 ignore = helper.webtest.ignored_exceptions 458 ignore.append(ValueError) 459 try: 460 valerr = '\n raise ValueError()\nValueError' 461 self.getPage("/error/page_method") 462 self.assertErrorPage(500, pattern=valerr) 463 464 self.getPage("/error/page_yield") 465 self.assertErrorPage(500, pattern=valerr) 466 467 if (cherrypy.server.protocol_version == "HTTP/1.0" or 468 getattr(cherrypy.server, "using_apache", False)): 469 self.getPage("/error/page_streamed") 470 # Because this error is raised after the response body has 471 # started, the status should not change to an error status. 472 self.assertStatus(200) 473 self.assertBody("word up") 474 else: 475 # Under HTTP/1.1, the chunked transfer-coding is used. 476 # The HTTP client will choke when the output is incomplete. 477 self.assertRaises((ValueError, IncompleteRead), self.getPage, 478 "/error/page_streamed") 479 480 # No traceback should be present 481 self.getPage("/error/cause_err_in_finalize") 482 msg = "Illegal response status from server ('ZOO' is non-numeric)." 483 self.assertErrorPage(500, msg, None) 484 finally: 485 ignore.pop() 486 487 # Test HTTPError with a reason-phrase in the status arg. 488 self.getPage('/error/reason_phrase') 489 self.assertStatus("410 Gone fishin'") 490 491 # Test custom error page for a specific error. 492 self.getPage("/error/custom") 493 self.assertStatus(404) 494 self.assertBody("Hello, world\r\n" + (" " * 499)) 495 496 # Test custom error page for a specific error. 497 self.getPage("/error/custom?err=401") 498 self.assertStatus(401) 499 self.assertBody("Error 401 Unauthorized - Well, I'm very sorry but you haven't paid!") 500 501 # Test default custom error page. 502 self.getPage("/error/custom_default") 503 self.assertStatus(500) 504 self.assertBody("Error 500 Internal Server Error - Well, I'm very sorry but you haven't paid!".ljust(513)) 505 506 # Test error in custom error page (ticket #305). 507 # Note that the message is escaped for HTML (ticket #310). 508 self.getPage("/error/noexist") 509 self.assertStatus(404) 510 msg = ("No, &lt;b&gt;really&lt;/b&gt;, not found!<br />" 511 "In addition, the custom error page failed:\n<br />" 512 "IOError: [Errno 2] No such file or directory: 'nonexistent.html'") 513 self.assertInBody(msg) 514 515 if getattr(cherrypy.server, "using_apache", False): 516 pass 517 else: 518 # Test throw_errors (ticket #186). 519 self.getPage("/error/rethrow") 520 self.assertInBody("raise ValueError()")
521
522 - def testExpect(self):
523 e = ('Expect', '100-continue') 524 self.getPage("/headerelements/get_elements?headername=Expect", [e]) 525 self.assertBody('100-continue') 526 527 self.getPage("/expect/expectation_failed", [e]) 528 self.assertStatus(417)
529
530 - def testHeaderElements(self):
531 # Accept-* header elements should be sorted, with most preferred first. 532 h = [('Accept', 'audio/*; q=0.2, audio/basic')] 533 self.getPage("/headerelements/get_elements?headername=Accept", h) 534 self.assertStatus(200) 535 self.assertBody("audio/basic\n" 536 "audio/*;q=0.2") 537 538 h = [('Accept', 'text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c')] 539 self.getPage("/headerelements/get_elements?headername=Accept", h) 540 self.assertStatus(200) 541 self.assertBody("text/x-c\n" 542 "text/html\n" 543 "text/x-dvi;q=0.8\n" 544 "text/plain;q=0.5") 545 546 # Test that more specific media ranges get priority. 547 h = [('Accept', 'text/*, text/html, text/html;level=1, */*')] 548 self.getPage("/headerelements/get_elements?headername=Accept", h) 549 self.assertStatus(200) 550 self.assertBody("text/html;level=1\n" 551 "text/html\n" 552 "text/*\n" 553 "*/*") 554 555 # Test Accept-Charset 556 h = [('Accept-Charset', 'iso-8859-5, unicode-1-1;q=0.8')] 557 self.getPage("/headerelements/get_elements?headername=Accept-Charset", h) 558 self.assertStatus("200 OK") 559 self.assertBody("iso-8859-5\n" 560 "unicode-1-1;q=0.8") 561 562 # Test Accept-Encoding 563 h = [('Accept-Encoding', 'gzip;q=1.0, identity; q=0.5, *;q=0')] 564 self.getPage("/headerelements/get_elements?headername=Accept-Encoding", h) 565 self.assertStatus("200 OK") 566 self.assertBody("gzip;q=1.0\n" 567 "identity;q=0.5\n" 568 "*;q=0") 569 570 # Test Accept-Language 571 h = [('Accept-Language', 'da, en-gb;q=0.8, en;q=0.7')] 572 self.getPage("/headerelements/get_elements?headername=Accept-Language", h) 573 self.assertStatus("200 OK") 574 self.assertBody("da\n" 575 "en-gb;q=0.8\n" 576 "en;q=0.7") 577 578 # Test malformed header parsing. See http://www.cherrypy.org/ticket/763. 579 self.getPage("/headerelements/get_elements?headername=Content-Type", 580 # Note the illegal trailing ";" 581 headers=[('Content-Type', 'text/html; charset=utf-8;')]) 582 self.assertStatus(200) 583 self.assertBody("text/html;charset=utf-8")
584
585 - def test_repeated_headers(self):
586 # Test that two request headers are collapsed into one. 587 # See http://www.cherrypy.org/ticket/542. 588 self.getPage("/headers/Accept-Charset", 589 headers=[("Accept-Charset", "iso-8859-5"), 590 ("Accept-Charset", "unicode-1-1;q=0.8")]) 591 self.assertBody("iso-8859-5, unicode-1-1;q=0.8") 592 593 # Tests that each header only appears once, regardless of case. 594 self.getPage("/headers/doubledheaders") 595 self.assertBody("double header test") 596 hnames = [name.title() for name, val in self.headers] 597 for key in ['Content-Length', 'Content-Type', 'Date', 598 'Expires', 'Location', 'Server']: 599 self.assertEqual(hnames.count(key), 1, self.headers)
600
601 - def test_encoded_headers(self):
602 # First, make sure the innards work like expected. 603 self.assertEqual(httputil.decode_TEXT(ntou("=?utf-8?q?f=C3=BCr?=")), ntou("f\xfcr")) 604 605 if cherrypy.server.protocol_version == "HTTP/1.1": 606 # Test RFC-2047-encoded request and response header values 607 u = ntou('\u212bngstr\xf6m', 'escape') 608 c = ntou("=E2=84=ABngstr=C3=B6m") 609 self.getPage("/headers/ifmatch", [('If-Match', ntou('=?utf-8?q?%s?=') % c)]) 610 # The body should be utf-8 encoded. 611 self.assertBody(ntob("\xe2\x84\xabngstr\xc3\xb6m")) 612 # But the Etag header should be RFC-2047 encoded (binary) 613 self.assertHeader("ETag", ntou('=?utf-8?b?4oSrbmdzdHLDtm0=?=')) 614 615 # Test a *LONG* RFC-2047-encoded request and response header value 616 self.getPage("/headers/ifmatch", 617 [('If-Match', ntou('=?utf-8?q?%s?=') % (c * 10))]) 618 self.assertBody(ntob("\xe2\x84\xabngstr\xc3\xb6m") * 10) 619 # Note: this is different output for Python3, but it decodes fine. 620 etag = self.assertHeader("ETag", 621 '=?utf-8?b?4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt' 622 '4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt' 623 '4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt' 624 '4oSrbmdzdHLDtm0=?=') 625 self.assertEqual(httputil.decode_TEXT(etag), u * 10)
626
627 - def test_header_presence(self):
628 # If we don't pass a Content-Type header, it should not be present 629 # in cherrypy.request.headers 630 self.getPage("/headers/Content-Type", 631 headers=[]) 632 self.assertStatus(500) 633 634 # If Content-Type is present in the request, it should be present in 635 # cherrypy.request.headers 636 self.getPage("/headers/Content-Type", 637 headers=[("Content-type", "application/json")]) 638 self.assertBody("application/json")
639
640 - def test_basic_HTTPMethods(self):
641 helper.webtest.methods_with_bodies = ("POST", "PUT", "PROPFIND") 642 643 # Test that all defined HTTP methods work. 644 for m in defined_http_methods: 645 self.getPage("/method/", method=m) 646 647 # HEAD requests should not return any body. 648 if m == "HEAD": 649 self.assertBody("") 650 elif m == "TRACE": 651 # Some HTTP servers (like modpy) have their own TRACE support 652 self.assertEqual(self.body[:5], ntob("TRACE")) 653 else: 654 self.assertBody(m) 655 656 # Request a PUT method with a form-urlencoded body 657 self.getPage("/method/parameterized", method="PUT", 658 body="data=on+top+of+other+things") 659 self.assertBody("on top of other things") 660 661 # Request a PUT method with a file body 662 b = "one thing on top of another" 663 h = [("Content-Type", "text/plain"), 664 ("Content-Length", str(len(b)))] 665 self.getPage("/method/request_body", headers=h, method="PUT", body=b) 666 self.assertStatus(200) 667 self.assertBody(b) 668 669 # Request a PUT method with a file body but no Content-Type. 670 # See http://www.cherrypy.org/ticket/790. 671 b = ntob("one thing on top of another") 672 self.persistent = True 673 try: 674 conn = self.HTTP_CONN 675 conn.putrequest("PUT", "/method/request_body", skip_host=True) 676 conn.putheader("Host", self.HOST) 677 conn.putheader('Content-Length', str(len(b))) 678 conn.endheaders() 679 conn.send(b) 680 response = conn.response_class(conn.sock, method="PUT") 681 response.begin() 682 self.assertEqual(response.status, 200) 683 self.body = response.read() 684 self.assertBody(b) 685 finally: 686 self.persistent = False 687 688 # Request a PUT method with no body whatsoever (not an empty one). 689 # See http://www.cherrypy.org/ticket/650. 690 # Provide a C-T or webtest will provide one (and a C-L) for us. 691 h = [("Content-Type", "text/plain")] 692 self.getPage("/method/reachable", headers=h, method="PUT") 693 self.assertStatus(411) 694 695 # Request a custom method with a request body 696 b = ('<?xml version="1.0" encoding="utf-8" ?>\n\n' 697 '<propfind xmlns="DAV:"><prop><getlastmodified/>' 698 '</prop></propfind>') 699 h = [('Content-Type', 'text/xml'), 700 ('Content-Length', str(len(b)))] 701 self.getPage("/method/request_body", headers=h, method="PROPFIND", body=b) 702 self.assertStatus(200) 703 self.assertBody(b) 704 705 # Request a disallowed method 706 self.getPage("/method/", method="LINK") 707 self.assertStatus(405) 708 709 # Request an unknown method 710 self.getPage("/method/", method="SEARCH") 711 self.assertStatus(501) 712 713 # For method dispatchers: make sure that an HTTP method doesn't 714 # collide with a virtual path atom. If you build HTTP-method 715 # dispatching into the core, rewrite these handlers to use 716 # your dispatch idioms. 717 self.getPage("/divorce/get?ID=13") 718 self.assertBody('Divorce document 13: empty') 719 self.assertStatus(200) 720 self.getPage("/divorce/", method="GET") 721 self.assertBody('<h1>Choose your document</h1>\n<ul>\n</ul>') 722 self.assertStatus(200)
723
724 - def test_CONNECT_method(self):
725 if getattr(cherrypy.server, "using_apache", False): 726 return self.skip("skipped due to known Apache differences... ") 727 728 self.getPage("/method/", method="CONNECT") 729 self.assertBody("CONNECT")
730
731 - def testEmptyThreadlocals(self):
732 results = [] 733 for x in range(20): 734 self.getPage("/threadlocal/") 735 results.append(self.body) 736 self.assertEqual(results, [ntob("None")] * 20)
737