| OLD | NEW |
| 1 # Copyright 2012 The LUCI Authors. All rights reserved. | 1 # Copyright 2012 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
| 4 | 4 |
| 5 """This module defines Isolate Server frontend url handlers.""" | 5 """This module defines Isolate Server frontend url handlers.""" |
| 6 | 6 |
| 7 import datetime | 7 import datetime |
| 8 import json | 8 import json |
| 9 import logging |
| 9 import re | 10 import re |
| 10 | 11 |
| 11 import webapp2 | 12 import webapp2 |
| 12 | 13 |
| 13 from google.appengine.api import memcache | 14 from google.appengine.api import memcache |
| 15 from google.appengine.api import modules |
| 14 from google.appengine.api import users | 16 from google.appengine.api import users |
| 15 | 17 |
| 16 import acl | 18 import acl |
| 17 import config | 19 import config |
| 18 import gcs | 20 import gcs |
| 19 import handlers_endpoints_v1 | 21 import handlers_endpoints_v1 |
| 20 import mapreduce_jobs | 22 import mapreduce_jobs |
| 21 import model | 23 import model |
| 22 import stats | 24 import stats |
| 23 import template | 25 import template |
| (...skipping 198 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 222 if not raw_data: | 224 if not raw_data: |
| 223 stream = gcs.read_file(config.settings().gs_bucket, entity.key.id()) | 225 stream = gcs.read_file(config.settings().gs_bucket, entity.key.id()) |
| 224 else: | 226 else: |
| 225 stream = [raw_data] | 227 stream = [raw_data] |
| 226 content = ''.join(model.expand_content(namespace, stream)) | 228 content = ''.join(model.expand_content(namespace, stream)) |
| 227 | 229 |
| 228 self.response.headers['X-Frame-Options'] = 'SAMEORIGIN' | 230 self.response.headers['X-Frame-Options'] = 'SAMEORIGIN' |
| 229 # We delete Content-Type before storing to it to avoid having two (yes, | 231 # We delete Content-Type before storing to it to avoid having two (yes, |
| 230 # two) Content-Type headers. | 232 # two) Content-Type headers. |
| 231 del self.response.headers['Content-Type'] | 233 del self.response.headers['Content-Type'] |
| 234 |
| 232 # Apparently, setting the content type to text/plain encourages the | 235 # Apparently, setting the content type to text/plain encourages the |
| 233 # browser (Chrome, at least) to sniff the mime type and display | 236 # browser (Chrome, at least) to sniff the mime type and display |
| 234 # things like images. Images are autowrapped in <img> and text is | 237 # things like images. Images are autowrapped in <img> and text is |
| 235 # wrapped in <pre>. | 238 # wrapped in <pre>. |
| 236 self.response.headers['Content-Type'] = 'text/plain; charset=utf-8' | 239 self.response.headers['Content-Type'] = 'text/plain; charset=utf-8' |
| 237 self.response.headers['Content-Disposition'] = str('filename=%s' % digest) | 240 |
| 238 if content.startswith('{'): | 241 # App Engine puts a limit of 33554432 bytes on a request, which includes |
| 239 # Try to format as JSON. | 242 # headers. Headers are ~150 bytes. If the content + headers might |
| 240 try: | 243 # exceed that limit, we give the user an option to workround getting |
| 241 content = json.dumps( | 244 # their file. |
| 242 json.loads(content), sort_keys=True, indent=2, | 245 if len(content) > 33554000: |
| 243 separators=(',', ': ')) | 246 host = modules.get_hostname(module='default', version='default') |
| 244 # If we don't wrap this in html, browsers will put content in a pre | 247 # host is something like default.default.myisolateserver.appspot.com |
| 245 # tag which is also styled with monospace/pre-wrap. We can't use | 248 host = host.replace('default.default.','') |
| 246 # anchor tags in <pre>, so we force it to be a <div>, which happily | 249 sizeInMib = len(content) / (1024.0 * 1024.0) |
| 247 # accepts links. | 250 content = ('Sorry, your file is %1.1f MiB big, which exceeds the 32 MiB' |
| 248 content = ( | 251 ' App Engine limit.\nTo work around this, run the following command:\n' |
| 249 '<div style="font-family:monospace;white-space:pre-wrap;">%s</div>' | 252 ' python isolateserver.py download -I %s --namespace %s -f %s %s' |
| 250 % content) | 253 % (sizeInMib, host, namespace, digest, digest)) |
| 251 # Linkify things that look like hashes | 254 else: |
| 252 content = re.sub(r'([0-9a-f]{40})', | 255 self.response.headers['Content-Disposition'] = str('filename=%s' |
| 253 r'<a target="_blank" href="/browse?namespace=%s' % namespace + | 256 % digest) |
| 254 r'&digest=\1">\1</a>', | 257 if content.startswith('{'): |
| 255 content) | 258 # Try to format as JSON. |
| 256 self.response.headers['Content-Type'] = 'text/html; charset=utf-8' | 259 try: |
| 257 except ValueError: | 260 content = json.dumps( |
| 258 pass | 261 json.loads(content), sort_keys=True, indent=2, |
| 262 separators=(',', ': ')) |
| 263 # If we don't wrap this in html, browsers will put content in a pre |
| 264 # tag which is also styled with monospace/pre-wrap. We can't use |
| 265 # anchor tags in <pre>, so we force it to be a <div>, which happily |
| 266 # accepts links. |
| 267 content = ( |
| 268 '<div style="font-family:monospace;white-space:pre-wrap;">%s' |
| 269 '</div>' % content) |
| 270 # Linkify things that look like hashes |
| 271 content = re.sub(r'([0-9a-f]{40})', |
| 272 r'<a target="_blank" href="/browse?namespace=%s' % namespace + |
| 273 r'&digest=\1">\1</a>', |
| 274 content) |
| 275 self.response.headers['Content-Type'] = 'text/html; charset=utf-8' |
| 276 except ValueError: |
| 277 pass |
| 259 | 278 |
| 260 self.response.write(content) | 279 self.response.write(content) |
| 261 | 280 |
| 262 | 281 |
| 263 class StatsHandler(webapp2.RequestHandler): | 282 class StatsHandler(webapp2.RequestHandler): |
| 264 """Returns the statistics web page.""" | 283 """Returns the statistics web page.""" |
| 265 def get(self): | 284 def get(self): |
| 266 """Presents nice recent statistics. | 285 """Presents nice recent statistics. |
| 267 | 286 |
| 268 It fetches data from the 'JSON' API. | 287 It fetches data from the 'JSON' API. |
| (...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 386 def create_application(debug): | 405 def create_application(debug): |
| 387 """Creates the url router. | 406 """Creates the url router. |
| 388 | 407 |
| 389 The basic layouts is as follow: | 408 The basic layouts is as follow: |
| 390 - /restricted/.* requires being an instance administrator. | 409 - /restricted/.* requires being an instance administrator. |
| 391 - /stats/.* has statistics. | 410 - /stats/.* has statistics. |
| 392 """ | 411 """ |
| 393 acl.bootstrap() | 412 acl.bootstrap() |
| 394 template.bootstrap() | 413 template.bootstrap() |
| 395 return webapp2.WSGIApplication(get_routes(), debug=debug) | 414 return webapp2.WSGIApplication(get_routes(), debug=debug) |
| OLD | NEW |