def send_file_data( request_handler: tornado.web.RequestHandler, inpath: str, range_pair_list=None, ): with open(inpath, 'rb') as infile: if range_pair_list is not None: total_number_of_chunks = 0 for range_pair in range_pair_list: remaining_bytes = range_pair[1] - range_pair[0] infile.seek(range_pair[0]) number_of_chunks = int( math.ceil(remaining_bytes / RESPONSE_FILE_CHUNKS_SIZE)) total_number_of_chunks += number_of_chunks pbar = tqdm.tqdm(total=total_number_of_chunks) for range_pair in range_pair_list: remaining_bytes = range_pair[1] - range_pair[0] infile.seek(range_pair[0]) number_of_chunks = int( math.ceil(remaining_bytes / RESPONSE_FILE_CHUNKS_SIZE)) for i in range(number_of_chunks): read_data_size = int( min(RESPONSE_FILE_CHUNKS_SIZE, remaining_bytes)) data = infile.read(read_data_size) request_handler.write(data) remaining_bytes -= read_data_size else: # send whole file # the content size header and status code should have already been set before calling this function filesize = os.stat(inpath).st_size number_of_chunks = int( math.ceil(filesize / RESPONSE_FILE_CHUNKS_SIZE)) pbar = tqdm.tqdm(range(number_of_chunks)) remote_address = request_handler.request.connection.stream.socket.getpeername( ) description = f'{inpath} > {remote_address}' pbar.set_description(description) for _ in pbar: data = infile.read(RESPONSE_FILE_CHUNKS_SIZE) request_handler.write(data)
def log_request(self, handler: tornado.web.RequestHandler) -> None: status_code = handler.get_status() if not self.debug and status_code in [200, 204, 206, 304]: # don't log successful requests in release mode return if status_code < 400: log_method = access_log.info elif status_code < 500: log_method = access_log.warning else: log_method = access_log.error request_time = 1000.0 * handler.request.request_time() user = handler.current_user username = "******" if user is not None and 'username' in user: username = user['username'] log_method(f"{status_code} {handler._request_summary()} " f"[{username}] {request_time:.2f}ms")
def extract_parameters(handler: tornado.web.RequestHandler): arg1 = handler.get_query_argument("arg1", default="") arg2 = handler.get_query_argument("arg2", default="") arg3 = handler.get_query_argument("arg3", default="") return arg1, arg2, arg3
def extract_parameters(handler: tornado.web.RequestHandler): vertex1 = str(handler.get_query_argument("vertex1", default=None)) vertex2 = str(handler.get_query_argument("vertex2", default=None)) key = str(handler.get_query_argument("key", default=None)) weight = str(handler.get_query_argument("weight", default=None)) return vertex1, vertex2, key, weight
def handle_sort_request( field_name: str, request_handler: tornado.web.RequestHandler, ): pbar = tqdm.tqdm(GAME_INFO_LIST) pbar.set_description(f'checking {field_name}') is_invalid_field = False for game_info_dict in pbar: if field_name not in game_info_dict: is_invalid_field = True break if is_invalid_field: request_handler.set_status(400) request_handler.set_header('Content-Type', 'application/json') response_obj = { 'message': 'invalid field name', 'field_name': field_name, } response_str = json.dumps(response_obj) request_handler.write(response_str) return if len(GAME_INFO_LIST) == 0: request_handler.set_status(400) request_handler.set_header('Content-Type', 'application/json') response_obj = { 'message': 'empty game list', } response_str = json.dumps(response_obj) request_handler.write(response_str) return if type(GAME_INFO_LIST[0][field_name]) not in [ int, float, str, bytes, bool ]: request_handler.set_status(400) request_handler.set_header('Content-Type', 'application/json') response_obj = { 'message': 'invalid field type', 'field_name': field_name, } response_str = json.dumps(response_obj) request_handler.write(response_str) return # sort start_time_ns = time.perf_counter_ns() GAME_INFO_LIST.sort(key=lambda x: x[field_name]) end_time_ns = time.perf_counter_ns() taken_time_ns = end_time_ns - start_time_ns response_obj = { 'message': 'sorted', 'field_name': field_name, 'taken_time_ns': taken_time_ns, } response_str = json.dumps(response_obj) request_handler.set_status(200) request_handler.set_header('Content-Type', 'application/json') request_handler.write(response_str)
def handle_game_binaries_request( quoted_url: str, request_handler: tornado.web.RequestHandler, ): # if the request header contains 'Last-Modified', 'If-Modified-Since' and 'If-Unmodified-Since' return 304 Not Modified for key in request_handler.request.headers: if key.lower() in ['if-modified-since', 'if-unmodified-since']: request_handler.set_status(304) return url = urllib.parse.unquote(quoted_url) if url not in GAME_BINARY_URL_INFO_DICT.keys(): # not found request_handler.set_status(404) request_handler.set_header('Content-Type', 'application/json') response_obj = { 'message': 'this url is not in cache', 'url': url, } response_str = json.dumps(response_obj) request_handler.write(response_str) return True body_content_cache_key = GAME_BINARY_URL_INFO_DICT[url] content_bs = cacherequests.get_body_content(body_content_cache_key) if content_bs is None: request_handler.set_status(404) request_handler.set_header('Content-Type', 'application/json') response_obj = { 'message': 'content is no longer in cache', 'url': url, } response_str = json.dumps(response_obj) request_handler.write(response_str) return True if len(content_bs) == 0: request_handler.set_status(404) request_handler.set_header('Content-Type', 'application/json') response_obj = { 'message': 'content is empty', 'url': url, } response_str = json.dumps(response_obj) request_handler.write(response_str) return True # I didn't store the response header, so we don't have the actual value request_handler.set_status(200) request_handler.set_header('Last-Modified', DEFAULT_LAST_MODIFIED_VALUE) request_handler.set_header('Content-Type', 'application/octet-stream') request_handler.set_header('Content-Length', str(len(content_bs))) request_handler.write(content_bs) return True
def handle_image_request( quoted_image_url: str, request_handler: tornado.web.RequestHandler, ): # if the request header contains 'Last-Modified', 'If-Modified-Since' and 'If-Unmodified-Since' return 304 Not Modified for key in request_handler.request.headers: if key.lower() in ['if-modified-since', 'if-unmodified-since']: request_handler.set_status(304) return image_url = urllib.parse.unquote(quoted_image_url) if image_url not in IMAGE_URL_INFO_DICT.keys(): # not found request_handler.set_status(404) request_handler.set_header('Content-Type', 'application/json') response_obj = { 'message': 'this image url is not in cache', 'path': image_url, } response_str = json.dumps(response_obj) request_handler.write(response_str) return body_content_cache_key = IMAGE_URL_INFO_DICT[image_url] image_content_bs = cacherequests.get_body_content(body_content_cache_key) if image_content_bs is None: request_handler.set_status(404) request_handler.set_header('Content-Type', 'application/json') response_obj = { 'message': 'image content is no longer in cache', 'path': image_url, } response_str = json.dumps(response_obj) request_handler.write(response_str) return # detect image type ext = os.path.splitext(image_url)[1] ext = ext.lower() if ext in MIME_TYPE_DICT: mime_type = MIME_TYPE_DICT[ext] else: image_type_str = detect_image_type_from_content(image_content_bs) if image_type_str is None: mime_type = 'application/octet-stream' else: mime_type = f'image/{image_type_str}' # I didn't store the response header, so we don't have the actual value request_handler.set_status(200) request_handler.set_header('Last-Modified', DEFAULT_LAST_MODIFIED_VALUE) request_handler.set_header('Content-Type', mime_type) request_handler.set_header('Content-Length', str(len(image_content_bs))) request_handler.write(image_content_bs) return
def handle_game_list_request(request_handler: tornado.web.RequestHandler, ): # implement pagination params = request_handler.request.query_arguments print('handle_game_list_request: params', params) start_index = 0 count = 16 if 'index' in params: try: start_index = parse_index_value(params['index']) except Exception as ex: # invalid index stack_trace = traceback.format_exc() print(FG_RED, 'EXCEPTION:', ex) print(stack_trace, RESET_COLOR) request_handler.set_status(400) request_handler.set_header('Content-Type', 'application/json') response_obj = { 'message': 'invalid index', 'exception': str(ex), 'stacktrace': stack_trace, } response_str = json.dumps(response_obj) request_handler.write(response_str) return if 'count' in params: try: count = parse_count_value(params['count']) except Exception as ex: # invalid count stack_trace = traceback.format_exc() print(FG_RED, 'EXCEPTION:', ex) print(stack_trace, RESET_COLOR) request_handler.set_status(400) request_handler.set_header('Content-Type', 'application/json') response_obj = { 'message': 'invalid count', 'exception': str(ex), 'stacktrace': stack_trace, } response_str = json.dumps(response_obj) request_handler.write(response_str) return end_index = start_index + count end_index = min(end_index, len(GAME_INFO_LIST)) response_obj = [] for i in range(start_index, end_index): game_info_dict = GAME_INFO_LIST[i] game_info_dict = game_info_dict.copy() game_info_dict['index'] = i response_obj.append(game_info_dict) # print(response_obj) response_obj = make_obj_json_friendly(response_obj) # print(response_obj) json_str = json.dumps(response_obj) # json_bs = json_str.encode('utf-8') request_handler.set_status(200) request_handler.set_header('Content-Type', 'application/json, charset=utf-8') # request_handler.set_header('Content-Length', str(len(json_bs))) # request_handler.write(json_bs) request_handler.write(json_str) return
def handle_webdata_request(request_handler: tornado.web.RequestHandler, ): request_path = request_handler.request.path # normalize request path try: normalized_request_path = normalize_request_path(request_path) except InvalidCharacterInPath as ex: print(ex) # unauthorize request_handler.set_status(403) request_handler.set_header('Content-Type', 'application/json') response_obj = { 'message': 'invalid character in path', 'path': request_path, } response_str = json.dumps(response_obj) request_handler.write(response_str) return # join with webdata directory if len(normalized_request_path) == 0: local_path = WEBDATA_DIRECTORY elif normalized_request_path == '/': local_path = WEBDATA_DIRECTORY else: local_path = os.path.join(WEBDATA_DIRECTORY, normalized_request_path) if local_path == WEBDATA_DIRECTORY: pass elif not is_child_path(WEBDATA_DIRECTORY, local_path): # unauthorize request_handler.set_status(403) request_handler.set_header('Content-Type', 'application/json') response_obj = { 'message': 'unauthorized access', 'path': request_path, } response_str = json.dumps(response_obj) request_handler.write(response_str) return # check if file exists if not os.path.exists(local_path): # not found request_handler.set_status(404) request_handler.set_header('Content-Type', 'application/json') response_obj = { 'message': 'file not found', 'path': request_path, } response_str = json.dumps(response_obj) request_handler.write(response_str) return file_stat = os.stat(local_path) if stat.S_ISDIR(file_stat.st_mode): # automatically serve index.html # directory # check if index.html exists child_filename_list = os.listdir(local_path) for child_filename in child_filename_list: lowered_child_filename = child_filename.lower() if lowered_child_filename in VALID_HTML_INDEX_FILENAME_LIST: child_filepath = os.path.join(local_path, child_filename) # send modified time os.path.getmtime(child_filepath) # TODO request_handler.set_status(200) request_handler.set_header('Content-Type', 'text/html') filesize = os.stat(child_filepath).st_size if filesize > 0: send_file_data(request_handler, child_filepath) return # not found # return directory listing # TODO option to disable directory listing # contruct static html page html_str = render_static_directory_listing_html( normalized_request_path, child_filename_list, ) request_handler.set_status(200) request_handler.set_header('Content-Type', 'text/html') request_handler.write(html_str) return if not stat.S_ISREG(file_stat.st_mode): # unauthorize # this is not a regular file request_handler.set_status(403) request_handler.set_header('Content-Type', 'application/json') response_obj = { 'message': 'this is not a regular file', 'path': request_path, } response_str = json.dumps(response_obj) request_handler.write(response_str) return # regular file # TODO parse and support Range header request_handler.set_status(200) mime_type = get_mime_type_by_filename(local_path) if mime_type in TEXT_MIME_TYPE_LIST: # set mime type and charset to utf-8 header_value = f'{mime_type}; charset=utf-8' request_handler.set_header('Content-Type', header_value) else: request_handler.set_header('Content-Type', mime_type) filesize = os.stat(local_path).st_size request_handler.set_header('Content-Length', str(filesize)) send_file_data(request_handler, local_path)