def respond_to_client(self, response: HttpResponse): with wrap_context('responding to client'): self.send_response_only(response.status_code) if has_header(response.headers, 'Content-Encoding'): del response.headers['Content-Encoding'] if self.config.verbose >= 2: log.debug('removing Content-Encoding header') if not has_header(response.headers, 'Content-Length') and \ not has_header(response.headers, 'Transfer-Encoding') and response.content: response.headers['Content-Length'] = str(len(response.content)) log.warn('adding missing Content-Length header') if has_header(response.headers, 'Content-Length') and has_header( response.headers, 'Transfer-Encoding'): del response.headers['Content-Length'] log.warn( 'removed Content-Length header conflicting with Transfer-Encoding' ) for name, value in response.headers.items(): self.send_header(name, value) self.end_headers() if self.config.allow_chunking and response.headers.get( 'Transfer-Encoding') == 'chunked': send_chunked_response(self.wfile, response.content) else: self.wfile.write(response.content) self.close_connection = True if self.config.verbose >= 2: log.debug('> response sent', client_addr=self.client_address[0], client_port=self.client_address[1])
def proxy_request(request: HttpRequest, default_url: str, timeout: int, verbose: int) -> HttpResponse: dst_url = request.dst_url if request.dst_url else default_url with logerr(): with wrap_context('proxying to URL', dst_url=dst_url, path=request.path, content=request.content): url = f'{dst_url}{request.path}' if verbose: log.debug(f'>> proxying to', url=url) response = requests.request(request.method, url, verify=False, allow_redirects=False, stream=False, timeout=timeout, headers=request.headers, data=request.content) content: bytes = response.content http_response = HttpResponse(status_code=response.status_code, headers=dict(response.headers), content=content) return http_response.log('<< received', verbose) # Bad Gateway response error_msg = f'Proxying failed: {dst_url}' return HttpResponse(status_code=502, headers={ 'X-Man-Error': 'proxying failed', }).set_content(error_msg)
def generate_response(self, request_0: HttpRequest) -> HttpResponse: with wrap_context('generating response'): request = request_0.transform(self.extensions.transform_request) if request != request_0 and self.config.verbose >= 2: log.debug('request transformed') immediate_reponse = self.find_immediate_response(request) if immediate_reponse: return immediate_reponse.log('> immediate response', self.config.verbose) self.cache.clear_old() if self.cache.has_cached_response(request): return self.cache.replay_response(request).log( '> Cache: returning cached response', self.config.verbose) if self.config.replay and self.config.verbose: log.warn('request not found in cache', path=request.path) response: HttpResponse = proxy_request( request, default_url=self.config.dst_url, timeout=self.config.timeout, verbose=self.config.verbose) if self.cache.saving_enabled(request, response): self.cache.save_response(request, response) return response
def replay_response(self, request: HttpRequest) -> HttpResponse: request_hash = self._request_hash(request) if self.config.replay_throttle: if self.config.verbose: log.debug('Cache: Throttled response') return too_many_requests_response return self.cache[request_hash].response
def transform_response(request: HttpRequest, response: HttpResponse) -> HttpResponse: """Transforms each Response before sending it.""" if request.path.startswith('/some/api'): log.debug('Found Ya', path=request.path) response = response.set_content('{"payload": "anythingyouwish"}"') return response
def transform_request(request: HttpRequest) -> HttpRequest: """Transforms each incoming Request before further processing (caching, forwarding).""" match = re.match(r'^/some/path/(.+?)(/[a-z]+)(/.*)', request.path) if match: request.path = match.expand(r'\3') log.debug('request path transformed', path=request.path) return request
def calculate_volume(song: AudioSegment) -> float: volume = song.max_dBFS if volume < -0.1: return volume lower_vol = 10 tmp_clip = '.anti_clip.mp3' log.debug('detecting clipping...') lowered = song.apply_gain(-lower_vol) lowered.export(tmp_clip, format="mp3") lowered = AudioSegment.from_mp3(tmp_clip) os.remove(tmp_clip) return lowered.max_dBFS + lower_vol
def clear_old(self): if not self.config.replay_clear_cache: return to_remove = [] now_timestamp: float = now_seconds() for request_hash, entry in self.cache.items(): if now_timestamp - entry.request.timestamp > self.config.replay_clear_cache_seconds: to_remove.append(request_hash) for request_hash in to_remove: del self.cache[request_hash] if to_remove: if self.config.verbose: log.debug('Cache: cleared old cache entries', removed=len(to_remove))
def bulk_rename( pattern: str, replacement_pattern: Optional[str], testing: bool = True, full: bool = False, recursive: bool = False, padding: int = 0, ) -> List[Match]: """ Rename (or match) multiple files at once :param pattern: regex pattern to match filenames :param replacement: replacement regex pattern for renamed files. Use \\1 syntax to make use of matched groups :param testing: True - just testing replacement pattern, False - do actual renaming files :param full: whether to enforce matching full filename against pattern :param recursive: whether to search directories recursively :param padding: applies padding with zeros with given length on matched numerical groups """ log.debug('matching regex pattern', pattern=pattern, replacement=replacement_pattern, testing_mode=testing, full_match=full, recursive=recursive, padding=padding) matches: List[Match] = match_files(Path(), pattern, replacement_pattern, recursive, full, padding) for match in matches: match.log_info(testing) if replacement_pattern: find_duplicates(matches) if testing: if matches: log.info('files matched', count=len(matches)) else: log.info('no files matched', count=len(matches)) elif replacement_pattern: rename_matches(matches) if matches: log.info('files renamed', count=len(matches)) else: log.info('no files renamed', count=len(matches)) else: raise RuntimeError('replacement pattern is required for renaming') return matches
def save_response(self, request: HttpRequest, response: HttpResponse): request_hash = self._request_hash(request) if request_hash not in self.cache: self.cache[request_hash] = CacheEntry(request, response) if self.config.record and self.config.record_file: serializable = list(self.cache.values()) txt = json.dumps(serializable, sort_keys=True, indent=4, cls=EnhancedJSONEncoder) Path(self.config.record_file).write_text(txt) ctx = {} if self.config.verbose: ctx['traits'] = str(self._request_traits(request)) log.debug(f'+ Cache: new request-response recorded', hash=request_hash, total_entries=len(self.cache), **ctx)
def wrap_shell(cmd): cmd = cmd.strip() log.debug(f'> {cmd}') if not settings.DRY_RUN: shell(cmd)