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

Source Code for Module cherrypy.test.test_static

  1  import os 
  2  import sys 
  3   
  4  from cherrypy._cpcompat import HTTPConnection, HTTPSConnection, ntob 
  5  from cherrypy._cpcompat import BytesIO 
  6   
  7  curdir = os.path.join(os.getcwd(), os.path.dirname(__file__)) 
  8  has_space_filepath = os.path.join(curdir, 'static', 'has space.html') 
  9  bigfile_filepath = os.path.join(curdir, "static", "bigfile.log") 
 10   
 11  # The file size needs to be big enough such that half the size of it 
 12  # won't be socket-buffered (or server-buffered) all in one go. See 
 13  # test_file_stream. 
 14  BIGFILE_SIZE = 1024 * 1024 * 4 
 15   
 16  import cherrypy 
 17  from cherrypy.lib import static 
 18  from cherrypy.test import helper 
 19   
 20   
21 -class StaticTest(helper.CPWebCase):
22
23 - def setup_server():
24 if not os.path.exists(has_space_filepath): 25 open(has_space_filepath, 'wb').write(ntob('Hello, world\r\n')) 26 if not os.path.exists(bigfile_filepath) or \ 27 os.path.getsize(bigfile_filepath) != BIGFILE_SIZE: 28 open(bigfile_filepath, 'wb').write(ntob("x" * BIGFILE_SIZE)) 29 30 class Root: 31 32 def bigfile(self): 33 from cherrypy.lib import static 34 self.f = static.serve_file(bigfile_filepath) 35 return self.f
36 bigfile.exposed = True 37 bigfile._cp_config = {'response.stream': True} 38 39 def tell(self): 40 if self.f.input.closed: 41 return '' 42 return repr(self.f.input.tell()).rstrip('L')
43 tell.exposed = True 44 45 def fileobj(self): 46 f = open(os.path.join(curdir, 'style.css'), 'rb') 47 return static.serve_fileobj(f, content_type='text/css') 48 fileobj.exposed = True 49 50 def bytesio(self): 51 f = BytesIO(ntob('Fee\nfie\nfo\nfum')) 52 return static.serve_fileobj(f, content_type='text/plain') 53 bytesio.exposed = True 54 55 class Static: 56 57 def index(self): 58 return 'You want the Baron? You can have the Baron!' 59 index.exposed = True 60 61 def dynamic(self): 62 return "This is a DYNAMIC page" 63 dynamic.exposed = True 64 65 root = Root() 66 root.static = Static() 67 68 rootconf = { 69 '/static': { 70 'tools.staticdir.on': True, 71 'tools.staticdir.dir': 'static', 72 'tools.staticdir.root': curdir, 73 }, 74 '/style.css': { 75 'tools.staticfile.on': True, 76 'tools.staticfile.filename': os.path.join(curdir, 'style.css'), 77 }, 78 '/docroot': { 79 'tools.staticdir.on': True, 80 'tools.staticdir.root': curdir, 81 'tools.staticdir.dir': 'static', 82 'tools.staticdir.index': 'index.html', 83 }, 84 '/error': { 85 'tools.staticdir.on': True, 86 'request.show_tracebacks': True, 87 }, 88 '/404test': { 89 'tools.staticdir.on': True, 90 'tools.staticdir.root': curdir, 91 'tools.staticdir.dir': 'static', 92 'error_page.404': error_page_404, 93 } 94 } 95 rootApp = cherrypy.Application(root) 96 rootApp.merge(rootconf) 97 98 test_app_conf = { 99 '/test': { 100 'tools.staticdir.index': 'index.html', 101 'tools.staticdir.on': True, 102 'tools.staticdir.root': curdir, 103 'tools.staticdir.dir': 'static', 104 }, 105 } 106 testApp = cherrypy.Application(Static()) 107 testApp.merge(test_app_conf) 108 109 vhost = cherrypy._cpwsgi.VirtualHost(rootApp, {'virt.net': testApp}) 110 cherrypy.tree.graft(vhost) 111 setup_server = staticmethod(setup_server) 112
113 - def teardown_server():
114 for f in (has_space_filepath, bigfile_filepath): 115 if os.path.exists(f): 116 try: 117 os.unlink(f) 118 except: 119 pass
120 teardown_server = staticmethod(teardown_server) 121
122 - def testStatic(self):
123 self.getPage("/static/index.html") 124 self.assertStatus('200 OK') 125 self.assertHeader('Content-Type', 'text/html') 126 self.assertBody('Hello, world\r\n') 127 128 # Using a staticdir.root value in a subdir... 129 self.getPage("/docroot/index.html") 130 self.assertStatus('200 OK') 131 self.assertHeader('Content-Type', 'text/html') 132 self.assertBody('Hello, world\r\n') 133 134 # Check a filename with spaces in it 135 self.getPage("/static/has%20space.html") 136 self.assertStatus('200 OK') 137 self.assertHeader('Content-Type', 'text/html') 138 self.assertBody('Hello, world\r\n') 139 140 self.getPage("/style.css") 141 self.assertStatus('200 OK') 142 self.assertHeader('Content-Type', 'text/css') 143 # Note: The body should be exactly 'Dummy stylesheet\n', but 144 # unfortunately some tools such as WinZip sometimes turn \n 145 # into \r\n on Windows when extracting the CherryPy tarball so 146 # we just check the content 147 self.assertMatchesBody('^Dummy stylesheet')
148
149 - def test_fallthrough(self):
150 # Test that NotFound will then try dynamic handlers (see [878]). 151 self.getPage("/static/dynamic") 152 self.assertBody("This is a DYNAMIC page") 153 154 # Check a directory via fall-through to dynamic handler. 155 self.getPage("/static/") 156 self.assertStatus('200 OK') 157 self.assertHeader('Content-Type', 'text/html;charset=utf-8') 158 self.assertBody('You want the Baron? You can have the Baron!')
159
160 - def test_index(self):
161 # Check a directory via "staticdir.index". 162 self.getPage("/docroot/") 163 self.assertStatus('200 OK') 164 self.assertHeader('Content-Type', 'text/html') 165 self.assertBody('Hello, world\r\n') 166 # The same page should be returned even if redirected. 167 self.getPage("/docroot") 168 self.assertStatus(301) 169 self.assertHeader('Location', '%s/docroot/' % self.base()) 170 self.assertMatchesBody("This resource .* <a href=(['\"])%s/docroot/\\1>" 171 "%s/docroot/</a>." % (self.base(), self.base()))
172
173 - def test_config_errors(self):
174 # Check that we get an error if no .file or .dir 175 self.getPage("/error/thing.html") 176 self.assertErrorPage(500) 177 if sys.version_info >= (3, 3): 178 errmsg = ntob("TypeError: staticdir\(\) missing 2 " 179 "required positional arguments") 180 else: 181 errmsg = ntob("TypeError: staticdir\(\) takes at least 2 " 182 "(positional )?arguments \(0 given\)") 183 self.assertMatchesBody(errmsg)
184
185 - def test_security(self):
186 # Test up-level security 187 self.getPage("/static/../../test/style.css") 188 self.assertStatus((400, 403))
189
190 - def test_modif(self):
191 # Test modified-since on a reasonably-large file 192 self.getPage("/static/dirback.jpg") 193 self.assertStatus("200 OK") 194 lastmod = "" 195 for k, v in self.headers: 196 if k == 'Last-Modified': 197 lastmod = v 198 ims = ("If-Modified-Since", lastmod) 199 self.getPage("/static/dirback.jpg", headers=[ims]) 200 self.assertStatus(304) 201 self.assertNoHeader("Content-Type") 202 self.assertNoHeader("Content-Length") 203 self.assertNoHeader("Content-Disposition") 204 self.assertBody("")
205
206 - def test_755_vhost(self):
207 self.getPage("/test/", [('Host', 'virt.net')]) 208 self.assertStatus(200) 209 self.getPage("/test", [('Host', 'virt.net')]) 210 self.assertStatus(301) 211 self.assertHeader('Location', self.scheme + '://virt.net/test/')
212
213 - def test_serve_fileobj(self):
214 self.getPage("/fileobj") 215 self.assertStatus('200 OK') 216 self.assertHeader('Content-Type', 'text/css;charset=utf-8') 217 self.assertMatchesBody('^Dummy stylesheet')
218
219 - def test_serve_bytesio(self):
220 self.getPage("/bytesio") 221 self.assertStatus('200 OK') 222 self.assertHeader('Content-Type', 'text/plain;charset=utf-8') 223 self.assertHeader('Content-Length', 14) 224 self.assertMatchesBody('Fee\nfie\nfo\nfum')
225
226 - def test_file_stream(self):
227 if cherrypy.server.protocol_version != "HTTP/1.1": 228 return self.skip() 229 230 self.PROTOCOL = "HTTP/1.1" 231 232 # Make an initial request 233 self.persistent = True 234 conn = self.HTTP_CONN 235 conn.putrequest("GET", "/bigfile", skip_host=True) 236 conn.putheader("Host", self.HOST) 237 conn.endheaders() 238 response = conn.response_class(conn.sock, method="GET") 239 response.begin() 240 self.assertEqual(response.status, 200) 241 242 body = ntob('') 243 remaining = BIGFILE_SIZE 244 while remaining > 0: 245 data = response.fp.read(65536) 246 if not data: 247 break 248 body += data 249 remaining -= len(data) 250 251 if self.scheme == "https": 252 newconn = HTTPSConnection 253 else: 254 newconn = HTTPConnection 255 s, h, b = helper.webtest.openURL( 256 ntob("/tell"), headers=[], host=self.HOST, port=self.PORT, 257 http_conn=newconn) 258 if not b: 259 # The file was closed on the server. 260 tell_position = BIGFILE_SIZE 261 else: 262 tell_position = int(b) 263 264 read_so_far = len(body) 265 266 # It is difficult for us to force the server to only read 267 # the bytes that we ask for - there are going to be buffers 268 # inbetween. 269 # 270 # CherryPy will attempt to write as much data as it can to 271 # the socket, and we don't have a way to determine what that 272 # size will be. So we make the following assumption - by 273 # the time we have read in the entire file on the server, 274 # we will have at least received half of it. If this is not 275 # the case, then this is an indicator that either: 276 # - machines that are running this test are using buffer 277 # sizes greater than half of BIGFILE_SIZE; or 278 # - streaming is broken. 279 # 280 # At the time of writing, we seem to have encountered 281 # buffer sizes bigger than 512K, so we've increased 282 # BIGFILE_SIZE to 4MB. 283 if tell_position >= BIGFILE_SIZE: 284 if read_so_far < (BIGFILE_SIZE / 2): 285 # https://bitbucket.org/cherrypy/cherrypy/issue/1199 286 sys.stderr.write( 287 "The file should have advanced to position %r, but " 288 "has already advanced to the end of the file. It " 289 "may not be streamed as intended, or at the wrong " 290 "chunk size (64k)" % read_so_far) 291 sys.stderr.write('\n') 292 elif tell_position < read_so_far: 293 self.fail( 294 "The file should have advanced to position %r, but has " 295 "only advanced to position %r. It may not be streamed " 296 "as intended, or at the wrong chunk size (64k)" % 297 (read_so_far, tell_position)) 298 299 if body != ntob("x" * BIGFILE_SIZE): 300 self.fail("Body != 'x' * %d. Got %r instead (%d bytes)." % 301 (BIGFILE_SIZE, body[:50], len(body))) 302 conn.close()
303
304 - def test_file_stream_deadlock(self):
305 if cherrypy.server.protocol_version != "HTTP/1.1": 306 return self.skip() 307 308 self.PROTOCOL = "HTTP/1.1" 309 310 # Make an initial request but abort early. 311 self.persistent = True 312 conn = self.HTTP_CONN 313 conn.putrequest("GET", "/bigfile", skip_host=True) 314 conn.putheader("Host", self.HOST) 315 conn.endheaders() 316 response = conn.response_class(conn.sock, method="GET") 317 response.begin() 318 self.assertEqual(response.status, 200) 319 body = response.fp.read(65536) 320 if body != ntob("x" * len(body)): 321 self.fail("Body != 'x' * %d. Got %r instead (%d bytes)." % 322 (65536, body[:50], len(body))) 323 response.close() 324 conn.close() 325 326 # Make a second request, which should fetch the whole file. 327 self.persistent = False 328 self.getPage("/bigfile") 329 if self.body != ntob("x" * BIGFILE_SIZE): 330 self.fail("Body != 'x' * %d. Got %r instead (%d bytes)." % 331 (BIGFILE_SIZE, self.body[:50], len(body)))
332
333 - def test_error_page_with_serve_file(self):
334 self.getPage("/404test/yunyeen") 335 self.assertStatus(404) 336 self.assertInBody("I couldn't find that thing")
337
338 -def error_page_404(status, message, traceback, version):
339 import os.path 340 return static.serve_file(os.path.join(curdir, 'static', '404.html'), 341 content_type='text/html')
342