def staticdir(section, dir, root="", match="", content_types=None, index="", generate_indexes=False): """Serve a static resource from the given (root +) dir. If 'match' is given, request.path_info will be searched for the given regular expression before attempting to serve static content. If content_types is given, it should be a Python dictionary of {file-extension: content-type} pairs, where 'file-extension' is a string (e.g. "gif") and 'content-type' is the value to write out in the Content-Type response header (e.g. "image/gif"). If 'index' is provided, it should be the (relative) name of a file to serve for directory requests. For example, if the dir argument is '/home/me', the Request-URI is 'myapp', and the index arg is 'index.html', the file '/home/me/myapp/index.html' will be sought. """ if cherrypy.request.method not in ('GET', 'HEAD'): return False if match and not re.search(match, cherrypy.request.path_info): return False # Allow the use of '~' to refer to a user's home directory. dir = os.path.expanduser(dir) # If dir is relative, make absolute using "root". if not os.path.isabs(dir): if not root: msg = "Static dir requires an absolute dir (or root)." raise ValueError(msg) dir = os.path.join(root, dir) # Determine where we are in the object tree relative to 'section' # (where the static tool was defined). if section == 'global': section = "/" section = section.rstrip(r"\/") branch = cherrypy.request.path_info[len(section) + 1:] branch = urllib.unquote(branch.lstrip(r"\/")) # If branch is "", filename will end in a slash filename = os.path.join(dir, branch) # There's a chance that the branch pulled from the URL might # have ".." or similar uplevel attacks in it. Check that the final # filename is a child of dir. if not os.path.normpath(filename).startswith(os.path.normpath(dir)): raise cherrypy.HTTPError(403) # Forbidden handled = _attempt(filename, content_types) if not handled: # Check for an index file if a folder was requested. if index: handled = _attempt(os.path.join(filename, index), content_types) if handled: cherrypy.request.is_index = filename[-1] in (r"\/") if not handled and generate_indexes: if callable(generate_indexes): handled = generate_indexes(filename, branch) else: handled = render_index(filename, branch) return handled
def index(self, **kwargs): """ Output a search result page. """ os = BaseSearcher.OpenSearch() os.log_request('search') if 'default_prefix' in kwargs: raise cherrypy.HTTPError( 400, 'Bad Request. Unknown parameter: default_prefix') if os.start_index > BaseSearcher.MAX_RESULTS: raise cherrypy.HTTPError( 400, 'Bad Request. Parameter start_index too high') sql = BaseSearcher.SQLStatement() sql.query = 'SELECT *' sql.from_ = ['v_appserver_books_4 as books'] # let derived classes prepare the query try: self.setup(os, sql) except ValueError as what: raise cherrypy.HTTPError(400, 'Bad Request. ' + str(what)) os.fix_sortorder() # execute the query try: BaseSearcher.SQLSearcher().search(os, sql) except DatabaseError as what: cherrypy.log("SQL Error: " + str(what), context='REQUEST', severity=logging.ERROR) raise cherrypy.HTTPError(400, 'Bad Request. Check your query.') # sync os.title and first entry header if os.entries: entry = os.entries[0] if os.title and not entry.header: entry.header = os.title elif entry.header and not os.title: os.title = entry.header os.template = os.page = 'results' # give derived class a chance to tweak result set self.fixup(os) # warn user about no records found if os.total_results == 0: self.nothing_found(os) # suggest alternate queries if os.total_results < 5: self.output_suggestions(os) # add sort by links if os.start_index == 1 and os.total_results > 1: if 'downloads' in os.alternate_sort_orders: self.sort_by_downloads(os) if 'release_date' in os.alternate_sort_orders: self.sort_by_release_date(os) if 'title' in os.alternate_sort_orders: self.sort_by_title(os) if 'alpha' in os.alternate_sort_orders: self.sort_alphabetically(os) if 'author' in os.alternate_sort_orders: self.sort_by_author(os) if 'quantity' in os.alternate_sort_orders: self.sort_by_quantity(os) os.finalize() self.finalize(os) if os.total_results > 0: # call this after finalize () os.entries.insert(0, self.status_line(os)) return self.format(os)
def find_acceptable_charset(self): request = cherrypy.serving.request response = cherrypy.serving.response if self.debug: cherrypy.log('response.stream %r' % response.stream, 'TOOLS.ENCODE') if response.stream: encoder = self.encode_stream else: encoder = self.encode_string if 'Content-Length' in response.headers: # Delete Content-Length header so finalize() recalcs it. # Encoded strings may be of different lengths from their # unicode equivalents, and even from each other. For example: # >>> t = u"\u7007\u3040" # >>> len(t) # 2 # >>> len(t.encode("UTF-8")) # 6 # >>> len(t.encode("utf7")) # 8 del response.headers['Content-Length'] # Parse the Accept-Charset request header, and try to provide one # of the requested charsets (in order of user preference). encs = request.headers.elements('Accept-Charset') charsets = [enc.value.lower() for enc in encs] if self.debug: cherrypy.log('charsets %s' % repr(charsets), 'TOOLS.ENCODE') if self.encoding is not None: # If specified, force this encoding to be used, or fail. encoding = self.encoding.lower() if self.debug: cherrypy.log('Specified encoding %r' % encoding, 'TOOLS.ENCODE') if (not charsets) or '*' in charsets or encoding in charsets: if self.debug: cherrypy.log('Attempting encoding %r' % encoding, 'TOOLS.ENCODE') if encoder(encoding): return encoding else: if not encs: if self.debug: cherrypy.log( 'Attempting default encoding %r' % self.default_encoding, 'TOOLS.ENCODE') # Any character-set is acceptable. if encoder(self.default_encoding): return self.default_encoding else: raise cherrypy.HTTPError( 500, self.failmsg % self.default_encoding) else: for element in encs: if element.qvalue > 0: if element.value == '*': # Matches any charset. Try our default. if self.debug: cherrypy.log( 'Attempting default encoding due ' 'to %r' % element, 'TOOLS.ENCODE') if encoder(self.default_encoding): return self.default_encoding else: encoding = element.value if self.debug: cherrypy.log( 'Attempting encoding %s (qvalue >' '0)' % element, 'TOOLS.ENCODE') if encoder(encoding): return encoding if '*' not in charsets: # If no "*" is present in an Accept-Charset field, then all # character sets not explicitly mentioned get a quality # value of 0, except for ISO-8859-1, which gets a quality # value of 1 if not explicitly mentioned. iso = 'iso-8859-1' if iso not in charsets: if self.debug: cherrypy.log('Attempting ISO-8859-1 encoding', 'TOOLS.ENCODE') if encoder(iso): return iso # No suitable encoding found. ac = request.headers.get('Accept-Charset') if ac is None: msg = 'Your client did not send an Accept-Charset header.' else: msg = 'Your client sent this Accept-Charset header: %s.' % ac _charsets = ', '.join(sorted(self.attempted_charsets)) msg += ' We tried these charsets: %s.' % (_charsets, ) raise cherrypy.HTTPError(406, msg)
def run(self, method, path, query_string, req_protocol, headers, rfile): r"""Process the Request. (Core) method, path, query_string, and req_protocol should be pulled directly from the Request-Line (e.g. "GET /path?key=val HTTP/1.0"). path This should be %XX-unquoted, but query_string should not be. When using Python 2, they both MUST be byte strings, not unicode strings. When using Python 3, they both MUST be unicode strings, not byte strings, and preferably not bytes \x00-\xFF disguised as unicode. headers A list of (name, value) tuples. rfile A file-like object containing the HTTP request entity. When run() is done, the returned object should have 3 attributes: * status, e.g. "200 OK" * header_list, a list of (name, value) tuples * body, an iterable yielding strings Consumer code (HTTP servers) should then access these response attributes to build the outbound stream. """ response = cherrypy.serving.response self.stage = 'run' try: self.error_response = cherrypy.HTTPError(500).set_response self.method = method path = path or '/' self.query_string = query_string or '' self.params = {} # Compare request and server HTTP protocol versions, in case our # server does not support the requested protocol. Limit our output # to min(req, server). We want the following output: # request server actual written supported response # protocol protocol response protocol feature set # a 1.0 1.0 1.0 1.0 # b 1.0 1.1 1.1 1.0 # c 1.1 1.0 1.0 1.0 # d 1.1 1.1 1.1 1.1 # Notice that, in (b), the response will be "HTTP/1.1" even though # the client only understands 1.0. RFC 2616 10.5.6 says we should # only return 505 if the _major_ version is different. rp = int(req_protocol[5]), int(req_protocol[7]) sp = int(self.server_protocol[5]), int(self.server_protocol[7]) self.protocol = min(rp, sp) response.headers.protocol = self.protocol # Rebuild first line of the request (e.g. "GET /path HTTP/1.0"). url = path if query_string: url += '?' + query_string self.request_line = '%s %s %s' % (method, url, req_protocol) self.header_list = list(headers) self.headers = httputil.HeaderMap() self.rfile = rfile self.body = None self.cookie = SimpleCookie() self.handler = None # path_info should be the path from the # app root (script_name) to the handler. self.script_name = self.app.script_name self.path_info = pi = path[len(self.script_name):] self.stage = 'respond' self.respond(pi) except self.throws: raise except Exception: if self.throw_errors: raise else: # Failure in setup, error handler or finalize. Bypass them. # Can't use handle_error because we may not have hooks yet. cherrypy.log(traceback=True, severity=40) if self.show_tracebacks: body = format_exc() else: body = '' r = bare_error(body) response.output_status, response.header_list, response.body = r if self.method == 'HEAD': # HEAD requests MUST NOT return a message-body in the response. response.body = [] try: cherrypy.log.access() except Exception: cherrypy.log.error(traceback=True) return response
def ssh_connect(hostname=None, username=None, password=None): if slycat.web.server.config["slycat-web-server"]["remote-authentication"][ "method"] != "certificate": ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(hostname=hostname, username=username, password=password) ssh.get_transport().set_keepalive(5) else: import requests import tempfile num_bits = 2056 # create the private key pvt_key = paramiko.RSAKey.generate(num_bits) # create the public key pub_key = "ssh-rsa " + pvt_key.get_base64() # SSO specific format # pub_key = "ssh-rsa " + pvt_key.get_base64() # + " " + principal + "\n" # General Format, principal is <username>@<hostname> cherrypy.log.error( "ssh_connect cert method, POST to sso-auth-server for user: %s" % cherrypy.request.login) r = requests.post(slycat.web.server.config["slycat-web-server"] ["sso-auth-server"]["url"], cert=(slycat.web.server.config["slycat-web-server"] ["ssl-certificate"]["cert-path"], slycat.web.server.config["slycat-web-server"] ["ssl-certificate"]["key-path"]), data='{"principal": "' + cherrypy.request.login + '", "pubkey": "' + pub_key + '"}', headers={"Content-Type": "application/json"}, verify=False) cherrypy.log.error("ssh_connect cert method, POST result: %s" % str(r)) # create a cert file obj # cert_file_object = tempfile.TemporaryFile().write(str(r.json()["certificate"])).seek(0) #this line crashes cert_file_object = tempfile.TemporaryFile() cert_file_object.write(str(r.json()["certificate"])) cert_file_object.seek(0) # create a key file obj key_file_object = tempfile.TemporaryFile() pvt_key.write_private_key(key_file_object) key_file_object.seek(0) # create the cert used for auth cert = paramiko.RSACert(privkey_file_obj=key_file_object, cert_file_obj=cert_file_object) ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) cherrypy.log.error( "ssh_connect cert method, calling ssh.connect for user: %s" % cherrypy.request.login) import traceback try: ssh.connect(hostname=hostname, username=cherrypy.request.login, pkey=cert, port=slycat.web.server.config["slycat-web-server"] ["remote-authentication"]["port"]) except paramiko.AuthenticationException as e: cherrypy.log.error( "ssh_connect cert method, authentication failed for %s@%s: %s" % (cherrypy.request.login, hostname, str(e))) cherrypy.log.error( "ssh_connect cert method, called ssh.connect traceback: %s" % traceback.print_exc()) raise cherrypy.HTTPError("403 Remote authentication failed.") ssh.get_transport().set_keepalive(5) cert_file_object.close() key_file_object.close() return ssh
def matches(self, n=0): n = int(n) event = self.getevent() datapath = 'data_' + event + '.db' self.database_exists(event) conn = sql.connect(datapath) cursor = conn.cursor() m = [] headers = {"X-TBA-App-Id": "frc2067:scouting-system:v01"} try: if n: # request a specific team m = requests.get( "http://www.thebluealliance.com/api/v2/team/frc{0}/event/{1}/matches" .format(n, event), params=headers) else: # get all the matches from this event m = requests.get( "http://www.thebluealliance.com/api/v2/event/{0}/matches". format(event), params=headers) if m.status_code == 400: raise cherrypy.HTTPError( 400, "Request rejected by The Blue Alliance.") with open(event + "_matches.json", "w+") as file: file.write(str(m.text)) m = m.json() except: try: with open(event + '_matches.json') as matches_data: m = json.load(matches_data) except: m = [] output = '' if 'feed' in m: raise cherrypy.HTTPError( 503, "Unable to retrieve data about this event.") # assign weights, so we can sort the matches for match in m: match['value'] = match['match_number'] type = match['comp_level'] if type == 'qf': match['value'] += 1000 elif type == 'sf': match['value'] += 2000 elif type == 'f': match['value'] += 3000 m = sorted(m, key=lambda k: k['value']) for match in m: if match['comp_level'] != 'qm': match['num'] = match['comp_level'].upper() + ' ' + str( match['match_number']) else: match['num'] = match['match_number'] output += ''' <tr> <td><a href="alliances?b1={1}&b2={2}&b3={3}&r1={4}&r2={5}&r3={6}">{0}</a></td> <td><a href="team?n={1}">{1}</a></td> <td><a href="team?n={2}">{2}</a></td> <td><a href="team?n={3}">{3}</a></td> <td><a href="team?n={4}">{4}</a></td> <td><a href="team?n={5}">{5}</a></td> <td><a href="team?n={6}">{6}</a></td> <td>{7}</td> <td>{8}</td> </tr> '''.format(match['num'], match['alliances']['blue']['teams'][0][3:], match['alliances']['blue']['teams'][1][3:], match['alliances']['blue']['teams'][2][3:], match['alliances']['red']['teams'][0][3:], match['alliances']['red']['teams'][1][3:], match['alliances']['red']['teams'][2][3:], match['alliances']['blue']['score'], match['alliances']['red']['score']) return ''' <html> <head> <title>PiScout</title> <link href="https://fonts.googleapis.com/css?family=Oxygen" rel="stylesheet" type="text/css"> <link href="/static/css/style.css" rel="stylesheet"> </head> <body> <h1>Matches{0}</h1> <h2><a style="color: #B20000" href='/'>PiScout Database</a></h2> <br><br> <table> <thead><tr> <th>Match</th> <th>Blue 1</th> <th>Blue 2</th> <th>Blue 3</th> <th>Red 1</th> <th>Red 2</th> <th>Red 3</th> <th>Blue Score</th> <th>Red Score</th> </tr></thead> <tbody> {1} </tbody> </table> </body> </html> '''.format(": {}".format(n) if n else "", output)
def POST(self, **kwargs): prefix = '[POST /submit/]' self.logger.debug(prefix) if not hasattr(cherrypy.request, "json"): self.logger.error(prefix + " No json data sent") raise cherrypy.HTTPError(400) data = cherrypy.request.json if 'metadata' not in data.keys(): self.logger.error(prefix + " No 'metadata' in json data") raise cherrypy.HTTPError(400) self.logger.debug( "----------------------- All Data JSON (Start) ------------------ " ) self.logger.debug( json.dumps( data, \ sort_keys=True, \ indent=4, \ separators=(',', ': ') ) ) self.logger.debug( "----------------------- All Data JSON (End ) ------------------ " ) data['metadata']['http_username'] = self._extract_http_username( cherrypy.request.headers['Authorization']) self.logger.debug(prefix + " Append to metadata 'http_username' = '" + data['metadata']['http_username'] + "'") # # Make sure we have all the metadata we need # rtn = self._validate_metadata(data['metadata']) if rtn is not None: return rtn # # Convert the phase # phase = self._convert_phase(data["metadata"]['phase']) if phase == self._phase_unknown: return self._return_error( prefix, -1, "%s An unknown phase (%s) was specified in the metadata" % (prefix, data["metadata"]["phase"])) self.logger.debug("Phase: %2d = [%s]" % (phase, data["metadata"]['phase'])) if 'data' not in data.keys(): self.logger.error(prefix + " No 'data' array in json data") raise cherrypy.HTTPError(400) rtn = {} # # Pushing data to the database # if 'data' in data.keys(): # # Get the submission id # The client could be submitting one they want us to use, # otherwise create a new one. # submit_info = {} if 'submit_id' in data['metadata'].keys( ) and data['metadata']['submit_id'] > 0: self.logger.debug("************** submit_id: Existing %s" % (str(data['metadata']['submit_id']))) submit_info = {'submit_id': data['metadata']['submit_id']} else: self.logger.debug("************** submit_id: New...") rtn = self._validate_submit(data['metadata']) if rtn is not None: return rtn submit_info = self._db.get_submit_id(data['metadata']) if "submit_id" not in submit_info.keys(): return self._return_error( prefix, -1, "%s Failed [%s]" % (prefix, submit_info['error_msg'])) # # Submit each entry to the database # ids = [] for entry in data['data']: value = None if phase is self._phase_mpi_install: rtn = self._validate_mpi_install(submit_info['submit_id'], data['metadata'], entry) if rtn is not None: return rtn value = self._db.insert_mpi_install( submit_info['submit_id'], data['metadata'], entry) elif phase is self._phase_test_build: rtn = self._validate_test_build(submit_info['submit_id'], data['metadata'], entry) if rtn is not None: return rtn value = self._db.insert_test_build( submit_info['submit_id'], data['metadata'], entry) elif phase is self._phase_test_run: rtn = self._validate_test_run(submit_info['submit_id'], data['metadata'], entry) if rtn is not None: return rtn value = self._db.insert_test_run(submit_info['submit_id'], data['metadata'], entry) else: self.logger.error("Unkown phase...") if value is None: #ids.append( {'error':'failed to submit this run'} ) return self._return_error( prefix, -1, "%s Failed to submit an entry (unknown reason)" % (prefix)) elif 'error_msg' in value.keys(): return self._return_error(prefix, -2, value['error_msg']) else: ids.append(value) # # Return the ids for each of those submissions # rtn['ids'] = ids rtn['submit_id'] = submit_info['submit_id'] # # Pulling data from the database # elif 'query' in data.keys(): # # Execute query # table_name = data['query']['table_name'] if 'filters' in data['query'].keys(): filters = [ f['filter_left'] + " " + f['filter_mid'] + " " + f['filter_right'] for f in data['query']['filters'] ] else: filters = [] if 'row_id' in data['query'].keys(): row_id = data['query']['row_id'] else: row_id = None if 'fields' in data['query'].keys(): fields = data['query']['fields'] else: fields = None query_data = self._db._query(table_name, filters=filters, row_id=row_id, fields=fields) if query_data is None: self.logger.error(prefix) return self._return_error(prefix, -1, "%s Query Failed" % (prefix)) rtn['query_data'] = query_data rtn['status'] = 0 rtn['status_message'] = 'Success' self.logger.debug( "----------------------- Return Values JSON (Start) ------------------ " ) self.logger.debug( json.dumps( rtn, \ sort_keys=True, \ indent=4, \ separators=(',', ': ') ) ) self.logger.debug( "----------------------- Return Values JSON (End ) ------------------ " ) return rtn
def raise500(): raise cherrypy.HTTPError(500)
def error(self, code=500): raise cherrypy.HTTPError(code)
class Collection(object): """ A Collection is a container for Resource objects. To create a new Collection type, subclass this and make the following changes to the child class: - Set self.resource to the type of Resource that this Collection contains - Set self.resource_args. This can remain an empty list if the Resources can be initialized with only one identifier. Otherwise, include additional values as needed (eg. to identify a parent resource). - Set self.model_args. Similar to above, this is needed only if the model needs additional information to identify this Collection. - Implement the base operations of 'create' and 'get_list' in the model. """ def __init__(self, model): self.model = model self.resource = Resource self.resource_args = [] self.model_args = [] def create(self, params, *args): try: create = getattr(self.model, model_fn(self, 'create')) except AttributeError: e = InvalidOperation('KCHAPI0005E', {'resource': get_class_name(self)}) raise cherrypy.HTTPError(405, e.message) validate_params(params, self, 'create') args = self.model_args + [params] name = create(*args) cherrypy.response.status = 201 args = self.resource_args + [name] res = self.resource(self.model, *args) return res.get() def _get_resources(self, flag_filter): try: get_list = getattr(self.model, model_fn(self, 'get_list')) idents = get_list(*self.model_args, **flag_filter) res_list = [] for ident in idents: # internal text, get_list changes ident to unicode for sorted args = self.resource_args + [ident] res = self.resource(self.model, *args) res.lookup() res_list.append(res) return res_list except AttributeError: return [] def _cp_dispatch(self, vpath): if vpath: ident = vpath.pop(0) ident = urllib2.unquote(ident) # incoming text, from URL, is not unicode, need decode args = self.resource_args + [ident.decode("utf-8")] return self.resource(self.model, *args) def filter_data(self, resources, fields_filter): data = [] for res in resources: if all(key in res.data and res.data[key] == val for key, val in fields_filter.iteritems()): data.append(res.data) return data def get(self, filter_params): def _split_filter(params): flag_filter = dict() fields_filter = params for key, val in params.items(): if key.startswith('_'): flag_filter[key] = fields_filter.pop(key) return flag_filter, fields_filter flag_filter, fields_filter = _split_filter(filter_params) resources = self._get_resources(flag_filter) data = self.filter_data(resources, fields_filter) return kimchi.template.render(get_class_name(self), data) @cherrypy.expose def index(self, *args, **kwargs): method = validate_method(('GET', 'POST')) try: if method == 'GET': filter_params = cherrypy.request.params validate_params(filter_params, self, 'get_list') return self.get(filter_params) elif method == 'POST': return self.create(parse_request(), *args) except InvalidOperation, e: raise cherrypy.HTTPError(400, e.message) except InvalidParameter, e: raise cherrypy.HTTPError(400, e.message)
def serve_raw_file(self, file, content_type=None, disposition=None, name=None): # Adapted from CherryPy's serve_file(), modified to work with file-like # objects response = cherrypy.response st = None if isinstance(file, str): path = file if not os.path.isabs(path): raise ValueError("'%s' is not an absolute path." % path) try: st = os.stat(path) except OSError: raise cherrypy.NotFound() if stat.S_ISDIR(st.st_mode): raise cherrypy.NotFound() response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) cptools.validate_since() file = open(path, "rb") else: path = getattr(file, "name", None) if path: try: st = os.stat(path) except OSError: pass else: if not hasattr(file, "read"): raise ValueError( "Expected a file-like object, got %r instead " "(object has no read() method)" % file) if not hasattr(file, "seek"): raise ValueError("Can't serve file-like object %r " "(object has no seek() method)" % file) if not hasattr(file, "tell"): raise ValueError("Can't serve file-like object %r " "(object has no tell() method)" % file) # Set the content type if content_type is None: if path: content_type = mimetypes.guess_type(path)[0] if not content_type: content_type = "text/plain" response.headers["Content-Type"] = content_type # Set the content disposition if disposition is not None: cd = disposition if not name and path: name = os.path.basename(path) if name: cd = rfc6266.build_header(name, cd) response.headers["Content-Disposition"] = cd if self.use_xsendfile and path: response.headers["X-Sendfile"] = path return "" # Find the size of the file if st is None: start = file.tell() file.seek(0, 2) # Move to the end of the file c_len = file.tell() - start file.seek(start) else: c_len = st.st_size # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code if cherrypy.request.protocol >= (1, 1): response.headers["Accept-Ranges"] = "bytes" r = httputil.get_ranges(cherrypy.request.headers.get('Range'), c_len) if r == []: response.headers['Content-Range'] = "bytes */%s" % c_len message = "Invalid Range (first-byte-pos greater than Content-Length)" raise cherrypy.HTTPError(416, message) if r: if len(r) == 1: # Return a single-part response. start, stop = r[0] if stop > c_len: stop = c_len r_len = stop - start response.status = "206 Partial Content" response.headers['Content-Range'] = ( "bytes %s-%s/%s" % (start, stop - 1, c_len)) response.headers['Content-Length'] = r_len file.seek(start) response.body = file_generator_limited(file, r_len) else: # Return a multipart/byteranges response. response.status = "206 Partial Content" import mimetools boundary = mimetools.choose_boundary() ct = "multipart/byteranges; boundary=%s" % boundary response.headers['Content-Type'] = ct if "Content-Length" in response.headers: # Delete Content-Length header so finalize() recalcs it. del response.headers["Content-Length"] def file_ranges(): # Apache compatibility: yield "\r\n" for start, stop in r: yield "--" + boundary yield "\r\nContent-type: %s" % content_type yield ( "\r\nContent-range: bytes %s-%s/%s\r\n\r\n" % (start, stop - 1, c_len)) file.seek(start) for chunk in file_generator_limited( file, stop - start): yield chunk yield "\r\n" # Final boundary yield "--" + boundary + "--" # Apache compatibility: yield "\r\n" response.body = file_ranges() else: response.headers['Content-Length'] = c_len response.body = file else: response.headers['Content-Length'] = c_len response.body = file return response.body
self._redirect(ident) uri_params = [] for arg in self.model_args: if arg is None: arg = '' uri_params.append( urllib2.quote(arg.encode('utf-8'), safe="")) raise internal_redirect(self.uri_fmt % tuple(uri_params)) except MissingParameter, e: raise cherrypy.HTTPError(400, e.message) except InvalidParameter, e: raise cherrypy.HTTPError(400, e.message) except InvalidOperation, e: raise cherrypy.HTTPError(400, e.message) except OperationFailed, e: raise cherrypy.HTTPError(500, e.message) except NotFoundError, e: raise cherrypy.HTTPError(404, e.message) except KimchiException, e: raise cherrypy.HTTPError(500, e.message) wrapper.__name__ = action_name wrapper.exposed = True return wrapper def lookup(self): try: lookup = getattr(self.model, model_fn(self, 'lookup')) self.info = lookup(*self.model_args) except AttributeError: self.info = {}
def get_plot_cmp(self, dbName1='', acc1='', tag1='', since1='1', fileType1='png', png1='', dbName2='', acc2='', tag2='', since2='1', fileType2='png', png2='', type='3', istkmap='1'): self.check_dbName_acc(dbName1, acc1, since1) self.check_dbName_acc(dbName2, acc2, since2) if fileType1 != "png" and fileType2 != "png": cherrypy.HTTPError(405, "Bad file type !!!!!") tmpdb = self.masker.unmask_dbname(dbName1) db = av.get_validated_dbname(value=tmpdb, acc=self.masker.unmask_schema( tmpdb, acc1)) vtag = av.get_validated_tag(dbName=db, value=tag1) vsince = av.get_validated_since(value=since1.strip(), db=db, tag=vtag, onlyone=True) plot = SubdetectorFactory.getPlotInstance(dbName=db, tag=vtag, since=vsince, fileType=fileType1, directory=self.__plotsdir, image=png1) img1 = plot.get(get_fname=True) tmpdb = self.masker.unmask_dbname(dbName2) db = av.get_validated_dbname(value=tmpdb, acc=self.masker.unmask_schema( tmpdb, acc2)) vtag = av.get_validated_tag(dbName=db, value=tag2) vsince = av.get_validated_since(value=since2.strip(), db=db, tag=vtag, onlyone=True) plot = SubdetectorFactory.getPlotInstance(dbName=db, tag=vtag, since=vsince, fileType=fileType2, directory=self.__plotsdir, image=png2) img2 = plot.get(get_fname=True) #raise Exception('asdasd'+img2) '''if type == '1': img3 = ImageChops.subtract(img1, img2, scale=1, offset=128) elif type == '2': img3 = ImageChops.subtract(img2, img1, scale=1, offset=128) elif type == '3': img3 = ImageChops.difference(img1, img2) elif type == '4': img3 = ImageChops.subtract(img1, img2, scale=1, offset=0) else: img3 = ImageChops.subtract(img2, img1, scale=1, offset=0) ''' if not os.path.isdir(self.__tmpdir): os.makedirs(self.__tmpdir) temp = tempfile.TemporaryFile(dir=self.__tmpdir) plotTxt = "" if re.search('strip', vtag.lower()): plotTxt = 'ABSOLUTE DIFFERENCE (The more lighter colors, the higher the difference.)' cmpPNG.CmpTrackerDiff(fileName1=img1, fileName2=img2, result=temp, txt=plotTxt, debug=False) #img3.save(temp, 'png') temp.seek(0) cherrypy.response.headers['Content-Type'] = 'image/' + fileType1 data = temp.read() temp.close() return data
def get_plot(self, dbName='', acc='', tag='', since='1', fileType='png', png='', test=None): self.check_dbName_acc(dbName, acc, since) if fileType != "png": cherrypy.HTTPError(405, "Bad file type !!!!!") '''Returns plot image (changes response header content-type) All arguments in get_plot method have default value equal to '' just for avoiding exception if some parameter is missing. For testing: http://HOSTNAME:PORT/get_plot?dbName=oracle://cms_orcoff_prod/CMS_COND_31X_ECAL&tag=EcalIntercalibConstants_EBg50_EEnoB&since=1&fileType=root ''' #try: #ArgumentValidator.validateArgs(dbName = dbName, tag = tag, since = since, onesince = True) #c = readXML() #db = str(c.dbMap_reverse[dbName]+"/CMS_COND_"+acc) connectionString = getFrontierConnectionString(acc, dbName) shortConnectionString = getFrontierConnectionString(acc, dbName, short=True) vtag = str(tag) vsince = av.get_validated_since(value=since.strip(), db=connectionString, tag=vtag, onlyone=True) plot = SubdetectorFactory.getPlotInstance( dbName=connectionString, tag=vtag, since=vsince, fileType=fileType, directory=self.__plotsdir, image=png, shortName=shortConnectionString) plotData = plot.get() #return plotData '''except ValueError, e: return 'Wrong parameter value. ERROR returned: %s' % str(e) except TypeError, e: return 'Wrong parameter type. ERROR returned: %s' % str(e) except IOError, e: return 'Could not access file %s. ERROR returned: %s' % (self.__plotsdir, e) except AttributeError, e: return 'Couldn\t generate plot instance. ERROR returned: %s' % str(e) except RuntimeError,e: return 'Error connecting to DB. ERROR returned: %s' % str(e) except Exception, e: return 'Plot is not implemented for this tag. ERROR returned: %s ' % str(e) else: if (fileType == 'root'): cherrypy.response.headers['Content-Disposition'] = \ 'attachment;filename=' + plot.get_name() cherrypy.response.headers['Content-Type'] = 'text/plain' else:''' cherrypy.response.headers['Content-Type'] = 'image/' + fileType return plotData
def team(self, n="2067"): if not n.isdigit(): raise cherrypy.HTTPRedirect('/') if int(n) == 666: raise cherrypy.HTTPError( 403, 'Satan has commanded me to not disclose his evil strategy secrets.' ) conn = sql.connect(self.datapath()) cursor = conn.cursor() entries = cursor.execute( 'SELECT * FROM scout WHERE d0=? ORDER BY d1 DESC', (n, )).fetchall() averages = cursor.execute('SELECT * FROM averages WHERE team=?', (n, )).fetchall() assert len( averages) < 2 # ensure there aren't two entries for one team if len(averages): s = averages[0] print(s) else: s = [0] * 7 # generate zeros if no data exists for the team yet comments = cursor.execute('SELECT * FROM comments WHERE team=?', (n, )).fetchall() conn.close() # Generate html for comments section commentstr = '' #for comment in comments: # commentstr += '<div class="commentbox"><p>{}</p></div>'.format(comment[1]) # Iterate through all the data entries and generate some text to go in the main table # this entire section will need to change from year to year output = '' dataset = [] for e in entries: # Important: the index of e refers to the number of the field set in main.py # For example e[1] gets value #1 from main.py # Important: the index of e refers to the number of the field set in main.py # For example e[1] gets value #0 from main.py dp = { "match": e[1], "autoswitch": 0, "autoscale": 0, "teleswitch": 0, "telescale": 0, "teleexch": 0, "teledrop": 0 } a = '' a += 'baseline, ' if e[6] else '' dp['autoswitch'] += e[2] a += str(e[2]) + 'x switch, ' if e[2] else '' a += str(e[3]) + 'x scale, ' if e[3] else '' a += str(e[4]) + 'x exch, ' if e[4] else '' a += str(e[5]) + 'x dropped, ' if e[5] else '' dp['autoscale'] += e[3] d = '' d += str(e[7]) + 'x switch, ' if e[7] else '' d += str(e[11]) + 'x opp switch, ' if e[11] else '' d += str(e[8]) + 'x scale, ' if e[8] else '' d += str(e[9]) + 'x exch, ' if e[9] else '' d += str(e[10]) + 'x drop ' if e[10] else '' dp['teleswitch'] = e[7] dp['telescale'] += e[8] dp['teleexch'] += e[9] dp["teledrop"] += e[10] end = '' end += 'climb, ' if e[12] else '' end += 'park ' if e[13] else '' end += 'climbed bot ' if e[14] else '' o = '' o += 'defense, ' if e[15] else '' o += 'defended,' if e[16] else '' c = '' c += e[17] # Generate a row in the table for each match output += ''' <tr {6}> <td>{0}</td> <td>{1}</td> <td>{2}</td> <td>{3}</td> <td>{4}</td> <td>{5}</td> <td><a class="flag" href="javascript:flag({6}, {7});">X</a></td> </tr>'''.format(e[1], a, d, end, o, c, 'style="color: #B20000"' if e[18] else '', e[1], e[18]) for key, val in dp.items(): dp[key] = round(val, 2) if not e[18]: # if flagged dataset.append( dp ) # add it to dataset, which is an array of data that is fed into the graphs dataset.reverse() # reverse so that graph is in the correct order # Grab the image from the blue alliance imcode = '' headers = {"X-TBA-App-Id": "frc2067:scouting-system:v01"} m = [] try: # get the picture for a given team m = self.get( "http://www.thebluealliance.com/api/v2/team/frc{0}/media". format(n), params=headers).json() if m.status_code == 400: m = [] except: pass # swallow the error lol for media in m: if media[ 'type'] == 'imgur': # check if there's an imgur image on TBA imcode = '''<br> <div style="text-align: center"> <p style="font-size: 32px; line-height: 0em;">Image</p> <img src=http://i.imgur.com/{}.jpg></img> </div>'''.format(media['foreign_key']) break if media['type'] == 'cdphotothread': imcode = '''<br> <div style="text-align: center"> <p style="font-size: 32px; line-height: 0em;">Image</p> <img src=http://chiefdelphi.com/media/img/{}></img> </div>'''.format(media['details']['image_partial'].replace( '_l', '_m')) break # Every year, update the labels for the graphs. The data will come from the variable dataset # Then update all the column headers and stuff return ''' <html> <head> <title>{0} | PiScout</title> <link href="https://fonts.googleapis.com/css?family=Oxygen" rel="stylesheet" type="text/css"> <link href="/static/css/style.css" rel="stylesheet"> <script type="text/javascript" src="/static/js/amcharts.js"></script> <script type="text/javascript" src="/static/js/serial.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script> if (typeof jQuery === 'undefined') document.write(unescape('%3Cscript%20src%3D%22/static/js/jquery.js%22%3E%3C/script%3E')); </script> <script> var chart; var graph; var chartData = {9}; AmCharts.ready(function () {{ // SERIAL CHART chart = new AmCharts.AmSerialChart(); chart.dataProvider = chartData; chart.marginLeft = 10; chart.categoryField = "match"; // AXES // category var categoryAxis = chart.categoryAxis; categoryAxis.dashLength = 3; categoryAxis.minorGridEnabled = true; categoryAxis.minorGridAlpha = 0.1; // value var valueAxis = new AmCharts.ValueAxis(); valueAxis.position = "left"; valueAxis.axisColor = "#111111"; valueAxis.gridAlpha = 0; valueAxis.axisThickness = 2; chart.addValueAxis(valueAxis) var valueAxis2 = new AmCharts.ValueAxis(); valueAxis2.position = "right"; valueAxis2.axisColor = "#FCD202"; valueAxis2.gridAlpha = 0; valueAxis2.axisThickness = 2; chart.addValueAxis(valueAxis2); // GRAPH graph = new AmCharts.AmGraph(); graph.title = "Auto Switch"; graph.valueAxis = valueAxis; graph.type = "smoothedLine"; // this line makes the graph smoothed line. graph.lineColor = "#637bb6"; graph.bullet = "round"; graph.bulletSize = 8; graph.bulletBorderColor = "#FFFFFF"; graph.bulletBorderAlpha = 1; graph.bulletBorderThickness = 2; graph.lineThickness = 2; graph.valueField = "autoswitch"; graph.balloonText = "Auto Switch:<br><b><span style='font-size:14px;'>[[value]]</span></b>"; chart.addGraph(graph); graph2 = new AmCharts.AmGraph(); graph2.title = "Auto Scale"; graph2.valueAxis = valueAxis2; graph2.type = "smoothedLine"; // this line makes the graph smoothed line. graph2.lineColor = "#187a2e"; graph2.bullet = "round"; graph2.bulletSize = 8; graph2.bulletBorderColor = "#FFFFFF"; graph2.bulletBorderAlpha = 1; graph2.bulletBorderThickness = 2; graph2.lineThickness = 2; graph2.valueField = "autoscale"; graph2.balloonText = "Auto Scale:<br><b><span style='font-size:14px;'>[[value]]</span></b>"; chart.addGraph(graph2); graph3 = new AmCharts.AmGraph(); graph3.title = "Tele Switch"; graph3.valueAxis = valueAxis; graph3.type = "smoothedLine"; // this line makes the graph smoothed line. graph3.lineColor = "#FF6600"; graph3.bullet = "round"; graph3.bulletSize = 8; graph3.bulletBorderColor = "#FFFFFF"; graph3.bulletBorderAlpha = 1; graph3.bulletBorderThickness = 2; graph3.lineThickness = 2; graph3.valueField = "teleswitch"; graph3.balloonText = "Tele Switch:<br><b><span style='font-size:14px;'>[[value]]</span></b>"; chart.addGraph(graph3); graph4 = new AmCharts.AmGraph(); graph4.valueAxis = valueAxis2; graph4.title = "Tele Exchange"; graph4.type = "smoothedLine"; // this line makes the graph smoothed line. graph4.lineColor = "#FCD202"; graph4.bullet = "round"; graph4.bulletSize = 8; graph4.bulletBorderColor = "#FFFFFF"; graph4.bulletBorderAlpha = 1; graph4.bulletBorderThickness = 2; graph4.lineThickness = 2; graph4.valueField = "teleexch"; graph4.balloonText = "Tele Exchange:<br><b><span style='font-size:14px;'>[[value]]</span></b>"; chart.addGraph(graph4); graph5 = new AmCharts.AmGraph(); graph5.valueAxis = valueAxis2; graph5.title = "Dropped Gears"; graph5.type = "smoothedLine"; // this line makes the graph smoothed line. graph5.lineColor = "#FF0000"; graph5.bullet = "round"; graph5.bulletSize = 8; graph5.bulletBorderColor = "#FFFFFF"; graph5.bulletBorderAlpha = 1; graph5.bulletBorderThickness = 2; graph5.lineThickness = 2; graph5.valueField = "cubedrop"; graph5.balloonText = "Dropped Cubes:<br><b><span style='font-size:14px;'>[[value]]</span></b>"; chart.addGraph(graph5); graph6 = new AmCharts.AmGraph(); graph6.valueAxis = valueAxis2; graph6.title = "Tele Scale"; graph6.type = "smoothedLine"; // this line makes the graph smoothed line. graph6.lineColor = "#FF2200"; graph6.bullet = "round"; graph6.bulletSize = 8; graph6.bulletBorderColor = "#FFFFFF"; graph6.bulletBorderAlpha = 1; graph6.bulletBorderThickness = 2; graph6.lineThickness = 2; graph6.valueField = "telescale"; graph6.balloonText = "Tele Scale:<br><b><span style='font-size:14px;'>[[value]]</span></b>"; chart.addGraph(graph6); // CURSOR var chartCursor = new AmCharts.ChartCursor(); chartCursor.cursorAlpha = 0; chartCursor.cursorPosition = "mouse"; chart.addChartCursor(chartCursor); var legend = new AmCharts.AmLegend(); legend.marginLeft = 110; legend.useGraphSettings = true; chart.addLegend(legend); chart.creditsPosition = "bottom-right"; // WRITE chart.write("chartdiv"); }}); function flag(m, f) {{ $.post( "flag", {{num: {0}, match: m, flagval: f}} ); window.location.reload(true); }} </script> </head> <body> <h1 class="big">Team {0}</h1> <h2><a style="color: #B20000" href='/'>PiScout Database</a></h2> <br><br> <div style="text-align:center;"> <div id="apr"> <p class="statbox" style="font-weight:bold; font-size:100%">Average Total:</p> <p style="font-size: 400%; line-height: 0em">{2}</p> </div> <div id="stats"> <p class="statbox" style="font-weight:bold">Average Match:</p> <p class="statbox">Auto Switch: {3}</p> <p class="statbox">Auto Scale: {4}</p> <p class="statbox">Tele Switch: {5}</p> <p class="statbox">Tele Scale: {6}</p> <p class="statbox">Tele Exchange: {7}</p> <p class="statbox">Endgame Points: {8}</p> </div> </div> <br> <div id="chartdiv" style="width:1000px; height:400px; margin: 0 auto;"></div> <br> <table> <thead><tr> <th>Match</th> <th>Auto</th> <th>Teleop</th> <th>Endgame</th> <th>Other</th> <th>Comments</th> <th>Flag</th> </tr></thead>{1} </table> {10} <br> <br> <p style="text-align: center; font-size: 24px"><a href="/matches?n={0}">View this team's match schedule</a></p> </body> </html>'''.format(n, output, s[2], s[3], s[4], s[5], s[6], s[7], s[8], str(dataset).replace("'", '"'), imcode, commentstr)
def error(self, code): # raise an error based on the get query raise cherrypy.HTTPError( "403 Forbidden", "You are not allowed to access this resource.")
def alliances(self, b1='', b2='', b3='', r1='', r2='', r3=''): nums = [b1, b2, b3, r1, r2, r3] averages = [] conn = sql.connect(self.datapath()) cursor = conn.cursor() # start a div table for the comparison # to later be formatted with sum APR apr = [] output = '' # iterate through all six teams for i, n in enumerate(nums): # at halfway pointm switch to the second row if not n.isdigit(): raise cherrypy.HTTPError( 400, "You fool! Enter six valid team numbers!") average = cursor.execute('SELECT * FROM averages WHERE team=?', (n, )).fetchall() assert len(average) < 2 if len(average): entry = average[0] else: entry = [0] * 7 apr.append(entry[1]) output += '''<div style="text-align:center; display: inline-block; margin: 16px;"> <p><a href="/team?n={0}" style="font-size: 32px; line-height: 0em;">Team {0}</a></p> <div id="apr"> <p style="font-size: 100%; margin: 0.65em; line-height: 0.1em">Average Cubes</p> <p style="font-size: 400%; line-height: 0em">{7}</p> </div> <div id="stats"> <p class="statbox" style="font-weight:bold">Match Averages:</p> <p class="statbox">Auto Switch: {1}</p> <p class="statbox">Auto Scale: {2}</p> <p class="statbox">Tele Switch: {3}</p> <p class="statbox">Tele Scale: {4}</p> <p class="statbox">Tele Exch: {5}</p> <p class="statbox">Endgame: {6}</p> </div> </div>'''.format(n, entry[3], entry[4], entry[5], entry[6], entry[7], entry[8], round(entry[2], 1)) # unpack the elements output += "</div></div>" prob_red = 1 / (1 + math.e**(-0.08099 * (sum(apr[3:6]) - sum(apr[0:3]))) ) # calculates win probability from 2016 data output = output.format(sum(apr[0:3]), sum(apr[3:6]), round((1 - prob_red) * 100, 1), round(prob_red * 100, 1)) output = output.format() conn.close() return ''' <html> <head> <title>PiScout</title> <link href="https://fonts.googleapis.com/css?family=Oxygen" rel="stylesheet" type="text/css"> <link href="/static/css/style.css" rel="stylesheet"> </head> <body> <h1 class="big">Compare Alliances</h1> <h2><a style="color: #B20000" href='/'>PiScout Database</a></h2> <br><br> <div style="margin: 0 auto; text-align: center; max-width: 1000px;"> {0} <br><br><br> </div> </body> </html>'''.format(output)
def PUT(self, id): enable_crossdomain() raise cherrypy.HTTPError(405, "Not allowed.")
def adduserform(self): if self.checkauth(returntopage=True) != 'admin': raise cherrypy.HTTPError( "403 Forbidden", "Only admin allowed to access this resource.") return logon.Logon.base_page % '''
def error(self, code): # raise an error based on the get query raise cherrypy.HTTPError(status=code)
class Request(object): """An HTTP request. This object represents the metadata of an HTTP request message; that is, it contains attributes which describe the environment in which the request URL, headers, and body were sent (if you want tools to interpret the headers and body, those are elsewhere, mostly in Tools). This 'metadata' consists of socket data, transport characteristics, and the Request-Line. This object also contains data regarding the configuration in effect for the given URL, and the execution plan for generating a response. """ prev = None """ The previous Request object (if any). This should be None unless we are processing an InternalRedirect.""" # Conversation/connection attributes local = httputil.Host('127.0.0.1', 80) 'An httputil.Host(ip, port, hostname) object for the server socket.' remote = httputil.Host('127.0.0.1', 1111) 'An httputil.Host(ip, port, hostname) object for the client socket.' scheme = 'http' """ The protocol used between client and server. In most cases, this will be either 'http' or 'https'.""" server_protocol = 'HTTP/1.1' """ The HTTP version for which the HTTP server is at least conditionally compliant.""" base = '' """The (scheme://host) portion of the requested URL. In some cases (e.g. when proxying via mod_rewrite), this may contain path segments which cherrypy.url uses when constructing url's, but which otherwise are ignored by CherryPy. Regardless, this value MUST NOT end in a slash.""" # Request-Line attributes request_line = '' """ The complete Request-Line received from the client. This is a single string consisting of the request method, URI, and protocol version (joined by spaces). Any final CRLF is removed.""" method = 'GET' """ Indicates the HTTP method to be performed on the resource identified by the Request-URI. Common methods include GET, HEAD, POST, PUT, and DELETE. CherryPy allows any extension method; however, various HTTP servers and gateways may restrict the set of allowable methods. CherryPy applications SHOULD restrict the set (on a per-URI basis).""" query_string = '' """ The query component of the Request-URI, a string of information to be interpreted by the resource. The query portion of a URI follows the path component, and is separated by a '?'. For example, the URI 'http://www.cherrypy.org/wiki?a=3&b=4' has the query component, 'a=3&b=4'.""" query_string_encoding = 'utf8' """ The encoding expected for query string arguments after % HEX HEX decoding). If a query string is provided that cannot be decoded with this encoding, 404 is raised (since technically it's a different URI). If you want arbitrary encodings to not error, set this to 'Latin-1'; you can then encode back to bytes and re-decode to whatever encoding you like later. """ protocol = (1, 1) """The HTTP protocol version corresponding to the set of features which should be allowed in the response. If BOTH the client's request message AND the server's level of HTTP compliance is HTTP/1.1, this attribute will be the tuple (1, 1). If either is 1.0, this attribute will be the tuple (1, 0). Lower HTTP protocol versions are not explicitly supported.""" params = {} """ A dict which combines query string (GET) and request entity (POST) variables. This is populated in two stages: GET params are added before the 'on_start_resource' hook, and POST params are added between the 'before_request_body' and 'before_handler' hooks.""" # Message attributes header_list = [] """ A list of the HTTP request headers as (name, value) tuples. In general, you should use request.headers (a dict) instead.""" headers = httputil.HeaderMap() """ A dict-like object containing the request headers. Keys are header names (in Title-Case format); however, you may get and set them in a case-insensitive manner. That is, headers['Content-Type'] and headers['content-type'] refer to the same value. Values are header values (decoded according to :rfc:`2047` if necessary). See also: httputil.HeaderMap, httputil.HeaderElement.""" cookie = SimpleCookie() """See help(Cookie).""" rfile = None """ If the request included an entity (body), it will be available as a stream in this attribute. However, the rfile will normally be read for you between the 'before_request_body' hook and the 'before_handler' hook, and the resulting string is placed into either request.params or the request.body attribute. You may disable the automatic consumption of the rfile by setting request.process_request_body to False, either in config for the desired path, or in an 'on_start_resource' or 'before_request_body' hook. WARNING: In almost every case, you should not attempt to read from the rfile stream after CherryPy's automatic mechanism has read it. If you turn off the automatic parsing of rfile, you should read exactly the number of bytes specified in request.headers['Content-Length']. Ignoring either of these warnings may result in a hung request thread or in corruption of the next (pipelined) request. """ process_request_body = True """ If True, the rfile (if any) is automatically read and parsed, and the result placed into request.params or request.body.""" methods_with_bodies = ('POST', 'PUT', 'PATCH') """ A sequence of HTTP methods for which CherryPy will automatically attempt to read a body from the rfile. If you are going to change this property, modify it on the configuration (recommended) or on the "hook point" `on_start_resource`. """ body = None """ If the request Content-Type is 'application/x-www-form-urlencoded' or multipart, this will be None. Otherwise, this will be an instance of :class:`RequestBody<cherrypy._cpreqbody.RequestBody>` (which you can .read()); this value is set between the 'before_request_body' and 'before_handler' hooks (assuming that process_request_body is True).""" # Dispatch attributes dispatch = cherrypy.dispatch.Dispatcher() """ The object which looks up the 'page handler' callable and collects config for the current request based on the path_info, other request attributes, and the application architecture. The core calls the dispatcher as early as possible, passing it a 'path_info' argument. The default dispatcher discovers the page handler by matching path_info to a hierarchical arrangement of objects, starting at request.app.root. See help(cherrypy.dispatch) for more information.""" script_name = '' """ The 'mount point' of the application which is handling this request. This attribute MUST NOT end in a slash. If the script_name refers to the root of the URI, it MUST be an empty string (not "/"). """ path_info = '/' """ The 'relative path' portion of the Request-URI. This is relative to the script_name ('mount point') of the application which is handling this request.""" login = None """ When authentication is used during the request processing this is set to 'False' if it failed and to the 'username' value if it succeeded. The default 'None' implies that no authentication happened.""" # Note that cherrypy.url uses "if request.app:" to determine whether # the call is during a real HTTP request or not. So leave this None. app = None """The cherrypy.Application object which is handling this request.""" handler = None """ The function, method, or other callable which CherryPy will call to produce the response. The discovery of the handler and the arguments it will receive are determined by the request.dispatch object. By default, the handler is discovered by walking a tree of objects starting at request.app.root, and is then passed all HTTP params (from the query string and POST body) as keyword arguments.""" toolmaps = {} """ A nested dict of all Toolboxes and Tools in effect for this request, of the form: {Toolbox.namespace: {Tool.name: config dict}}.""" config = None """ A flat dict of all configuration entries which apply to the current request. These entries are collected from global config, application config (based on request.path_info), and from handler config (exactly how is governed by the request.dispatch object in effect for this request; by default, handler config can be attached anywhere in the tree between request.app.root and the final handler, and inherits downward).""" is_index = None """ This will be True if the current request is mapped to an 'index' resource handler (also, a 'default' handler if path_info ends with a slash). The value may be used to automatically redirect the user-agent to a 'more canonical' URL which either adds or removes the trailing slash. See cherrypy.tools.trailing_slash.""" hooks = HookMap(hookpoints) """ A HookMap (dict-like object) of the form: {hookpoint: [hook, ...]}. Each key is a str naming the hook point, and each value is a list of hooks which will be called at that hook point during this request. The list of hooks is generally populated as early as possible (mostly from Tools specified in config), but may be extended at any time. See also: _cprequest.Hook, _cprequest.HookMap, and cherrypy.tools.""" error_response = cherrypy.HTTPError(500).set_response """ The no-arg callable which will handle unexpected, untrapped errors during request processing. This is not used for expected exceptions (like NotFound, HTTPError, or HTTPRedirect) which are raised in response to expected conditions (those should be customized either via request.error_page or by overriding HTTPError.set_response). By default, error_response uses HTTPError(500) to return a generic error response to the user-agent.""" error_page = {} """ A dict of {error code: response filename or callable} pairs. The error code must be an int representing a given HTTP error code, or the string 'default', which will be used if no matching entry is found for a given numeric code. If a filename is provided, the file should contain a Python string- formatting template, and can expect by default to receive format values with the mapping keys %(status)s, %(message)s, %(traceback)s, and %(version)s. The set of format mappings can be extended by overriding HTTPError.set_response. If a callable is provided, it will be called by default with keyword arguments 'status', 'message', 'traceback', and 'version', as for a string-formatting template. The callable must return a string or iterable of strings which will be set to response.body. It may also override headers or perform any other processing. If no entry is given for an error code, and no 'default' entry exists, a default template will be used. """ show_tracebacks = True """ If True, unexpected errors encountered during request processing will include a traceback in the response body.""" show_mismatched_params = True """ If True, mismatched parameters encountered during PageHandler invocation processing will be included in the response body.""" throws = (KeyboardInterrupt, SystemExit, cherrypy.InternalRedirect) """The sequence of exceptions which Request.run does not trap.""" throw_errors = False """ If True, Request.run will not trap any errors (except HTTPRedirect and HTTPError, which are more properly called 'exceptions', not errors).""" closed = False """True once the close method has been called, False otherwise.""" stage = None """ A string containing the stage reached in the request-handling process. This is useful when debugging a live server with hung requests.""" unique_id = None """A lazy object generating and memorizing UUID4 on ``str()`` render.""" namespaces = reprconf.NamespaceSet( **{ 'hooks': hooks_namespace, 'request': request_namespace, 'response': response_namespace, 'error_page': error_page_namespace, 'tools': cherrypy.tools, }) def __init__(self, local_host, remote_host, scheme='http', server_protocol='HTTP/1.1'): """Populate a new Request object. local_host should be an httputil.Host object with the server info. remote_host should be an httputil.Host object with the client info. scheme should be a string, either "http" or "https". """ self.local = local_host self.remote = remote_host self.scheme = scheme self.server_protocol = server_protocol self.closed = False # Put a *copy* of the class error_page into self. self.error_page = self.error_page.copy() # Put a *copy* of the class namespaces into self. self.namespaces = self.namespaces.copy() self.stage = None self.unique_id = LazyUUID4() def close(self): """Run cleanup code. (Core)""" if not self.closed: self.closed = True self.stage = 'on_end_request' self.hooks.run('on_end_request') self.stage = 'close' def run(self, method, path, query_string, req_protocol, headers, rfile): r"""Process the Request. (Core) method, path, query_string, and req_protocol should be pulled directly from the Request-Line (e.g. "GET /path?key=val HTTP/1.0"). path This should be %XX-unquoted, but query_string should not be. When using Python 2, they both MUST be byte strings, not unicode strings. When using Python 3, they both MUST be unicode strings, not byte strings, and preferably not bytes \x00-\xFF disguised as unicode. headers A list of (name, value) tuples. rfile A file-like object containing the HTTP request entity. When run() is done, the returned object should have 3 attributes: * status, e.g. "200 OK" * header_list, a list of (name, value) tuples * body, an iterable yielding strings Consumer code (HTTP servers) should then access these response attributes to build the outbound stream. """ response = cherrypy.serving.response self.stage = 'run' try: self.error_response = cherrypy.HTTPError(500).set_response self.method = method path = path or '/' self.query_string = query_string or '' self.params = {} # Compare request and server HTTP protocol versions, in case our # server does not support the requested protocol. Limit our output # to min(req, server). We want the following output: # request server actual written supported response # protocol protocol response protocol feature set # a 1.0 1.0 1.0 1.0 # b 1.0 1.1 1.1 1.0 # c 1.1 1.0 1.0 1.0 # d 1.1 1.1 1.1 1.1 # Notice that, in (b), the response will be "HTTP/1.1" even though # the client only understands 1.0. RFC 2616 10.5.6 says we should # only return 505 if the _major_ version is different. rp = int(req_protocol[5]), int(req_protocol[7]) sp = int(self.server_protocol[5]), int(self.server_protocol[7]) self.protocol = min(rp, sp) response.headers.protocol = self.protocol # Rebuild first line of the request (e.g. "GET /path HTTP/1.0"). url = path if query_string: url += '?' + query_string self.request_line = '%s %s %s' % (method, url, req_protocol) self.header_list = list(headers) self.headers = httputil.HeaderMap() self.rfile = rfile self.body = None self.cookie = SimpleCookie() self.handler = None # path_info should be the path from the # app root (script_name) to the handler. self.script_name = self.app.script_name self.path_info = pi = path[len(self.script_name):] self.stage = 'respond' self.respond(pi) except self.throws: raise except Exception: if self.throw_errors: raise else: # Failure in setup, error handler or finalize. Bypass them. # Can't use handle_error because we may not have hooks yet. cherrypy.log(traceback=True, severity=40) if self.show_tracebacks: body = format_exc() else: body = '' r = bare_error(body) response.output_status, response.header_list, response.body = r if self.method == 'HEAD': # HEAD requests MUST NOT return a message-body in the response. response.body = [] try: cherrypy.log.access() except Exception: cherrypy.log.error(traceback=True) return response def respond(self, path_info): """Generate a response for the resource at self.path_info. (Core)""" try: try: try: self._do_respond(path_info) except (cherrypy.HTTPRedirect, cherrypy.HTTPError): inst = sys.exc_info()[1] inst.set_response() self.stage = 'before_finalize (HTTPError)' self.hooks.run('before_finalize') cherrypy.serving.response.finalize() finally: self.stage = 'on_end_resource' self.hooks.run('on_end_resource') except self.throws: raise except Exception: if self.throw_errors: raise self.handle_error() def _do_respond(self, path_info): response = cherrypy.serving.response if self.app is None: raise cherrypy.NotFound() self.hooks = self.__class__.hooks.copy() self.toolmaps = {} # Get the 'Host' header, so we can HTTPRedirect properly. self.stage = 'process_headers' self.process_headers() self.stage = 'get_resource' self.get_resource(path_info) self.body = _cpreqbody.RequestBody(self.rfile, self.headers, request_params=self.params) self.namespaces(self.config) self.stage = 'on_start_resource' self.hooks.run('on_start_resource') # Parse the querystring self.stage = 'process_query_string' self.process_query_string() # Process the body if self.process_request_body: if self.method not in self.methods_with_bodies: self.process_request_body = False self.stage = 'before_request_body' self.hooks.run('before_request_body') if self.process_request_body: self.body.process() # Run the handler self.stage = 'before_handler' self.hooks.run('before_handler') if self.handler: self.stage = 'handler' response.body = self.handler() # Finalize self.stage = 'before_finalize' self.hooks.run('before_finalize') response.finalize() def process_query_string(self): """Parse the query string into Python structures. (Core)""" try: p = httputil.parse_query_string( self.query_string, encoding=self.query_string_encoding) except UnicodeDecodeError: raise cherrypy.HTTPError( 404, 'The given query string could not be processed. Query ' 'strings for this resource must be encoded with %r.' % self.query_string_encoding) # Python 2 only: keyword arguments must be byte strings (type 'str'). if six.PY2: for key, value in p.items(): if isinstance(key, six.text_type): del p[key] p[key.encode(self.query_string_encoding)] = value self.params.update(p) def process_headers(self): """Parse HTTP header data into Python structures. (Core)""" # Process the headers into self.headers headers = self.headers for name, value in self.header_list: # Call title() now (and use dict.__method__(headers)) # so title doesn't have to be called twice. name = name.title() value = value.strip() headers[name] = httputil.decode_TEXT_maybe(value) # Some clients, notably Konquoror, supply multiple # cookies on different lines with the same key. To # handle this case, store all cookies in self.cookie. if name == 'Cookie': try: self.cookie.load(value) except CookieError as exc: raise cherrypy.HTTPError(400, str(exc)) if not dict.__contains__(headers, 'Host'): # All Internet-based HTTP/1.1 servers MUST respond with a 400 # (Bad Request) status code to any HTTP/1.1 request message # which lacks a Host header field. if self.protocol >= (1, 1): msg = "HTTP/1.1 requires a 'Host' request header." raise cherrypy.HTTPError(400, msg) host = dict.get(headers, 'Host') if not host: host = self.local.name or self.local.ip self.base = '%s://%s' % (self.scheme, host) def get_resource(self, path): """Call a dispatcher (which sets self.handler and .config). (Core)""" # First, see if there is a custom dispatch at this URI. Custom # dispatchers can only be specified in app.config, not in _cp_config # (since custom dispatchers may not even have an app.root). dispatch = self.app.find_config(path, 'request.dispatch', self.dispatch) # dispatch() should set self.handler and self.config dispatch(path) def handle_error(self): """Handle the last unanticipated exception. (Core)""" try: self.hooks.run('before_error_response') if self.error_response: self.error_response() self.hooks.run('after_error_response') cherrypy.serving.response.finalize() except cherrypy.HTTPRedirect: inst = sys.exc_info()[1] inst.set_response() cherrypy.serving.response.finalize()
def messageArg(self): message = ("If you construct an HTTPError with a 'message' " 'argument, it wil be placed on the error page ' '(underneath the status line by default).') raise cherrypy.HTTPError(500, message=message)
def _get_file_path(self): f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id) if not os.path.abspath(f).startswith(self.storage_path): raise cherrypy.HTTPError(400, 'Invalid session id in cookie.') return f
def index(self): if cherrypy.request.method == 'GET': # List ALL THE TASKS! return json.dumps(self.magic.get_tasks(),indent=1) raise cherrypy.HTTPError(405)
def test_callable_spec(callable, callable_args, callable_kwargs): """ Inspect callable and test to see if the given args are suitable for it. When an error occurs during the handler's invoking stage there are 2 erroneous cases: 1. Too many parameters passed to a function which doesn't define one of *args or **kwargs. 2. Too little parameters are passed to the function. There are 3 sources of parameters to a cherrypy handler. 1. query string parameters are passed as keyword parameters to the handler. 2. body parameters are also passed as keyword parameters. 3. when partial matching occurs, the final path atoms are passed as positional args. Both the query string and path atoms are part of the URI. If they are incorrect, then a 404 Not Found should be raised. Conversely the body parameters are part of the request; if they are invalid a 400 Bad Request. """ show_mismatched_params = getattr(cherrypy.serving.request, 'show_mismatched_params', False) try: (args, varargs, varkw, defaults) = inspect.getargspec(callable) except TypeError: if isinstance(callable, object) and hasattr(callable, '__call__'): (args, varargs, varkw, defaults) = inspect.getargspec(callable.__call__) else: # If it wasn't one of our own types, re-raise # the original error raise if args and args[0] == 'self': args = args[1:] arg_usage = dict([( arg, 0, ) for arg in args]) vararg_usage = 0 varkw_usage = 0 extra_kwargs = set() for i, value in enumerate(callable_args): try: arg_usage[args[i]] += 1 except IndexError: vararg_usage += 1 for key in callable_kwargs.keys(): try: arg_usage[key] += 1 except KeyError: varkw_usage += 1 extra_kwargs.add(key) # figure out which args have defaults. args_with_defaults = args[-len(defaults or []):] for i, val in enumerate(defaults or []): # Defaults take effect only when the arg hasn't been used yet. if arg_usage[args_with_defaults[i]] == 0: arg_usage[args_with_defaults[i]] += 1 missing_args = [] multiple_args = [] for key, usage in arg_usage.items(): if usage == 0: missing_args.append(key) elif usage > 1: multiple_args.append(key) if missing_args: # In the case where the method allows body arguments # there are 3 potential errors: # 1. not enough query string parameters -> 404 # 2. not enough body parameters -> 400 # 3. not enough path parts (partial matches) -> 404 # # We can't actually tell which case it is, # so I'm raising a 404 because that covers 2/3 of the # possibilities # # In the case where the method does not allow body # arguments it's definitely a 404. message = None if show_mismatched_params: message = "Missing parameters: %s" % ",".join(missing_args) raise cherrypy.HTTPError(404, message=message) # the extra positional arguments come from the path - 404 Not Found if not varargs and vararg_usage > 0: raise cherrypy.HTTPError(404) body_params = cherrypy.serving.request.body.params or {} body_params = set(body_params.keys()) qs_params = set(callable_kwargs.keys()) - body_params if multiple_args: if qs_params.intersection(set(multiple_args)): # If any of the multiple parameters came from the query string then # it's a 404 Not Found error = 404 else: # Otherwise it's a 400 Bad Request error = 400 message = None if show_mismatched_params: message="Multiple values for parameters: "\ "%s" % ",".join(multiple_args) raise cherrypy.HTTPError(error, message=message) if not varkw and varkw_usage > 0: # If there were extra query string parameters, it's a 404 Not Found extra_qs_params = set(qs_params).intersection(extra_kwargs) if extra_qs_params: message = None if show_mismatched_params: message="Unexpected query string "\ "parameters: %s" % ", ".join(extra_qs_params) raise cherrypy.HTTPError(404, message=message) # If there were any extra body parameters, it's a 400 Not Found extra_body_params = set(body_params).intersection(extra_kwargs) if extra_body_params: message = None if show_mismatched_params: message="Unexpected body parameters: "\ "%s" % ", ".join(extra_body_params) raise cherrypy.HTTPError(400, message=message)
def read_lines_to_boundary(self, fp_out=None): """Read bytes from self.fp and return or write them to a file. If the 'fp_out' argument is None (the default), all bytes read are returned in a single byte string. If the 'fp_out' argument is not None, it must be a file-like object that supports the 'write' method; all bytes read will be written to the fp, and that fp is returned. """ endmarker = self.boundary + ntob("--") delim = ntob("") prev_lf = True lines = [] seen = 0 while True: line = self.fp.readline(1 << 16) if not line: raise EOFError("Illegal end of multipart body.") if line.startswith(ntob("--")) and prev_lf: strippedline = line.strip() if strippedline == self.boundary: break if strippedline == endmarker: self.fp.finish() break line = delim + line if line.endswith(ntob("\r\n")): delim = ntob("\r\n") line = line[:-2] prev_lf = True elif line.endswith(ntob("\n")): delim = ntob("\n") line = line[:-1] prev_lf = True else: delim = ntob("") prev_lf = False if fp_out is None: lines.append(line) seen += len(line) if seen > self.maxrambytes: fp_out = self.make_file() for line in lines: fp_out.write(line) else: fp_out.write(line) if fp_out is None: result = ntob('').join(lines) for charset in self.attempt_charsets: try: result = result.decode(charset) except UnicodeDecodeError: pass else: self.charset = charset return result else: raise cherrypy.HTTPError( 400, "The request entity could not be decoded. The following " "charsets were attempted: %s" % repr(self.attempt_charsets) ) else: fp_out.seek(0) return fp_out
def handle_before_request(cls, e): controller = e.source if not controller.allowed_publication_targets: raise cherrypy.HTTPError(403, "Forbidden")
def read(self, size=None, fp_out=None): """Read bytes from the request body and return or write them to a file. A number of bytes less than or equal to the 'size' argument are read off the socket. The actual number of bytes read are tracked in self.bytes_read. The number may be smaller than 'size' when 1) the client sends fewer bytes, 2) the 'Content-Length' request header specifies fewer bytes than requested, or 3) the number of bytes read exceeds self.maxbytes (in which case, 413 is raised). If the 'fp_out' argument is None (the default), all bytes read are returned in a single byte string. If the 'fp_out' argument is not None, it must be a file-like object that supports the 'write' method; all bytes read will be written to the fp, and None is returned. """ if self.length is None: if size is None: remaining = inf else: remaining = size else: remaining = self.length - self.bytes_read if size and size < remaining: remaining = size if remaining == 0: self.finish() if fp_out is None: return ntob('') else: return None chunks = [] # Read bytes from the buffer. if self.buffer: if remaining is inf: data = self.buffer self.buffer = ntob('') else: data = self.buffer[:remaining] self.buffer = self.buffer[remaining:] datalen = len(data) remaining -= datalen # Check lengths. self.bytes_read += datalen if self.maxbytes and self.bytes_read > self.maxbytes: raise cherrypy.HTTPError(413) # Store the data. if fp_out is None: chunks.append(data) else: fp_out.write(data) # Read bytes from the socket. while remaining > 0: chunksize = min(remaining, self.bufsize) try: data = self.fp.read(chunksize) except Exception: e = sys.exc_info()[1] if e.__class__.__name__ == 'MaxSizeExceeded': # Post data is too big raise cherrypy.HTTPError( 413, "Maximum request length: %r" % e.args[1]) else: raise if not data: self.finish() break datalen = len(data) remaining -= datalen # Check lengths. self.bytes_read += datalen if self.maxbytes and self.bytes_read > self.maxbytes: raise cherrypy.HTTPError(413) # Store the data. if fp_out is None: chunks.append(data) else: fp_out.write(data) if fp_out is None: return ntob('').join(chunks)
def gzip(compress_level=5, mime_types=['text/html', 'text/plain'], debug=False): """Try to gzip the response body if Content-Type in mime_types. cherrypy.response.headers['Content-Type'] must be set to one of the values in the mime_types arg before calling this function. The provided list of mime-types must be of one of the following form: * `type/subtype` * `type/*` * `type/*+subtype` No compression is performed if any of the following hold: * The client sends no Accept-Encoding request header * No 'gzip' or 'x-gzip' is present in the Accept-Encoding header * No 'gzip' or 'x-gzip' with a qvalue > 0 is present * The 'identity' value is given with a qvalue > 0. """ request = cherrypy.serving.request response = cherrypy.serving.response set_vary_header(response, 'Accept-Encoding') if not response.body: # Response body is empty (might be a 304 for instance) if debug: cherrypy.log('No response body', context='TOOLS.GZIP') return # If returning cached content (which should already have been gzipped), # don't re-zip. if getattr(request, 'cached', False): if debug: cherrypy.log('Not gzipping cached response', context='TOOLS.GZIP') return acceptable = request.headers.elements('Accept-Encoding') if not acceptable: # If no Accept-Encoding field is present in a request, # the server MAY assume that the client will accept any # content coding. In this case, if "identity" is one of # the available content-codings, then the server SHOULD use # the "identity" content-coding, unless it has additional # information that a different content-coding is meaningful # to the client. if debug: cherrypy.log('No Accept-Encoding', context='TOOLS.GZIP') return ct = response.headers.get('Content-Type', '').split(';')[0] for coding in acceptable: if coding.value == 'identity' and coding.qvalue != 0: if debug: cherrypy.log('Non-zero identity qvalue: %s' % coding, context='TOOLS.GZIP') return if coding.value in ('gzip', 'x-gzip'): if coding.qvalue == 0: if debug: cherrypy.log('Zero gzip qvalue: %s' % coding, context='TOOLS.GZIP') return if ct not in mime_types: # If the list of provided mime-types contains tokens # such as 'text/*' or 'application/*+xml', # we go through them and find the most appropriate one # based on the given content-type. # The pattern matching is only caring about the most # common cases, as stated above, and doesn't support # for extra parameters. found = False if '/' in ct: ct_media_type, ct_sub_type = ct.split('/') for mime_type in mime_types: if '/' in mime_type: media_type, sub_type = mime_type.split('/') if ct_media_type == media_type: if sub_type == '*': found = True break elif '+' in sub_type and '+' in ct_sub_type: ct_left, ct_right = ct_sub_type.split('+') left, right = sub_type.split('+') if left == '*' and ct_right == right: found = True break if not found: if debug: cherrypy.log('Content-Type %s not in mime_types %r' % (ct, mime_types), context='TOOLS.GZIP') return if debug: cherrypy.log('Gzipping', context='TOOLS.GZIP') # Return a generator that compresses the page response.headers['Content-Encoding'] = 'gzip' response.body = compress(response.body, compress_level) if 'Content-Length' in response.headers: # Delete Content-Length header so finalize() recalcs it. del response.headers['Content-Length'] return if debug: cherrypy.log('No acceptable encoding found.', context='GZIP') cherrypy.HTTPError(406, 'identity, gzip').set_response()
def serve_file(path, content_type=None, disposition=None, name=None): """Set status, headers, and body in order to serve the given file. The Content-Type header will be set to the content_type arg, if provided. If not provided, the Content-Type will be guessed by the file extension of the 'path' argument. If disposition is not None, the Content-Disposition header will be set to "<disposition>; filename=<name>". If name is None, it will be set to the basename of path. If disposition is None, no Content-Disposition header will be written. """ response = cherrypy.response # If path is relative, users should fix it by making path absolute. # That is, CherryPy should not guess where the application root is. # It certainly should *not* use cwd (since CP may be invoked from a # variety of paths). If using tools.static, you can make your relative # paths become absolute by supplying a value for "tools.static.root". if not os.path.isabs(path): raise ValueError("'%s' is not an absolute path." % path) try: st = os.stat(path) except OSError: raise cherrypy.NotFound() # Check if path is a directory. if stat.S_ISDIR(st.st_mode): # Let the caller deal with it as they like. raise cherrypy.NotFound() # Set the Last-Modified response header, so that # modified-since validation code can work. response.headers['Last-Modified'] = http.HTTPDate(st.st_mtime) cptools.validate_since() if content_type is None: # Set content-type based on filename extension ext = "" i = path.rfind('.') if i != -1: ext = path[i:].lower() content_type = mimetypes.types_map.get(ext, "text/plain") response.headers['Content-Type'] = content_type if disposition is not None: if name is None: name = os.path.basename(path) cd = '%s; filename="%s"' % (disposition, name) response.headers["Content-Disposition"] = cd # Set Content-Length and use an iterable (file object) # this way CP won't load the whole file in memory c_len = st.st_size bodyfile = open(path, 'rb') # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code if cherrypy.request.protocol >= (1, 1): response.headers["Accept-Ranges"] = "bytes" r = http.get_ranges(cherrypy.request.headers.get('Range'), c_len) if r == []: response.headers['Content-Range'] = "bytes */%s" % c_len message = "Invalid Range (first-byte-pos greater than Content-Length)" raise cherrypy.HTTPError(416, message) if r: if len(r) == 1: # Return a single-part response. start, stop = r[0] if stop > c_len: stop = c_len r_len = stop - start response.status = "206 Partial Content" response.headers['Content-Range'] = ("bytes %s-%s/%s" % (start, stop - 1, c_len)) response.headers['Content-Length'] = r_len bodyfile.seek(start) response.body = file_generator_limited(bodyfile, r_len) else: # Return a multipart/byteranges response. response.status = "206 Partial Content" import mimetools boundary = mimetools.choose_boundary() ct = "multipart/byteranges; boundary=%s" % boundary response.headers['Content-Type'] = ct if response.headers.has_key("Content-Length"): # Delete Content-Length header so finalize() recalcs it. del response.headers["Content-Length"] def file_ranges(): # Apache compatibility: yield "\r\n" for start, stop in r: yield "--" + boundary yield "\r\nContent-type: %s" % content_type yield ("\r\nContent-range: bytes %s-%s/%s\r\n\r\n" % (start, stop - 1, c_len)) bodyfile.seek(start) for chunk in file_generator_limited( bodyfile, stop - start): yield chunk yield "\r\n" # Final boundary yield "--" + boundary + "--" # Apache compatibility: yield "\r\n" response.body = file_ranges() else: response.headers['Content-Length'] = c_len response.body = bodyfile else: response.headers['Content-Length'] = c_len response.body = bodyfile return response.body