def parse_cookies(self): from http.cookies import SimpleCookie, CookieError if not self._headers_history: self._parse_headers_raw() # Get cookies from endpoint cookies = [] for header in chain(*self._headers_history): if len(header) > 2: continue key, value = header[0], header[1] if key.lower().startswith("set-cookie"): try: cookie = SimpleCookie() cookie.load(value) cookies.extend(list(cookie.values())) # update cookie jar for morsel in list(cookie.values()): if isinstance(self._cookies_jar, CookieJar): self._cookies_jar.set_cookie(morsel_to_cookie(morsel)) except CookieError as e: logger.warn(e) self._cookies = dict([(cookie.key, cookie.value) for cookie in cookies]) return self._cookies
def parse_cookies(self): from http.cookies import SimpleCookie, CookieError if not self._headers_history: self._parse_headers_raw() # Get cookies from endpoint cookies = [] for header in chain(*self._headers_history): if len(header) > 2: continue key, value = header[0], header[1] if key.lower().startswith("set-cookie"): try: cookie = SimpleCookie() cookie.load(value) cookies.extend(list(cookie.values())) # update cookie jar for morsel in list(cookie.values()): if isinstance(self._cookies_jar, CookieJar): self._cookies_jar.set_cookie( morsel_to_cookie(morsel)) except CookieError as e: #logger.warn(e) pass self._cookies = dict([(cookie.key, cookie.value) for cookie in cookies]) return self._cookies
def request(self, url, callback): cookie_text = 'uuid_n_v=v1; uuid=9EB2A080B96A11EA9F28E30FF5FFF73CB5154A84C7A94D1DAB10BB8C2D31FEB8; _csrf=448d5750ef63e51bf723695e9008d2c176352dad7c0fc460f422f517baa014ba; Hm_lvt_703e94591e87be68cc8da0da7cbd0be2=1593367820; _lxsdk_cuid=172fc1f7724c8-098b7ae06bfb5b-39647b09-1fa400-172fc1f7724c8; _lxsdk=9EB2A080B96A11EA9F28E30FF5FFF73CB5154A84C7A94D1DAB10BB8C2D31FEB8; mojo-uuid=005c478c1b1c76a729dcde705c8ea14b; mojo-session-id={"id":"2749c8c2bea2ee39e46c57deda270ee5","time":1593387106486}; mojo-trace-id=6; Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2=1593387742; __mta=147693064.1593367823108.1593387730201.1593387742702.13; _lxsdk_s=172fd4449b1-687-c0b-19b%7C%7C12' cookie = SimpleCookie(cookie_text) cookie_dict = {cookie.key: cookie.value for cookie in cookie.values()} request = scrapy.Request(url=url, callback=callback) request.cookies = cookie_dict return request
def test(raw_cookie): from http.cookies import SimpleCookie pcookie = SimpleCookie() pcookie.load(raw_cookie) cookie = {} for p in pcookie.values(): cookie[p.key] = p.coded_value #import pdb; pdb.set_trace() url = 'https://radar.statcan.gc.ca/api/' data = { "variables": {}, "query": '''{ studies(orderBy: CREATED_AT_DESC) { nodes { ...StudyData } } } fragment StudyData on Study { id name description type createdBy createdAt updatedAt sourceRepositoryId } ''' } r = requests.post(url=url, data=data, timeout=10, cookies=cookie) print(r.text, r.status_code)
def _get_cookies(self, *args, **kwargs): u''' Override method in superclass to ensure HttpOnly is set appropriately. ''' super_cookies = super(CkanAuthTktCookiePlugin, self). \ _get_cookies(*args, **kwargs) cookies = [] for k, v in super_cookies: cookie = SimpleCookie(str(v)) morsel = list(cookie.values())[0] # SameSite was only added on Python 3.8 morsel._reserved['samesite'] = 'SameSite' # Keep old case as it's the one used in tests, it should make no # difference in the browser morsel._reserved['httponly'] = 'HttpOnly' morsel._reserved['secure'] = 'Secure' if self.httponly: cookie[self.cookie_name]['HttpOnly'] = True if self.samesite == 'none': cookie[self.cookie_name]['SameSite'] = 'None' elif self.samesite == 'strict': cookie[self.cookie_name]['SameSite'] = 'Strict' else: cookie[self.cookie_name]['SameSite'] = 'Lax' cookies.append((k, cookie.output().replace('Set-Cookie: ', ''))) return cookies
def cookies_action(cli_args): if cli_args.csv: output_writer = csv.writer(cli_args.output) try: jar = getattr(browser_cookie3, cli_args.browser)() except browser_cookie3.BrowserCookieError: die('Could not extract cookies from %s!' % cli_args.browser) if cli_args.url is not None: resolver = CookieResolver(jar) cookie = resolver(cli_args.url) if cookie is not None: if cli_args.csv: output_writer.writerow(MORSEL_CSV_HEADER) parsed = SimpleCookie(cookie) for morsel in parsed.values(): output_writer.writerow(format_morsel_for_csv(morsel)) else: print(cookie, file=cli_args.output) else: die('Could not find relevant cookie for %s in %s!' % (cli_args.url, cli_args.browser)) else: if cli_args.csv: output_writer.writerow(COOKIE_CSV_HEADER) for cookie in jar: output_writer.writerow(format_cookie_for_csv(cookie)) else: write_jar_as_text_mozilla(jar, cli_args.output)
def COOKIES(self): if self._COOKIES is None: raw_dict = SimpleCookie(self._environ.get('HTTP_COOKIE', '')) self._COOKIES = {} for cookie in raw_dict.values(): self._COOKIES[cookie.key] = cookie.value return self._COOKIES
def wrapped_start_response(_status, _response_headers, _exc_info=None): # This mess is needed for CORS - Cross Origin Resource Sharing # Preflight isn't getting called the way we're using it apparently. Might can get rid of * in Allow-Headers # Not to be confused with the game engine! :) origin = environ.get("HTTP_ORIGIN") if origin: _response_headers.extend([ ("Access-Control-Allow-Origin", origin), ("Access-Control-Allow-Credentials", "true"), ("Access-Control-Allow-Methods", "GET,POST"), ("Access-Control-Allow-Headers", "Content-Type") ]) _sid = self.factory.save(environ["wsgi.session"]) _cookies = SimpleCookie() _cookies["session_id"] = _sid _cookie = _cookies["session_id"] _cookie["path"] = cookie_path _cookie["httponly"] = 1 _response_headers.extend(("set-cookie", morsel.OutputString()) for morsel in _cookies.values()) return start_response(_status, _response_headers, _exc_info)
async def test_set_cookies(tab, recordingServer): """ Make sure cookies are set properly and only affect the domain they were set for """ logger = Logger() url, reqs = recordingServer cookies = [] c = Morsel() c.set('foo', 'bar', '') c['domain'] = 'localhost' cookies.append(c) c = Morsel() c.set('buz', 'beef', '') c['domain'] = 'nonexistent.example' settings = ControllerSettings(idleTimeout=1, timeout=60, cookies=cookies) controller = SinglePageController(url=url, logger=logger, service=Process(), behavior=[], settings=settings) await asyncio.wait_for(controller.run(), settings.timeout * 2) assert len(reqs) == 1 req = reqs[0] reqCookies = SimpleCookie(req.headers['cookie']) assert len(reqCookies) == 1 c = next(iter(reqCookies.values())) assert c.key == cookies[0].key assert c.value == cookies[0].value
def load_account_from_cookies(self): # Get any cookies from the request cookie_data = self.headers.get('Cookie') account = None if cookie_data is not None: # Parse the cookie data parsed_cookie = SimpleCookie(cookie_data) cookie = {} # Look through cookies for a session for c in parsed_cookie.values(): if c.key == "session": # Check if the session is set and try to load an account from it if c.coded_value != "": account = load_account_from_session(c.coded_value) break return account
def requests_1_sde(one_input): try: ipt_data = str(one_input) except UnicodeDecodeError: sys.exit(0) C = SimpleCookie(ipt_data) for morsel in C.values(): cookie = morsel_to_cookie(morsel) print(cookie) ipt_data2 = "{}={}; path=/; domain=.test.com".format(ipt_data, ipt_data) C = SimpleCookie(ipt_data2) for morsel in C.values(): cookie = morsel_to_cookie(morsel) print(cookie)
class Response(object): default_status_code = STATUS.OK def __init__(self, content="", status_code=None, content_type="text/html", status_message=None, **kwargs): self.content = content self.encoding = kwargs.get("encoding", DEFAULT_ENCODING) if status_code is None: status_code = self.default_status_code self.status_code = status_code self.status_message = status_message self.headers = {} self.headers["Content-Type"] = content_type self.cookies = SimpleCookie() def __iter__(self): """WSGI Iterates response content.""" value = self.content if not hasattr(value, "__iter__") or isinstance(value, (bytes, str)): value = [value] for chunk in value: # Don't encode when already bytes or Content-Encoding set if not isinstance(chunk, bytes): chunk = chunk.encode(self.encoding) yield chunk def build_headers(self): """ Return the list of headers as two-tuples """ if not "Content-Type" in self.headers: content_type = self.content_type if self.encoding != DEFAULT_ENCODING: content_type += "; charset=%s" % self.encoding self.headers["Content-Type"] = content_type headers = list(self.headers.items()) # Append cookies headers += [("Set-Cookie", cookie.OutputString()) for cookie in self.cookies.values()] return headers def add_cookie(self, key, value, **attrs): """ Finer control over cookies. Allow specifying an Morsel arguments. """ if attrs: c = Morsel() c.set(key, value, **attrs) self.cookies[key] = c else: self.cookies[key] = value @property def status(self): """Allow custom status messages""" message = self.status_message if message is None: message = STATUS[self.status_code] return "%s %s" % (self.status_code, message)
def identity(req, resp): identity = int(time.time()) cookie = SimpleCookie() cookie['identity'] = 'USER: {}'.format(identity) for set_cookie in cookie.values(): resp.headers.add_header('Set-Cookie', set_cookie.OutputString()) return b'Go back to <a href="/">index</a> to check your identity'
def request(self, url, callback, err_callback): cookie_text = 'uuid_n_v=v1; uuid=9EB2A080B96A11EA9F28E30FF5FFF73CB5154A84C7A94D1DAB10BB8C2D31FEB8; _csrf=448d5750ef63e51bf723695e9008d2c176352dad7c0fc460f422f517baa014ba; Hm_lvt_703e94591e87be68cc8da0da7cbd0be2=1593367820; _lxsdk_cuid=172fc1f7724c8-098b7ae06bfb5b-39647b09-1fa400-172fc1f7724c8; _lxsdk=9EB2A080B96A11EA9F28E30FF5FFF73CB5154A84C7A94D1DAB10BB8C2D31FEB8; mojo-uuid=005c478c1b1c76a729dcde705c8ea14b; Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2=1593500815; __mta=147693064.1593367823108.1593500797746.1593500814850.16; _lxsdk_s=173065bd698-1d5-a44-de2%7C%7C1' cookie = SimpleCookie(cookie_text) cookie_dict = {cookie.key: cookie.value for cookie in cookie.values()} request = scrapy.Request(url=url, callback=callback, errback=err_callback) request.cookies = cookie_dict return request
def cookie (s): """ argparse: Cookie """ c = SimpleCookie (s) # for some reason the constructor does not raise an exception if the cookie # supplied is invalid. It’ll simply be empty. if len (c) != 1: raise argparse.ArgumentTypeError ('Invalid cookie') # we want a single Morsel return next (iter (c.values ()))
def get_me(cookies: SimpleCookie): request_cookies = {c.key: c.value for c in cookies.values()} resp = requests.get(API_URL + "/users/me", cookies=request_cookies) if not resp.ok: if resp.status_code == http.HTTPStatus.UNAUTHORIZED: raise tornado.web.HTTPError(http.HTTPStatus.UNAUTHORIZED) raise tornado.web.HTTPError() resp.raise_for_status() user = resp.json().get('user') return user
def getCookies(self): ''' 从字符串中格式化出字典形式的Cookies ''' items = self.data for item in items: if 'cookie' in item or 'Cookie' in item: cookies = SimpleCookie(item[7:]) return {i.key: i.value for i in cookies.values()} return {}
def _set_cookies(self, appid, headers): if appid not in self.cookies: self._init_cookies(appid) for sc in headers.get_list("Set-Cookie"): c = SimpleCookie(sc) for morsel in c.values(): if morsel.key not in ['data_bizuin', 'slave_user', 'bizuin']: if morsel.value and morsel.value != 'EXPIRED': self.cookies[appid][morsel.key] = morsel.value else: self.cookies[appid].pop(morsel.key, None)
def _handle_cookies(self, response): # type: (httplib.HTTPResponse) -> None """ Parse cookies from |HTTP| response and store for next request. :param httplib.HTTPResponse: The |HTTP| response. """ # FIXME: this cookie handling doesn't respect path, domain and expiry cookies = SimpleCookie() cookies.load(response.getheader('set-cookie', '')) self.cookies.update( dict((cookie.key, cookie.value) for cookie in cookies.values()))
class Response: """ Describes an HTTP response. Currently very simple since the actual body of the request is handled separately. """ def __init__(self): """ Create a new Response defaulting to HTML content and "200 OK" status """ self.status = "200 OK" self.headers = HeaderDict({"content-type": "text/html; charset=UTF-8"}) self.cookies = SimpleCookie() def set_content_type(self, type_): """ Sets the Content-Type header """ self.headers["content-type"] = type_ def get_content_type(self): return self.headers.get("content-type", None) def send_redirect(self, url): """ Send an HTTP redirect response to (target `url`) """ if "\n" in url or "\r" in url: raise webob.exc.HTTPInternalServerError( "Invalid redirect URL encountered.") raise webob.exc.HTTPFound(location=url, headers=self.wsgi_headeritems()) def wsgi_headeritems(self): """ Return headers in format appropriate for WSGI `start_response` """ result = self.headers.headeritems() # Add cookie to header for crumb in self.cookies.values(): header, value = str(crumb).split(': ', 1) result.append((header, value)) return result def wsgi_status(self): """ Return status line in format appropriate for WSGI `start_response` """ if isinstance(self.status, int): exception = webob.exc.status_map.get(self.status) return "%d %s" % (exception.code, exception.title) else: return self.status
def parse_cookie_str(cookie_str: str) -> Dict: sc = SimpleCookie() sc.load(cookie_str) cd = {} for morsal in sc.values(): cd["name"] = morsal.key cd["value"] = morsal.value for k, v in morsal.items(): if k in SkippedValue: continue if v: cd[k if k != "httponly" else "httpOnly"] = v return cd
def clean_cookie(self): """Only clean the cookie from headers and return self.""" if not self.is_cookie_necessary: return self headers = self.request.get('headers', {}) cookies = SimpleCookie(headers['Cookie']) for k, v in cookies.items(): new_cookie = '; '.join( [i.OutputString() for i in cookies.values() if i != v]) new_request = deepcopy(self.request) new_request['headers']['Cookie'] = new_cookie self._add_task('Cookie', k, new_request) return self
class HttpResponse: def __init__(self, body, *, status=200, content_type='text/html'): if isinstance(body, str): body = body.encode('utf-8') self._body = body self._status = int(status) self._cookies = SimpleCookie() self._headers = [ ('Content-Encoding', 'UTF-8'), ('Content-Type', content_type), ('Content-Length', str(len(body))), ] self._content_type = content_type def __call__(self, start_response): status = status_line(self._status) start_response(status, self._get_headers()) return [self._body] def set_cookie(self, name, value='', max_age=None, path='/', domain=None, secure=False, httponly=False): self._cookies[name] = value if max_age is not None: self._cookies[name]['max-age'] = max_age if not max_age: expires_date = 'Thu, 01-Jan-1970 00:00:00 GMT' else: dt = formatdate(time.time() + max_age) expires_date = '%s-%s-%s GMT' % (dt[:7], dt[8:11], dt[12:25]) self._cookies[name]['expires'] = expires_date if path is not None: self._cookies[name]['path'] = path if domain is not None: self._cookies[name]['domain'] = domain if secure: self._cookies[name]['secure'] = True if httponly: self._cookies[name]['httponly'] = True def delete_cookie(self, key, path='/', domain=None): self.set_cookie(key, max_age=0, path=path, domain=domain) def _get_headers(self): headers = [(x.encode('ascii'), y.encode('ascii')) for x, y in self._headers] for c in self._cookies.values(): headers.append((b'Set-Cookie', c.output(header='').encode('ascii'))) return headers
def _load_cookies(self, data): """将str或dict或tuple-pair加载为cookie 由于标准库的 load() 方法不支持list, 所以对list单独处理 """ if utils.like_list(data): data = collections.OrderedDict(data) simple_cookie = SimpleCookie(data) pairs = [(c.key, c.value) for c in simple_cookie.values()] pairs.sort(key=lambda item: self._find_key_pos(data, item[0])) self.update(pairs)
def _hide_request_sensitive_data(self, request): request.body = self._replace_sensitive_data(request.body) if 'Cookie' in request.headers: cookie_string = request.headers['Cookie'] cookie = SimpleCookie() cookie.load(str(cookie_string)) cookies = [ c.output(header='').strip() for c in list(cookie.values()) ] request.headers['Cookie'] = '; '.join( self._filter_cookies(cookies)) request.uri = request.uri.replace(self.real_login, self.fake_login)
def cookies(self): if self._cached_cookies is None: cookie_header = self.get_header('Cookie', default='') parser = SimpleCookie() for cookie_part in cookie_header.split('; '): try: parser.load(cookie_part) except CookieError: log.error('Invalid Cookie: %s' % cookie_part) cookies = {} for morsel in parser.values(): cookies[morsel.key] = morsel.value self._cached_cookies = cookies return self._cached_cookies.copy()
def load_account_from_cookies(self): cookie_data = self.headers.get('Cookie') account = None if (cookie_data is not None): parsed_cookie = SimpleCookie(cookie_data) cookie = {} for c in parsed_cookie.values(): cookie[c.key] = c.coded_value if "session" in cookie and cookie["session"] != "": try: account = Account.sessions[cookie.get("session")] except KeyError: pass return account
class HttpResponse: def __init__(self, body, *, status=200, content_type="text/html"): if isinstance(body, str): body = body.encode("utf-8") self._body = body self._status = int(status) self._cookies = SimpleCookie() self._headers = [ ("Content-Encoding", "UTF-8"), ("Content-Type", content_type), ("Content-Length", str(len(body))), ] self._content_type = content_type def __call__(self, start_response): status = status_line(self._status) start_response(status, self._get_headers()) return [self._body] def set_cookie(self, name, value="", max_age=None, path="/", domain=None, secure=False, httponly=False): self._cookies[name] = value if max_age is not None: self._cookies[name]["max-age"] = max_age if not max_age: expires_date = "Thu, 01-Jan-1970 00:00:00 GMT" else: dt = formatdate(time.time() + max_age) expires_date = "%s-%s-%s GMT" % (dt[:7], dt[8:11], dt[12:25]) self._cookies[name]["expires"] = expires_date if path is not None: self._cookies[name]["path"] = path if domain is not None: self._cookies[name]["domain"] = domain if secure: self._cookies[name]["secure"] = True if httponly: self._cookies[name]["httponly"] = True def delete_cookie(self, key, path="/", domain=None): self.set_cookie(key, max_age=0, path=path, domain=domain) def _get_headers(self): headers = [(x.encode("ascii"), y.encode("ascii")) for x, y in self._headers] for c in self._cookies.values(): headers.append((b"Set-Cookie", c.output(header="").encode("ascii"))) return headers
def __init__(self): self.pool = redis.ConnectionPool(host='192.168.2.74') self.r = redis.Redis(connection_pool=self.pool) # 初始话selenium self.option = Options() self.account_list = [] # self.option.add_argument('--headless') self.option.add_argument('--disable-gpu') self.option.add_argument('--ignore-certificate-errors') cookie = 'SINAGLOBAL=2079748157535.4148.1562656336661; _s_tentry=-; Apache=6138501991200.746.1578878421920; ULV=1578878421977:15:2:1:6138501991200.746.1578878421920:1577966277068; login_sid_t=4041ea8280741ef6a60c24968bf7ed52; cross_origin_proto=SSL; WBtopGlobal_register_version=307744aa77dd5677; secsys_id=d3e0da3cb4c0fb539669ba32cf3d0751; ALF=1610614524; SSOLoginState=1579078525; SCF=AncjsCf7zbAbUzNBAKOizieYzy1LkJJc5eum43dQUuxtx1pHo_67XdOiZfYQ5pr9nZip8tM5XaA6dshnDiGWE1s.; SUB=_2A25zGqMmDeRhGeRG4loZ8S_LzTmIHXVQUZPurDV8PUNbmtAfLU7mkW9NTeZINGNGUkBCRAcmRLrsnuAuL4q2BGyT; SUBP=0033WrSXqPxfM725Ws9jqgMF55529P9D9WFOfqD9fkE8ALcVFJfdDIpl5JpX5KzhUgL.FozR1KnReK2NSo-2dJLoIfQLxK-L12qL1KqLxKBLBonLB-2LxK-L1K5L12BLxK-LB-BL1KMLxKBLBo.L1-qLxK-LB.-L1hnLxK.L1-2LB.-LxK-L1K-L122LxKqL1hnL1K2LxK-L12-LB.zt; SUHB=0S7yje5GVwlj4F; wvr=6; UOR=,,v3.jqsocial.com; webim_unReadCount=%7B%22time%22%3A1579140897294%2C%22dm_pub_total%22%3A5%2C%22chat_group_client%22%3A0%2C%22allcountNum%22%3A43%2C%22msgbox%22%3A0%7D; WBStorage=42212210b087ca50|undefined' cookies = SimpleCookie(cookie) self.driver = webdriver.Chrome(options=self.option) self.driver.add_cookie({i.key: i.value for i in cookies.values()}) self.driver.set_script_timeout(1) # 页面加载超时时间 self.driver.set_page_load_timeout(10) # 微博主页 self.login_url = 'https://weibo.com/'
def __init__(self, autologin_url, auth_cookies=None, logout_url=None, splash_url=None, login_url=None, username=None, password=None, user_agent=None): self.autologin_url = autologin_url self.splash_url = splash_url self.login_url = login_url self.username = username self.password = password self.user_agent = user_agent if auth_cookies: cookies = SimpleCookie() cookies.load(auth_cookies) self.auth_cookies = [ {'name': m.key, 'value': m.value} for m in cookies.values()] self.logged_in = True else: self.auth_cookies = None self.logged_in = False self.logout_urls = set() if logout_url: self.logout_urls.add(logout_url)
def __init__(self, iterable, status, headers): self._text = None self._content = b''.join(iterable) if hasattr(iterable, 'close'): iterable.close() self._status = status self._status_code = int(status[:3]) self._headers = CiDict(headers) cookies = SimpleCookie() for name, value in headers.items(): if name.lower() == 'set-cookie': cookies.load(value) self._cookies = dict( (morsel.key, Cookie(morsel)) for morsel in cookies.values()) self._encoding = content_type_encoding( self._headers.get('content-type'))
def _check_cookies(self, vector, configs): """ Checks the vector's cookies. If values were not specified in the cookies object, then the 'Cookies' header is checked. Each cookie in the header is parsed manually. :param vector: vector dictionary :param configs: AVA configs """ cookies = vector['cookies'] # check headers if not cookies if not cookies and 'Cookie' in vector['headers']: header = vector['headers']['Cookie'] simple = SimpleCookie() # convert from 'key=value; key=value' simple.load(header) for morsel in simple.values(): cookies[morsel.key] = morsel.value # add configs cookies.update(configs['cookies'])
def authenticate_via_cookie(c_user=None, xs=None): """Authenticate with facebook via cookie This function will look for cookie saved in '.fb_cookies'. The cookies values can be overriden by the input parameters. """ global ACCESS_TOKEN authenticated = _authenticate_via_saved_token() if not authenticated: cookies = SimpleCookie() try: with open(COOKIES_FILE) as f: saved_cookies = f.read() cookies.load(saved_cookies) except IOError: pass if c_user: cookies['c_user'] = c_user if xs: cookies['xs'] = xs cookie_str = ';'.join(c.key + '=' + c.value for c in cookies.values()) request = Request( oauth_url(APP_ID, 'https://www.facebook.com/connect/login_success.html', AUTH_SCOPE), headers={'Cookie': cookie_str}) opener = urlopen(request) try: opener.read() url = opener.geturl() finally: opener.close() params = parse_qs(urlparse(url).fragment) authenticated = _save_access_token(params) return authenticated
class StreamResponse(collections.MutableMapping, HeadersMixin): _length_check = True def __init__(self, *, status=200, reason=None, headers=None): self._body = None self._keep_alive = None self._chunked = False self._compression = False self._compression_force = False self._cookies = SimpleCookie() self._req = None self._payload_writer = None self._eof_sent = False self._body_length = 0 self._state = {} if headers is not None: self._headers = CIMultiDict(headers) else: self._headers = CIMultiDict() self.set_status(status, reason) @property def prepared(self): return self._payload_writer is not None @property def task(self): return getattr(self._req, 'task', None) @property def status(self): return self._status @property def chunked(self): return self._chunked @property def compression(self): return self._compression @property def reason(self): return self._reason def set_status(self, status, reason=None, _RESPONSES=RESPONSES): assert not self.prepared, \ 'Cannot change the response status code after ' \ 'the headers have been sent' self._status = int(status) if reason is None: try: reason = _RESPONSES[self._status][0] except Exception: reason = '' self._reason = reason @property def keep_alive(self): return self._keep_alive def force_close(self): self._keep_alive = False @property def body_length(self): return self._body_length @property def output_length(self): warnings.warn('output_length is deprecated', DeprecationWarning) return self._payload_writer.buffer_size def enable_chunked_encoding(self, chunk_size=None): """Enables automatic chunked transfer encoding.""" self._chunked = True if hdrs.CONTENT_LENGTH in self._headers: raise RuntimeError("You can't enable chunked encoding when " "a content length is set") if chunk_size is not None: warnings.warn('Chunk size is deprecated #1615', DeprecationWarning) def enable_compression(self, force=None): """Enables response compression encoding.""" # Backwards compatibility for when force was a bool <0.17. if type(force) == bool: force = ContentCoding.deflate if force else ContentCoding.identity elif force is not None: assert isinstance(force, ContentCoding), ("force should one of " "None, bool or " "ContentEncoding") self._compression = True self._compression_force = force @property def headers(self): return self._headers @property def cookies(self): return self._cookies def set_cookie(self, name, value, *, expires=None, domain=None, max_age=None, path='/', secure=None, httponly=None, version=None): """Set or update response cookie. Sets new cookie or updates existent with new value. Also updates only those params which are not None. """ old = self._cookies.get(name) if old is not None and old.coded_value == '': # deleted cookie self._cookies.pop(name, None) self._cookies[name] = value c = self._cookies[name] if expires is not None: c['expires'] = expires elif c.get('expires') == 'Thu, 01 Jan 1970 00:00:00 GMT': del c['expires'] if domain is not None: c['domain'] = domain if max_age is not None: c['max-age'] = max_age elif 'max-age' in c: del c['max-age'] c['path'] = path if secure is not None: c['secure'] = secure if httponly is not None: c['httponly'] = httponly if version is not None: c['version'] = version def del_cookie(self, name, *, domain=None, path='/'): """Delete cookie. Creates new empty expired cookie. """ # TODO: do we need domain/path here? self._cookies.pop(name, None) self.set_cookie(name, '', max_age=0, expires="Thu, 01 Jan 1970 00:00:00 GMT", domain=domain, path=path) @property def content_length(self): # Just a placeholder for adding setter return super().content_length @content_length.setter def content_length(self, value): if value is not None: value = int(value) if self._chunked: raise RuntimeError("You can't set content length when " "chunked encoding is enable") self._headers[hdrs.CONTENT_LENGTH] = str(value) else: self._headers.pop(hdrs.CONTENT_LENGTH, None) @property def content_type(self): # Just a placeholder for adding setter return super().content_type @content_type.setter def content_type(self, value): self.content_type # read header values if needed self._content_type = str(value) self._generate_content_type_header() @property def charset(self): # Just a placeholder for adding setter return super().charset @charset.setter def charset(self, value): ctype = self.content_type # read header values if needed if ctype == 'application/octet-stream': raise RuntimeError("Setting charset for application/octet-stream " "doesn't make sense, setup content_type first") if value is None: self._content_dict.pop('charset', None) else: self._content_dict['charset'] = str(value).lower() self._generate_content_type_header() @property def last_modified(self, _LAST_MODIFIED=hdrs.LAST_MODIFIED): """The value of Last-Modified HTTP header, or None. This header is represented as a `datetime` object. """ httpdate = self.headers.get(_LAST_MODIFIED) if httpdate is not None: timetuple = parsedate(httpdate) if timetuple is not None: return datetime.datetime(*timetuple[:6], tzinfo=datetime.timezone.utc) return None @last_modified.setter def last_modified(self, value): if value is None: self.headers.pop(hdrs.LAST_MODIFIED, None) elif isinstance(value, (int, float)): self.headers[hdrs.LAST_MODIFIED] = time.strftime( "%a, %d %b %Y %H:%M:%S GMT", time.gmtime(math.ceil(value))) elif isinstance(value, datetime.datetime): self.headers[hdrs.LAST_MODIFIED] = time.strftime( "%a, %d %b %Y %H:%M:%S GMT", value.utctimetuple()) elif isinstance(value, str): self.headers[hdrs.LAST_MODIFIED] = value def _generate_content_type_header(self, CONTENT_TYPE=hdrs.CONTENT_TYPE): params = '; '.join("%s=%s" % i for i in self._content_dict.items()) if params: ctype = self._content_type + '; ' + params else: ctype = self._content_type self.headers[CONTENT_TYPE] = ctype def _do_start_compression(self, coding): if coding != ContentCoding.identity: self.headers[hdrs.CONTENT_ENCODING] = coding.value self._payload_writer.enable_compression(coding.value) # Compressed payload may have different content length, # remove the header self._headers.popall(hdrs.CONTENT_LENGTH, None) def _start_compression(self, request): if self._compression_force: self._do_start_compression(self._compression_force) else: accept_encoding = request.headers.get( hdrs.ACCEPT_ENCODING, '').lower() for coding in ContentCoding: if coding.value in accept_encoding: self._do_start_compression(coding) return async def prepare(self, request): if self._eof_sent: return if self._payload_writer is not None: return self._payload_writer await request._prepare_hook(self) return self._start(request) def _start(self, request, HttpVersion10=HttpVersion10, HttpVersion11=HttpVersion11, CONNECTION=hdrs.CONNECTION, DATE=hdrs.DATE, SERVER=hdrs.SERVER, CONTENT_TYPE=hdrs.CONTENT_TYPE, CONTENT_LENGTH=hdrs.CONTENT_LENGTH, SET_COOKIE=hdrs.SET_COOKIE, SERVER_SOFTWARE=SERVER_SOFTWARE, TRANSFER_ENCODING=hdrs.TRANSFER_ENCODING): self._req = request keep_alive = self._keep_alive if keep_alive is None: keep_alive = request.keep_alive self._keep_alive = keep_alive version = request.version writer = self._payload_writer = request._payload_writer headers = self._headers for cookie in self._cookies.values(): value = cookie.output(header='')[1:] headers.add(SET_COOKIE, value) if self._compression: self._start_compression(request) if self._chunked: if version != HttpVersion11: raise RuntimeError( "Using chunked encoding is forbidden " "for HTTP/{0.major}.{0.minor}".format(request.version)) writer.enable_chunking() headers[TRANSFER_ENCODING] = 'chunked' if CONTENT_LENGTH in headers: del headers[CONTENT_LENGTH] elif self._length_check: writer.length = self.content_length if writer.length is None: if version >= HttpVersion11: writer.enable_chunking() headers[TRANSFER_ENCODING] = 'chunked' if CONTENT_LENGTH in headers: del headers[CONTENT_LENGTH] else: keep_alive = False headers.setdefault(CONTENT_TYPE, 'application/octet-stream') headers.setdefault(DATE, rfc822_formatted_time()) headers.setdefault(SERVER, SERVER_SOFTWARE) # connection header if CONNECTION not in headers: if keep_alive: if version == HttpVersion10: headers[CONNECTION] = 'keep-alive' else: if version == HttpVersion11: headers[CONNECTION] = 'close' # status line status_line = 'HTTP/{}.{} {} {}\r\n'.format( version[0], version[1], self._status, self._reason) writer.write_headers(status_line, headers) return writer async def write(self, data): assert isinstance(data, (bytes, bytearray, memoryview)), \ "data argument must be byte-ish (%r)" % type(data) if self._eof_sent: raise RuntimeError("Cannot call write() after write_eof()") if self._payload_writer is None: raise RuntimeError("Cannot call write() before prepare()") await self._payload_writer.write(data) async def drain(self): assert not self._eof_sent, "EOF has already been sent" assert self._payload_writer is not None, \ "Response has not been started" warnings.warn("drain method is deprecated, use await resp.write()", DeprecationWarning, stacklevel=2) await self._payload_writer.drain() async def write_eof(self, data=b''): assert isinstance(data, (bytes, bytearray, memoryview)), \ "data argument must be byte-ish (%r)" % type(data) if self._eof_sent: return assert self._payload_writer is not None, \ "Response has not been started" await self._payload_writer.write_eof(data) self._eof_sent = True self._req = None self._body_length = self._payload_writer.output_size self._payload_writer = None def __repr__(self): if self._eof_sent: info = "eof" elif self.prepared: info = "{} {} ".format(self._req.method, self._req.path) else: info = "not prepared" return "<{} {} {}>".format(self.__class__.__name__, self.reason, info) def __getitem__(self, key): return self._state[key] def __setitem__(self, key, value): self._state[key] = value def __delitem__(self, key): del self._state[key] def __len__(self): return len(self._state) def __iter__(self): return iter(self._state) def __hash__(self): return hash(id(self))
class HTTPResponse( Plugin ): """Plugin to encapsulate HTTP response.""" implements( IHTTPResponse ) start_response = False """Response headers are already sent on the connection.""" write_buffer = [] """Either a list of byte-string buffered by write() method. Or a generator function created via chunk_generator() method.""" flush_callback = None """Flush callback subscribed using flush() method.""" finish_callback = None """Finish callback subscribed using set_finish_callback() method.""" finished = False """A request is considered finished when there is no more response data to be sent for the on-going request. This is typically indicated by flushing the response with finishing=True argument.""" def __init__( self, request ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.__init__` interface method.""" # Initialize response attributes self.statuscode = b'200' self.reason = http.client.responses[ int(self.statuscode) ] self.version = request.httpconn.version self.headers = {} self.body = b'' self.chunk_generator = None self.trailers = {} self.setcookies = SimpleCookie() # Initialize framework attributes self.request = request self.context = h.Context() self.media_type = None self.content_coding = None self.charset = self.webapp['encoding'] self.language = self.webapp['language'] # Book keeping self.httpconn = request.httpconn self.start_response = False self.write_buffer = [] self.finished = False self.flush_callback = None self.finish_callback = None #---- IHTTPResponse APIs def set_status( self, code ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.set_status` interface method.""" if isinstance(code, int) : self.statuscode = str(code).encode('utf-8') elif isinstance(code, str) : self.statuscode = code.encode('utf-8') else : self.statuscode = code return self.statuscode def set_header( self, name, value ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.set_header` interface method.""" value = value if isinstance( value, bytes ) \ else str( value ).encode('utf-8') self.headers[ name ] = value return value def add_header( self, name, value ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.add_header` interface method.""" value = value if isinstance(value,bytes) else str(value).encode('utf-8') pvalue = self.headers.get( name, b'' ) self.headers[name] = b','.join([pvalue, value]) if pvalue else None return self.headers[name] def set_trailer( self, name, value ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.set_trailer` interface method.""" value = value if isinstance(value,bytes) else str(value).encode('utf=8') self.trailers[name] = value return value def add_trailer( self, name, value ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.add_trailer` interface method.""" value = value if isinstance(value,bytes) else str(value).encode('utf-8') pvalue = self.trailers.get(name, b'') self.trailers[name] = b','.join([pvalue, value]) if pvalue else None return self.trailers[name] def set_cookie( self, name, value, **kwargs ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.set_cookie` interface method.""" return self.request.cookie.set_cookie( self.setcookies, name, value, **kwargs ) def set_secure_cookie( self, name, value, expires_days=30, **kwargs ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.set_secure_cookie` interface method.""" cookie = self.request.cookie value = cookie.create_signed_value(name, value) return cookie.set_cookie( self.setcookies, name, value, **kwargs ) def clear_cookie( self, name, path="/", domain=None ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.clear_cookie` interface method.""" value = self.setcookies[ name ] expires = dt.datetime.utcnow() - dt.timedelta(days=365) self.request.cookie.set_cookie( self.setcookies, name, "", path=path, expires=expires, domain=domain ) return value def clear_all_cookies(self): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.clear_all_cookies` interface method.""" list( map( self.clear_cookie, self.setcookies.keys() )) return None def set_finish_callback(self, callback): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.set_finish_callback` interface method.""" self.finish_callback = callback def has_finished( self ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.has_finished` interface method.""" return self.finished def isstarted( self ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.isstarted` interface method.""" return self.start_response def ischunked( self ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.ischunked` interface method.""" vals = dict( h.parse_transfer_encoding( self.headers.get( 'transfer_encoding', b'' ))).keys() return b'chunked' in list( vals ) def write( self, data ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.write` interface method.""" if self.has_finished() : raise Exception( "Cannot write() after the response is finished." ) data = data.encode(self.charset) if isinstance(data, str) else data self.write_buffer = self.write_buffer or [] self.write_buffer.append( data ) def flush( self, finishing=False, callback=None ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.flush` interface method.""" if callback : self.flush_callback = callback self.finished = finishing if callable( self.write_buffer ) : self._flush_chunk( finishing ) else : self._flush_body( finishing ) def httperror( self, statuscode=b'500', message=b'' ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.httperror` interface method.""" self.statuscode = statuscode self.write( message ) if message else None self.flush( finishing=True ) _renderers = { '.ttl' : 'tayra.TTLCompiler', } _renderer_plugins = { } def render( self, *args, **kwargs ): """:meth:`pluggdapps.interfaces.IHTTPResponse.render` interface method. positional argument, ``request``, Instance of plugin implement :class:`pluggdapps.web.interfaces.IHTTPRequest` interface. ``context``, Dictionary of context information to be passed. keyword arguments, ``file``, Template file to be used for rendering. ``text``, Template text to be used for rendering. ``ITemplate``, :class:`ITemplate` plugin to use for rendering. This argument must be in canonical form of plugin's name. If ``file`` keyword argument is passed, this method will resolve the correct renderer plugin based on file-extension. if ``text`` keyword argument is passed, better pass the ``ITemplate`` argument as well. """ request, context = args[0], args[1] renderer = kwargs.get( 'ITemplate', None ) if renderer is None : tfile = kwargs.get( 'file', '' ) _, ext = splitext( tfile ) renderer = self._renderers.get( ext, None ) if ext else None # If in debug mode enable ttl file reloading. tfile = h.abspath_from_asset_spec( tfile ) if self['debug'] and isfile( tfile ): self.pa._monitoredfiles.append( tfile ) if renderer in self._renderer_plugins : plugin = self._renderer_plugins[ renderer ] elif renderer : plugin = self.qp( ITemplate, renderer ) else : plugin = None if plugin : self.media_type = 'text/html' self._renderer_plugins.setdefault( renderer, plugin ) return plugin.render( context, **kwargs ) else : raise Exception('Unknown renderer') def chunk_generator( self, callback, request, c ): """:meth:`pluggdapps.web.webinterfaces.IHTTPResponse.chunk_generator` interface method.""" class ChunkGenerator( object ): def __iter__( self ): return self def next( self ): return callback( request, c ) return ChunkGenerator() #---- Local functions def _try_start_headers( self, finishing=True ) : """Generate default headers for this response. And return the byte-string of response header to write. This can be overriden by view callable attributes.""" if self.start_response : return b'' self.start_response = True stline = self._status_line() return self._header_data( self.headers, stline=stline ) def _status_line( self ): code = self.statuscode reason = http.client.responses[ int(code) ].encode( 'utf-8' ) return b' '.join([ self.version, code, reason ]) def _header_data( self, headers, stline=b'' ): # TODO : 3 header field types are specifically prohibited from # appearing as a trailer field: Transfer-Encoding, Content-Length and # Trailer. lines = [ stline ] if stline else [] for n, v in headers.items() : nC = h.hdr_str2camelcase.get( n, None ) if nC == None : n = n.encode('utf-8') nC = b'-'.join([ x.capitalize() for x in n.split('_') ]) lines.append( nC + b': ' + v ) [ lines.append( b"Set-Cookie: " + cookie.OutputString() ) for c in self.setcookies.values() ] return b"\r\n".join(lines) + b"\r\n\r\n" def _flush_body( self, finishing ): data = b''.join( self.write_buffer ) for tr in self.webapp.out_transformers : data = tr.transform( self.request, data, finishing=finishing ) if self._if_etag() : self.body = data else : self.body = b'' self.set_header( "content_length", len(self.body) ) data = self._try_start_headers( finishing=finishing ) if self.request.method == b'HEAD' : pass elif self.body : data += self.body self.httpconn.write( data, callback=self._onflush ) self.write_buffer = [] def _flush_chunk( self, finishing ): self.add_headers( 'transfer_encoding', 'chunked' ) data = self._try_start_headers( finishing=finishing ) chunk = self.write_buffer( self.request, self.c ) for tr in self.webapp.out_transformers : chunk = tr.transform( self.request, chunk, finishing=finishing ) if chunk : data += hex(len(chunk)).encode('utf-8') + b'\r\n' + chunk + b'\r\n' else : data += b'0\r\n' if self.trailers : data += self._header_data( self.trailers ) self.httpconn.write( data, callback=self._onflush ) def _if_etag( self ): etag = self.headers.get('etag', '') if self.ischunked() == False and etag : im = self.request.headers.get( "if_match", b'' ).strip() inm = self.request.headers.get( "if_none_match", b'' ).strip() if ( (im and im.find( etag ) == -1) or (inm and inm.find( etag ) != -1) ) : self.set_status( b'304' ) return False return True def _onflush( self ): if self.flush_callback : callback, self.flush_callback = self.flush_callback, None callback() if self.has_finished() : self._onfinish() def _onfinish( self ): if self.finish_callback : callback, self.finish_callback = self.finish_callback, None callback() self.request.onfinish() #---- ISettings interface methods @classmethod def default_settings( cls ): """:meth:`pluggdapps.plugin.interfaces.ISettings.default_settings` interface method.""" return _ds1
class WSGIResponse(object): """A basic HTTP response with content, headers, and out-bound cookies The class variable ``defaults`` specifies default values for ``content_type``, ``charset`` and ``errors``. These can be overridden for the current request via the registry. """ defaults = StackedObjectProxy( default=dict(content_type='text/html', charset='utf-8', errors='strict', headers={'Cache-Control': 'no-cache'})) def __init__(self, content=b'', mimetype=None, code=200): self._iter = None self._is_str_iter = True self.content = content self.headers = HeaderDict() self.cookies = SimpleCookie() self.status_code = code defaults = self.defaults._current_obj() if not mimetype: mimetype = defaults.get('content_type', 'text/html') charset = defaults.get('charset') if charset: mimetype = '%s; charset=%s' % (mimetype, charset) self.headers.update(defaults.get('headers', {})) self.headers['Content-Type'] = mimetype self.errors = defaults.get('errors', 'strict') def __str__(self): """Returns a rendition of the full HTTP message, including headers. When the content is an iterator, the actual content is replaced with the output of str(iterator) (to avoid exhausting the iterator). """ if self._is_str_iter: content = ''.join(self.get_content()) else: content = str(self.content) return '\n'.join(['%s: %s' % (key, value) for key, value in self.headers.headeritems()]) \ + '\n\n' + content def __call__(self, environ, start_response): """Convenience call to return output and set status information Conforms to the WSGI interface for calling purposes only. Example usage: .. code-block:: python def wsgi_app(environ, start_response): response = WSGIResponse() response.write("Hello world") response.headers['Content-Type'] = 'latin1' return response(environ, start_response) """ status_text = STATUS_CODE_TEXT[self.status_code] status = '%s %s' % (self.status_code, status_text) response_headers = self.headers.headeritems() for c in self.cookies.values(): response_headers.append(('Set-Cookie', c.output(header=''))) start_response(status, response_headers) is_file = isinstance(self.content, file) if 'wsgi.file_wrapper' in environ and is_file: return environ['wsgi.file_wrapper'](self.content) elif is_file: return iter(lambda: self.content.read(), '') return self.get_content() def determine_charset(self): """ Determine the encoding as specified by the Content-Type's charset parameter, if one is set """ charset_match = _CHARSET_RE.search(self.headers.get( 'Content-Type', '')) if charset_match: return charset_match.group(1) def has_header(self, header): """ Case-insensitive check for a header """ warnings.warn( 'WSGIResponse.has_header is deprecated, use ' 'WSGIResponse.headers.has_key instead', DeprecationWarning, 2) return self.headers.has_key(header) def set_cookie(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=None): """ Define a cookie to be sent via the outgoing HTTP headers """ self.cookies[key] = value for var_name, var_value in [('max_age', max_age), ('path', path), ('domain', domain), ('secure', secure), ('expires', expires), ('httponly', httponly)]: if var_value is not None and var_value is not False: self.cookies[key][var_name.replace('_', '-')] = var_value def delete_cookie(self, key, path='/', domain=None): """ Notify the browser the specified cookie has expired and should be deleted (via the outgoing HTTP headers) """ self.cookies[key] = '' if path is not None: self.cookies[key]['path'] = path if domain is not None: self.cookies[key]['domain'] = domain self.cookies[key]['expires'] = 0 self.cookies[key]['max-age'] = 0 def _set_content(self, content): if not isinstance(content, (six.binary_type, six.text_type)): self._iter = content if isinstance(content, list): self._is_str_iter = True else: self._is_str_iter = False else: self._iter = [content] self._is_str_iter = True content = property(lambda self: self._iter, _set_content, doc='Get/set the specified content, where content can ' 'be: a string, a list of strings, a generator function ' 'that yields strings, or an iterable object that ' 'produces strings.') def get_content(self): """ Returns the content as an iterable of strings, encoding each element of the iterator from a Unicode object if necessary. """ charset = self.determine_charset() if charset: return encode_unicode_app_iter(self.content, charset, self.errors) else: return self.content def wsgi_response(self): """ Return this WSGIResponse as a tuple of WSGI formatted data, including: (status, headers, iterable) """ status_text = STATUS_CODE_TEXT[self.status_code] status = '%s %s' % (self.status_code, status_text) response_headers = self.headers.headeritems() for c in self.cookies.values(): response_headers.append(('Set-Cookie', c.output(header=''))) return status, response_headers, self.get_content() # The remaining methods partially implement the file-like object interface. # See http://docs.python.org/lib/bltin-file-objects.html def write(self, content): if not self._is_str_iter: raise IOError( "This %s instance's content is not writable: (content " 'is an iterator)' % self.__class__.__name__) self.content.append(content) def flush(self): pass def tell(self): if not self._is_str_iter: raise IOError( 'This %s instance cannot tell its position: (content ' 'is an iterator)' % self.__class__.__name__) return sum([len(chunk) for chunk in self._iter]) ######################################## ## Content-type and charset def charset__get(self): """ Get/set the charset (in the Content-Type) """ header = self.headers.get('content-type') if not header: return None match = _CHARSET_RE.search(header) if match: return match.group(1) return None def charset__set(self, charset): if charset is None: del self.charset return try: header = self.headers.pop('content-type') except KeyError: raise AttributeError( "You cannot set the charset when no content-type is defined") match = _CHARSET_RE.search(header) if match: header = header[:match.start()] + header[match.end():] header += '; charset=%s' % charset self.headers['content-type'] = header def charset__del(self): try: header = self.headers.pop('content-type') except KeyError: # Don't need to remove anything return match = _CHARSET_RE.search(header) if match: header = header[:match.start()] + header[match.end():] self.headers['content-type'] = header charset = property(charset__get, charset__set, charset__del, doc=charset__get.__doc__) def content_type__get(self): """ Get/set the Content-Type header (or None), *without* the charset or any parameters. If you include parameters (or ``;`` at all) when setting the content_type, any existing parameters will be deleted; otherwise they will be preserved. """ header = self.headers.get('content-type') if not header: return None return header.split(';', 1)[0] def content_type__set(self, value): if ';' not in value: header = self.headers.get('content-type', '') if ';' in header: params = header.split(';', 1)[1] value += ';' + params self.headers['content-type'] = value def content_type__del(self): try: del self.headers['content-type'] except KeyError: pass content_type = property(content_type__get, content_type__set, content_type__del, doc=content_type__get.__doc__)
class StreamResponse(collections.MutableMapping, HeadersMixin): _length_check = True def __init__(self, *, status=200, reason=None, headers=None): self._body = None self._keep_alive = None self._chunked = False self._compression = False self._compression_force = None self._cookies = SimpleCookie() self._req = None self._payload_writer = None self._eof_sent = False self._body_length = 0 self._state = {} if headers is not None: self._headers = CIMultiDict(headers) else: self._headers = CIMultiDict() self.set_status(status, reason) @property def prepared(self): return self._payload_writer is not None @property def task(self): return getattr(self._req, 'task', None) @property def status(self): return self._status @property def chunked(self): return self._chunked @property def compression(self): return self._compression @property def reason(self): return self._reason def set_status(self, status, reason=None, _RESPONSES=RESPONSES): assert not self.prepared, \ 'Cannot change the response status code after ' \ 'the headers have been sent' self._status = int(status) if reason is None: try: reason = _RESPONSES[self._status][0] except Exception: reason = '' self._reason = reason @property def keep_alive(self): return self._keep_alive def force_close(self): self._keep_alive = False @property def body_length(self): return self._body_length @property def output_length(self): warnings.warn('output_length is deprecated', DeprecationWarning) return self._payload_writer.buffer_size def enable_chunked_encoding(self, chunk_size=None): """Enables automatic chunked transfer encoding.""" self._chunked = True if hdrs.CONTENT_LENGTH in self._headers: raise RuntimeError("You can't enable chunked encoding when " "a content length is set") if chunk_size is not None: warnings.warn('Chunk size is deprecated #1615', DeprecationWarning) def enable_compression(self, force=None): """Enables response compression encoding.""" # Backwards compatibility for when force was a bool <0.17. if type(force) == bool: force = ContentCoding.deflate if force else ContentCoding.identity elif force is not None: assert isinstance(force, ContentCoding), ("force should one of " "None, bool or " "ContentEncoding") self._compression = True self._compression_force = force @property def headers(self): return self._headers @property def cookies(self): return self._cookies def set_cookie(self, name, value, *, expires=None, domain=None, max_age=None, path='/', secure=None, httponly=None, version=None): """Set or update response cookie. Sets new cookie or updates existent with new value. Also updates only those params which are not None. """ old = self._cookies.get(name) if old is not None and old.coded_value == '': # deleted cookie self._cookies.pop(name, None) self._cookies[name] = value c = self._cookies[name] if expires is not None: c['expires'] = expires elif c.get('expires') == 'Thu, 01 Jan 1970 00:00:00 GMT': del c['expires'] if domain is not None: c['domain'] = domain if max_age is not None: c['max-age'] = max_age elif 'max-age' in c: del c['max-age'] c['path'] = path if secure is not None: c['secure'] = secure if httponly is not None: c['httponly'] = httponly if version is not None: c['version'] = version def del_cookie(self, name, *, domain=None, path='/'): """Delete cookie. Creates new empty expired cookie. """ # TODO: do we need domain/path here? self._cookies.pop(name, None) self.set_cookie(name, '', max_age=0, expires="Thu, 01 Jan 1970 00:00:00 GMT", domain=domain, path=path) @property def content_length(self): # Just a placeholder for adding setter return super().content_length @content_length.setter def content_length(self, value): if value is not None: value = int(value) if self._chunked: raise RuntimeError("You can't set content length when " "chunked encoding is enable") self._headers[hdrs.CONTENT_LENGTH] = str(value) else: self._headers.pop(hdrs.CONTENT_LENGTH, None) @property def content_type(self): # Just a placeholder for adding setter return super().content_type @content_type.setter def content_type(self, value): self.content_type # read header values if needed self._content_type = str(value) self._generate_content_type_header() @property def charset(self): # Just a placeholder for adding setter return super().charset @charset.setter def charset(self, value): ctype = self.content_type # read header values if needed if ctype == 'application/octet-stream': raise RuntimeError("Setting charset for application/octet-stream " "doesn't make sense, setup content_type first") if value is None: self._content_dict.pop('charset', None) else: self._content_dict['charset'] = str(value).lower() self._generate_content_type_header() @property def last_modified(self, _LAST_MODIFIED=hdrs.LAST_MODIFIED): """The value of Last-Modified HTTP header, or None. This header is represented as a `datetime` object. """ httpdate = self.headers.get(_LAST_MODIFIED) if httpdate is not None: timetuple = parsedate(httpdate) if timetuple is not None: return datetime.datetime(*timetuple[:6], tzinfo=datetime.timezone.utc) return None @last_modified.setter def last_modified(self, value): if value is None: self.headers.pop(hdrs.LAST_MODIFIED, None) elif isinstance(value, (int, float)): self.headers[hdrs.LAST_MODIFIED] = time.strftime( "%a, %d %b %Y %H:%M:%S GMT", time.gmtime(math.ceil(value))) elif isinstance(value, datetime.datetime): self.headers[hdrs.LAST_MODIFIED] = time.strftime( "%a, %d %b %Y %H:%M:%S GMT", value.utctimetuple()) elif isinstance(value, str): self.headers[hdrs.LAST_MODIFIED] = value def _generate_content_type_header(self, CONTENT_TYPE=hdrs.CONTENT_TYPE): params = '; '.join("%s=%s" % i for i in self._content_dict.items()) if params: ctype = self._content_type + '; ' + params else: ctype = self._content_type self.headers[CONTENT_TYPE] = ctype def _do_start_compression(self, coding): if coding != ContentCoding.identity: self.headers[hdrs.CONTENT_ENCODING] = coding.value self._payload_writer.enable_compression(coding.value) # Compressed payload may have different content length, # remove the header self._headers.popall(hdrs.CONTENT_LENGTH, None) def _start_compression(self, request): if self._compression_force: self._do_start_compression(self._compression_force) else: accept_encoding = request.headers.get(hdrs.ACCEPT_ENCODING, '').lower() for coding in ContentCoding: if coding.value in accept_encoding: self._do_start_compression(coding) return async def prepare(self, request): if self._eof_sent: return if self._payload_writer is not None: return self._payload_writer await request._prepare_hook(self) return await self._start(request) async def _start(self, request, HttpVersion10=HttpVersion10, HttpVersion11=HttpVersion11, CONNECTION=hdrs.CONNECTION, DATE=hdrs.DATE, SERVER=hdrs.SERVER, CONTENT_TYPE=hdrs.CONTENT_TYPE, CONTENT_LENGTH=hdrs.CONTENT_LENGTH, SET_COOKIE=hdrs.SET_COOKIE, SERVER_SOFTWARE=SERVER_SOFTWARE, TRANSFER_ENCODING=hdrs.TRANSFER_ENCODING): self._req = request keep_alive = self._keep_alive if keep_alive is None: keep_alive = request.keep_alive self._keep_alive = keep_alive version = request.version writer = self._payload_writer = request._payload_writer headers = self._headers for cookie in self._cookies.values(): value = cookie.output(header='')[1:] headers.add(SET_COOKIE, value) if self._compression: self._start_compression(request) if self._chunked: if version != HttpVersion11: raise RuntimeError("Using chunked encoding is forbidden " "for HTTP/{0.major}.{0.minor}".format( request.version)) writer.enable_chunking() headers[TRANSFER_ENCODING] = 'chunked' if CONTENT_LENGTH in headers: del headers[CONTENT_LENGTH] elif self._length_check: writer.length = self.content_length if writer.length is None: if version >= HttpVersion11: writer.enable_chunking() headers[TRANSFER_ENCODING] = 'chunked' if CONTENT_LENGTH in headers: del headers[CONTENT_LENGTH] else: keep_alive = False headers.setdefault(CONTENT_TYPE, 'application/octet-stream') headers.setdefault(DATE, rfc822_formatted_time()) headers.setdefault(SERVER, SERVER_SOFTWARE) # connection header if CONNECTION not in headers: if keep_alive: if version == HttpVersion10: headers[CONNECTION] = 'keep-alive' else: if version == HttpVersion11: headers[CONNECTION] = 'close' # status line status_line = 'HTTP/{}.{} {} {}\r\n'.format(version[0], version[1], self._status, self._reason) await writer.write_headers(status_line, headers) return writer async def write(self, data): assert isinstance(data, (bytes, bytearray, memoryview)), \ "data argument must be byte-ish (%r)" % type(data) if self._eof_sent: raise RuntimeError("Cannot call write() after write_eof()") if self._payload_writer is None: raise RuntimeError("Cannot call write() before prepare()") await self._payload_writer.write(data) async def drain(self): assert not self._eof_sent, "EOF has already been sent" assert self._payload_writer is not None, \ "Response has not been started" warnings.warn("drain method is deprecated, use await resp.write()", DeprecationWarning, stacklevel=2) await self._payload_writer.drain() async def write_eof(self, data=b''): assert isinstance(data, (bytes, bytearray, memoryview)), \ "data argument must be byte-ish (%r)" % type(data) if self._eof_sent: return assert self._payload_writer is not None, \ "Response has not been started" await self._payload_writer.write_eof(data) self._eof_sent = True self._req = None self._body_length = self._payload_writer.output_size self._payload_writer = None def __repr__(self): if self._eof_sent: info = "eof" elif self.prepared: info = "{} {} ".format(self._req.method, self._req.path) else: info = "not prepared" return "<{} {} {}>".format(self.__class__.__name__, self.reason, info) def __getitem__(self, key): return self._state[key] def __setitem__(self, key, value): self._state[key] = value def __delitem__(self, key): del self._state[key] def __len__(self): return len(self._state) def __iter__(self): return iter(self._state) def __hash__(self): return hash(id(self))
class WSGIResponse(object): """A basic HTTP response with content, headers, and out-bound cookies The class variable ``defaults`` specifies default values for ``content_type``, ``charset`` and ``errors``. These can be overridden for the current request via the registry. """ defaults = StackedObjectProxy( default=dict(content_type='text/html', charset='utf-8', errors='strict', headers={'Cache-Control':'no-cache'}) ) def __init__(self, content=b'', mimetype=None, code=200): self._iter = None self._is_str_iter = True self.content = content self.headers = HeaderDict() self.cookies = SimpleCookie() self.status_code = code defaults = self.defaults._current_obj() if not mimetype: mimetype = defaults.get('content_type', 'text/html') charset = defaults.get('charset') if charset: mimetype = '%s; charset=%s' % (mimetype, charset) self.headers.update(defaults.get('headers', {})) self.headers['Content-Type'] = mimetype self.errors = defaults.get('errors', 'strict') def __str__(self): """Returns a rendition of the full HTTP message, including headers. When the content is an iterator, the actual content is replaced with the output of str(iterator) (to avoid exhausting the iterator). """ if self._is_str_iter: content = ''.join(self.get_content()) else: content = str(self.content) return '\n'.join(['%s: %s' % (key, value) for key, value in self.headers.headeritems()]) \ + '\n\n' + content def __call__(self, environ, start_response): """Convenience call to return output and set status information Conforms to the WSGI interface for calling purposes only. Example usage: .. code-block:: python def wsgi_app(environ, start_response): response = WSGIResponse() response.write("Hello world") response.headers['Content-Type'] = 'latin1' return response(environ, start_response) """ status_text = STATUS_CODE_TEXT[self.status_code] status = '%s %s' % (self.status_code, status_text) response_headers = self.headers.headeritems() for c in self.cookies.values(): response_headers.append(('Set-Cookie', c.output(header=''))) start_response(status, response_headers) is_file = isinstance(self.content, file) if 'wsgi.file_wrapper' in environ and is_file: return environ['wsgi.file_wrapper'](self.content) elif is_file: return iter(lambda: self.content.read(), '') return self.get_content() def determine_charset(self): """ Determine the encoding as specified by the Content-Type's charset parameter, if one is set """ charset_match = _CHARSET_RE.search(self.headers.get('Content-Type', '')) if charset_match: return charset_match.group(1) def has_header(self, header): """ Case-insensitive check for a header """ warnings.warn('WSGIResponse.has_header is deprecated, use ' 'WSGIResponse.headers.has_key instead', DeprecationWarning, 2) return self.headers.has_key(header) def set_cookie(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=None): """ Define a cookie to be sent via the outgoing HTTP headers """ self.cookies[key] = value for var_name, var_value in [ ('max_age', max_age), ('path', path), ('domain', domain), ('secure', secure), ('expires', expires), ('httponly', httponly)]: if var_value is not None and var_value is not False: self.cookies[key][var_name.replace('_', '-')] = var_value def delete_cookie(self, key, path='/', domain=None): """ Notify the browser the specified cookie has expired and should be deleted (via the outgoing HTTP headers) """ self.cookies[key] = '' if path is not None: self.cookies[key]['path'] = path if domain is not None: self.cookies[key]['domain'] = domain self.cookies[key]['expires'] = 0 self.cookies[key]['max-age'] = 0 def _set_content(self, content): if not isinstance(content, (six.binary_type, six.text_type)): self._iter = content if isinstance(content, list): self._is_str_iter = True else: self._is_str_iter = False else: self._iter = [content] self._is_str_iter = True content = property(lambda self: self._iter, _set_content, doc='Get/set the specified content, where content can ' 'be: a string, a list of strings, a generator function ' 'that yields strings, or an iterable object that ' 'produces strings.') def get_content(self): """ Returns the content as an iterable of strings, encoding each element of the iterator from a Unicode object if necessary. """ charset = self.determine_charset() if charset: return encode_unicode_app_iter(self.content, charset, self.errors) else: return self.content def wsgi_response(self): """ Return this WSGIResponse as a tuple of WSGI formatted data, including: (status, headers, iterable) """ status_text = STATUS_CODE_TEXT[self.status_code] status = '%s %s' % (self.status_code, status_text) response_headers = self.headers.headeritems() for c in self.cookies.values(): response_headers.append(('Set-Cookie', c.output(header=''))) return status, response_headers, self.get_content() # The remaining methods partially implement the file-like object interface. # See http://docs.python.org/lib/bltin-file-objects.html def write(self, content): if not self._is_str_iter: raise IOError("This %s instance's content is not writable: (content " 'is an iterator)' % self.__class__.__name__) self.content.append(content) def flush(self): pass def tell(self): if not self._is_str_iter: raise IOError('This %s instance cannot tell its position: (content ' 'is an iterator)' % self.__class__.__name__) return sum([len(chunk) for chunk in self._iter]) ######################################## ## Content-type and charset def charset__get(self): """ Get/set the charset (in the Content-Type) """ header = self.headers.get('content-type') if not header: return None match = _CHARSET_RE.search(header) if match: return match.group(1) return None def charset__set(self, charset): if charset is None: del self.charset return try: header = self.headers.pop('content-type') except KeyError: raise AttributeError( "You cannot set the charset when no content-type is defined") match = _CHARSET_RE.search(header) if match: header = header[:match.start()] + header[match.end():] header += '; charset=%s' % charset self.headers['content-type'] = header def charset__del(self): try: header = self.headers.pop('content-type') except KeyError: # Don't need to remove anything return match = _CHARSET_RE.search(header) if match: header = header[:match.start()] + header[match.end():] self.headers['content-type'] = header charset = property(charset__get, charset__set, charset__del, doc=charset__get.__doc__) def content_type__get(self): """ Get/set the Content-Type header (or None), *without* the charset or any parameters. If you include parameters (or ``;`` at all) when setting the content_type, any existing parameters will be deleted; otherwise they will be preserved. """ header = self.headers.get('content-type') if not header: return None return header.split(';', 1)[0] def content_type__set(self, value): if ';' not in value: header = self.headers.get('content-type', '') if ';' in header: params = header.split(';', 1)[1] value += ';' + params self.headers['content-type'] = value def content_type__del(self): try: del self.headers['content-type'] except KeyError: pass content_type = property(content_type__get, content_type__set, content_type__del, doc=content_type__get.__doc__)
def trans_cookie(c*k): '''将字符串转换为字典形式的cookies''' cookie = SimpleCookie(c*k) return {i.key: i.value for i in cookie.values()}
def accept_cookies_for_response(self, cookies: HTTPCookies): """ Insert all the cookies as response set cookie headers. """ for cookie_morsel in cookies.values(): self.add("set-cookie", cookie_morsel.OutputString())
class Response(object): default_status_code = STATUS.OK def __init__(self, content='', status_code=None, content_type='text/html', status_message=None, **kwargs): self.content = content self.encoding = kwargs.get('encoding', DEFAULT_ENCODING) if status_code is None: status_code = self.default_status_code self.status_code = status_code self.status_message = status_message self.headers = {} self.headers['Content-Type'] = content_type self.cookies = SimpleCookie() def __iter__(self): '''WSGI Iterates response content.''' value = self.content if not hasattr(value, '__iter__') or isinstance(value, (bytes, str)): value = [value] for chunk in value: # Don't encode when already bytes or Content-Encoding set if not isinstance(chunk, bytes): chunk = chunk.encode(self.encoding) yield chunk def build_headers(self): ''' Return the list of headers as two-tuples ''' if not 'Content-Type' in self.headers: content_type = self.content_type if self.encoding != DEFAULT_ENCODING: content_type += '; charset=%s' % self.encoding self.headers['Content-Type'] = content_type headers = list(self.headers.items()) # Append cookies headers += [ ('Set-Cookie', cookie.OutputString()) for cookie in self.cookies.values() ] return headers def add_cookie(self, key, value, **attrs): ''' Finer control over cookies. Allow specifying an Morsel arguments. ''' if attrs: c = Morsel() c.set(key, value, **attrs) self.cookies[key] = c else: self.cookies[key] = value @property def status(self): '''Allow custom status messages''' message = self.status_message if message is None: message = STATUS[self.status_code] return '%s %s' % (self.status_code, message) def close(self): '''Close our content if it can be. WSGI must call close if the response object has one. ''' if callable(getattr(self.content, 'read', None)): self.content.close()
class RavelloClient(object): """A client for the Ravello API. The client is a thin wrapper around the Ravello RESTful API. The client manages a single HTTPS connection, and implements login, redirect and retry functionality. A single generic :meth:`request` method is provided to issue API requests. On top of this, most existing RESTful API calls are mapped as methods on this class. These mapped methods are simple wrappers around the generic :meth:`request` method. Some general comments on this mapping: * The calls are named "<method>_<resource>", for example ":meth:`create_keypair`" and ":meth:`get_blueprints`". A method is always an English verb, while a resource is can be a singular or plural Englush noun. * The standard methods are "get", "create", "update" and "delete". Not all methods are defined for all resources, and some resources have additional methods. * The available resources are "application", "blueprint", "image", "keypair" and "vm". The plural versions of these exist as well. * There is no client-side object model. The return value from any API call is simply the parsed JSON response. * Resources are returned as a dict or a list of dicts. A dict always represents a single object, and its key/value pairs correspond to the object's attributes. Lists always represents multiple objects. * Objects are identifed by a numeric ID, which is always the key "id". * All methods that accept an object ID either accept this parameter as a simple Python int, or alternatively as a dict with an "id" key containing the ID. In the latter case, the dict is typically returned previsouly by another API call. * HTTP response codes in the 4xx or 5xx range are considered errors, and are turned into :class:`RavelloError` exceptions (except for 404 which results in a response of ``None``). """ default_url = 'https://cloud.ravellosystems.com/api/v1' default_timeout = 60 default_retries = 3 default_redirects = 3 def __init__(self, username=None, password=None, url=None, timeout=None, retries=None, proxy_url=None): """Create a new client. The *username* and *password* parameters specify the credentials to use when connecting to the API. The *timeout* and *retries* parameters specify the default network system call time timeout and maximum number of retries respectively. """ self._username = username self._password = password self.timeout = timeout if timeout is not None else self.default_timeout self.retries = retries if retries is not None else self.default_retries self.redirects = self.default_redirects self._logger = logging.getLogger('ravello') self._autologin = True self._cookies = None self._user_info = None self._set_url(url or self.default_url) self._proxies = {} if proxy_url is not None: self._proxies = {"http": proxy_url, "https": proxy_url} @property def url(self): """The parsed URL of the API endpoint, which is a :class:`urllib.parse.SplitResult` instance.""" return self._url @property def connected(self): """Whether or not the client is connected to the API.""" return self._cookies is not None @property def have_credentials(self): """Whether or not credentials are available.""" return self._username is not None and self._password is not None @property def logged_in(self): """Whether or not the client is logged in.""" return self._cookies is not None @property def user_info(self): """Return information about the current logged-in user.""" return self._user_info def _set_url(self, url): if self.connected: raise RuntimeError('cannot change URL when connected') self._url = urlsplit2(url) def connect(self, url=None, proxy_url=None): """Connect to the API. It is not mandatory to call this method. If this method is not called, the client will automatically connect when required. """ if url is not None: self._set_url(url) if proxy_url is not None: self._proxies = {"http": proxy_url, "https": proxy_url} def login(self, username=None, password=None): """Login to the API. This method performs a login to the API, and store the resulting authentication cookie in memory. It is not mandatory to call this method. If this method is not called, the client will automatically login when required. """ if self.logged_in: raise RuntimeError('already logged in') if username is not None: self._username = username if password is not None: self._password = password self._login() def _login(self): if not self.have_credentials: raise RuntimeError('no credentials set') self._logger.debug('performing a username/password login') self._autologin = False auth = '{0}:{1}'.format(self._username, self._password) auth = base64.b64encode(auth.encode('ascii')).decode('ascii') headers = [('Authorization', 'Basic {0}'.format(auth))] response = self._request('POST', '/login', b'', headers) self._cookies = SimpleCookie() self._cookies.load(response.headers.get('Set-Cookie')) self._autologin = True self._user_info = response.entity def logout(self): """Logout from the API. This invalidates the authentication cookie.""" if not self.logged_in: return self.request('POST', '/logout') self._cookies = None def close(self): """Close the connection to the API.""" if not self.connected: return self._cookies = None # The request() method is the main function. All other methods are a small # shim on top of this. def request(self, method, path, entity=None, headers=None): """Issues a request to the API. The parsed entity is returned, or a :class:`RavelloError` exception is raised on error. This method can be used in case a certain API call has not yet been added as a method. """ body = json.dumps(entity).encode('utf8') if entity is not None else b'' headers = headers if headers is not None else [] response = self._request(method, path, body, headers) return response.entity def _request(self, method, path, body=b'', headers=None): rpath = self._url.path + path abpath = self.default_url + path hdict = {'Accept': 'application/json'} for key, value in headers: hdict[key] = value if body: hdict['Content-Type'] = 'application/json' retries = redirects = 0 while retries < self.retries and redirects < self.redirects: if not self.logged_in and self.have_credentials and self._autologin: self._login() if self._cookies: cookies = ['{0}={1}'.format(c.key, c.coded_value) for c in self._cookies.values()] hdict['Cookie'] = '; '.join(cookies) try: self._logger.debug('request: {0} {1}'.format(method, rpath)) response = http_methods[method](abpath, data=body, headers=hdict, proxies=self._proxies, timeout=self.timeout) status = response.status_code ctype = response.headers.get('Content-Type') if ctype == 'application/json': entity = response.json() else: entity = None self._logger.debug('response: {0} ({1})'.format(status, ctype)) if 200 <= status < 299: if isinstance(entity, dict) and entity.get('id'): if response.headers.get('Content-Location'): href = urlsplit2(response.headers.get('Content-Location')).path elif response.headers.get('Location'): href = urlsplit2(response.headers.get('Location')).path elif method == 'POST': # missing Location header e.g. with /pubkeys href = '{0}/{1}'.format(abpath, entity['id']) else: href = abpath entity['_href'] = href[len(self._url.path):] elif isinstance(entity, list): for elem in entity: if 'id' in elem: elem['_href'] = '{0}/{1}'.format(path, elem['id']) elif 300 <= status < 399: loc = response.headers.get('Location') if loc is None: raise RavelloError('no location for {0} response'.format(status)) if loc.startswith('/'): rpath = loc else: url = urlsplit2(loc) if url.netloc != self._url.netloc: raise RavelloError('will not chase referral to {0}'.format(loc)) rpath = url.path redirects += 1 elif status == 404: entity = None else: code = response.headers.get('ERROR-CODE', 'unknown') msg = response.headers.get('ERROR-MESSAGE', 'unknown') raise RavelloError('got status {0} ({1}/{2})' .format(status, code, msg)) response.entity = entity except (socket.timeout, ValueError) as e: self._logger.debug('error: {0!s}'.format(e)) self.close() if not _idempotent(method): self._logger.debug('not retrying {0} request'.format(method)) raise RavelloError('request timeout') retries += 1 continue break if retries == self.retries: raise RavelloError('maximum number of retries reached') if redirects == self.redirects: raise RavelloError('maximum number of redirects reached') return response def reload(self, obj): """Reload the object *obj*. The object must have been returned by the API, and must be a dict with an ``"_href"`` key. """ href = obj.get('_href') if href is None: raise RuntimeError('obj must have an "_href" key') return self.request('GET', href) def wait_for(self, obj, cond, timeout=None): """Wait for a condition on *obj* to become true. The object *obj* must be reloadable. See :meth:`reload` for more details. The condition *cond* must be a dict or a callable. If it is a dict, it lists the keys and values that the object must have. If it is a callable, it will be called with the object as an argument, and it should return True or False. The *timeout* argument specifies the total time to wait. If not specified, it will default to the system call timeout passed to the constructor. If the condition does not become true before the timeout, a :class:`RavelloError` exception is raised. """ end_time = time.time() + timeout while end_time > time.time(): obj = self.reload(obj) if _match_filter(obj, cond): break time.sleep(5) if end_time < time.time(): raise RavelloError('timeout waiting for condition') # Mapped API calls below def get_application_by_name(self, app_name, aspect=None): criteria = dict() criteria['type'] = 'COMPLEX' criteria['operator'] = 'And' criteria['criteria'] = [1] criterion = dict() criterion['type'] = 'SIMPLE' criterion['operator'] = 'Equals' criterion['propertyName'] = 'name' criterion['operand'] = app_name criteria['criteria'][0] = criterion apps = self.request('POST', '/applications/filter',criteria) if len(apps) == 0: raise RavelloError('app "{0}" not found'.format(app_name)) if len(apps) > 1: raise RavelloError('multiple apps for name "{0}" found'.format(app_name)) app = apps[0] if aspect is not 'properties': app = self.get_application(app,aspect) return app def get_application(self, app, aspect=None): """Return the application with ID *app*, or None if it does not exist. The *aspect* parameter can be used to return the application only with the specified aspect (e.g., design, deployment, properties). """ if isinstance(app, dict): app = app['id'] if aspect is not None: app = '{0};{1}'.format(app, aspect) return self.request('GET', '/applications/{0}'.format(app)) def get_applications(self, filter=None): """Return a list with all applications. The *filter* argument can be used to return only a subset of the applications. See the description of the *cond* argument to :meth:`wait_for`. """ apps = self.request('GET', '/applications') if filter is not None: apps = _match_filter(apps, filter) return apps def create_application(self, app): """Create a new application. The *app* parameter must be a dict describing the application to create. The new application is returned. """ return self.request('POST', '/applications', app) def update_application(self, app): """Update an existing application. The *app* parameter must be the updated application. The way to update an application (or any other resource) is to first retrieve it, make the updates client-side, and then use this method to make the update. The updated application is returned. """ return self.request('PUT', '/applications/{0}'.format(app['id']), app) def delete_application(self, app): """Delete an application with ID *app*.""" if isinstance(app, dict): app = app['id'] self.request('DELETE', '/applications/{0}'.format(app)) def publish_application(self, app, req=None): """Publish the application with ID *app*. The *req* parameter, if provided, must be a dict with publish parameters. """ if isinstance(app, dict): app = app['id'] self.request('POST', '/applications/{0}/publish'.format(app), req) def start_application(self, app, req=None): """Start the application with ID *app*. The *req* parameter, if provided, must be a dict with start parameters. """ if isinstance(app, dict): app = app['id'] self.request('POST', '/applications/{0}/start'.format(app), req) def stop_application(self, app, req=None): """Stop the application with ID *app*. The *req* parameter, if provided, must be a dict with stop parameters. """ if isinstance(app, dict): app = app['id'] self.request('POST', '/applications/{0}/stop'.format(app), req) def restart_application(self, app, req=None): """Restart the application with ID *app*. The *req* parameter, if provided, must be a dict with restart parameters. """ if isinstance(app, dict): app = app['id'] self.request('POST', '/applications/{0}/restart'.format(app), req) def publish_application_updates(self, app, autostart=True): """Publish updates for the application with ID *app*.""" if isinstance(app, dict): app = app['id'] url = '/applications/{0}/publishUpdates'.format(app) if not autostart: url += '?startAllDraftVms=false' self.request('POST', url) def set_application_expiration(self, app, req): """Set the expiration for the application with ID *app*. The *req* parameter must be a dict describing the new expiration. """ if isinstance(app, dict): app = app['id'] self.request('POST', '/applications/{0}/setExpiration'.format(app), req) def get_application_publish_locations(self, app, req=None): """Get a list of locations where *app* can be published.""" if isinstance(app, dict): app = app['id'] url = '/applications/{0}/findPublishLocations'.format(app) return self.request('POST', url, req) def get_blueprint_publish_locations(self, bp, req=None): """Get a list of locations where *bp* can be published.""" if isinstance(bp, dict): bp = bp['id'] url = '/blueprints/{0}/findPublishLocations'.format(bp) return self.request('POST', url, req) def get_vm(self, app, vm, aspect=None): """Return the vm with ID *vm* in the appplication with ID *app*, or None if it does not exist. The *aspect* parameter (design, deployment) can be used to return the vm as designed or as deployed in the cloud. """ if isinstance(app, dict): app = app['id'] if isinstance(vm, dict): vm = vm['id'] if aspect is not None: app = '{0};{1}'.format(app, aspect) return self.request('GET', '/applications/{0}/vms/{1}'.format(app, vm)) def get_vms(self, app, filter=None): """Return a list with all vms (for a given app). The *filter* argument can be used to return only a subset of the applications. See the description of the *cond* argument to :meth:`wait_for`. """ if isinstance(app, dict): app = app['id'] apps = self.request('GET', '/applications/{0}/vms'.format(app)) if filter is not None: apps = _match_filter(apps, filter) return apps def start_vm(self, app, vm): """Start the VM with ID *vm* in the application with ID *app*.""" if isinstance(app, dict): app = app['id'] if isinstance(vm, dict): vm = vm['id'] self.request('POST', '/applications/{0}/vms/{1}/start'.format(app, vm)) def stop_vm(self, app, vm): """Stop the VM with ID *vm* in the application with ID *app*.""" if isinstance(app, dict): app = app['id'] if isinstance(vm, dict): vm = vm['id'] self.request('POST', '/applications/{0}/vms/{1}/stop'.format(app, vm)) def poweroff_vm(self, app, vm): """Power off the VM with ID *vm* in the application with ID *app*.""" if isinstance(app, dict): app = app['id'] if isinstance(vm, dict): vm = vm['id'] self.request('POST', '/applications/{0}/vms/{1}/poweroff'.format(app, vm)) def restart_vm(self, app, vm): """Restart the VM with ID *vm* in the application with ID *app*.""" if isinstance(app, dict): app = app['id'] if isinstance(vm, dict): vm = vm['id'] self.request('POST', '/applications/{0}/vms/{1}/restart'.format(app, vm)) def redeploy_vm(self, app, vm): """Redeploy the VM with ID *vm* in the application with ID *app*.""" if isinstance(app, dict): app = app['id'] if isinstance(vm, dict): vm = vm['id'] self.request('POST', '/applications/{0}/vms/{1}/redeploy'.format(app, vm)) def get_vnc_url(self, app, vm): """Get the VNC URL for the VM with ID *vm* in the application with ID *app*.""" if isinstance(app, dict): app = app['id'] if isinstance(vm, dict): vm = vm['id'] headers = [('Accept', 'text/plain')] url = self.request('GET', '/applications/{0}/vms/{1}/vncUrl'.format(app, vm), headers=headers) return url.decode('iso-8859-1') def get_blueprint(self, bp): """Return the blueprint with ID *bp*, or None if it does not exist.""" if isinstance(bp, dict): bp = bp['id'] return self.request('GET', '/blueprints/{0}'.format(bp)) def get_blueprints(self, filter=None): """Return a list with all blueprints. The *filter* argument can be used to return only a subset of the applications. See the description of the *cond* argument to :meth:`wait_for`. """ bps = self.request('GET', '/blueprints') if filter is not None: bps = _match_filter(bps, filter) return bps def create_blueprint(self, bp): """Create a new blueprint. The *bp* parameter must be a dict describing the blueprint to create. The new blueprint is returned. """ return self.request('POST', '/blueprints', bp) def delete_blueprint(self, bp): """Delete the blueprint with ID *bp*.""" if isinstance(bp, dict): bp = bp['id'] self.request('DELETE', '/blueprints/{0}'.format(bp)) def get_image(self, img): """Return the image with ID *img*, or None if it does not exist.""" if isinstance(img, dict): img = img['id'] return self.request('GET', '/images/{0}'.format(img)) def get_images(self, filter=None): """Return a list with all images. The *filter* argument can be used to return only a subset of the images. See the description of the *cond* argument to :meth:`wait_for`. """ imgs = self.request('GET', '/images') if filter is not None: imgs = _match_filter(imgs, filter) return imgs def create_image(self, image): """Create a new image. The *image* parameter must be a dict describing the image to create. The new image is returned. """ return self.request('POST', '/images', image) def update_image(self, img): """Update an existing image. The *img* parameter must be the updated image. The updated image is returned. """ return self.request('PUT', '/images/{0}'.format(img['id']), img) def delete_image(self, img): """Delete the image with ID *img*.""" if isinstance(img, dict): img = img['id'] self.request('DELETE', '/images/{0}'.format(img)) def get_diskimage(self, img): """Return the disk image with ID *img*, or None if it does not exist.""" if isinstance(img, dict): img = img['id'] return self.request('GET', '/diskImages/{0}'.format(img)) def get_diskimages(self, filter=None): """Return a list with all disk images. The *filter* argument can be used to return only a subset of the disk images. See the description of the *cond* argument to :meth:`wait_for`. """ imgs = self.request('GET', '/diskImages') if filter is not None: imgs = _match_filter(imgs, filter) return imgs def create_diskimage(self, img): """Create a new disk image. The *img* parameter must be a dict describing the disk image to create. The new disk image is returned. """ return self.request('POST', '/diskImages', img) def update_diskimage(self, img): """Update an existing image. The *img* parameter must be the updated image. The updated disk image is returned. """ return self.request('PUT', '/diskImages/{0}'.format(img['id']), img) def delete_diskimage(self, img): """Delete the image with ID *img*.""" if isinstance(img, dict): img = img['id'] self.request('DELETE', '/diskImages/{0}'.format(img)) def get_keypair(self, kp): """Return the keypair with ID *kp*, or None if it does not exist.""" if isinstance(kp, dict): kp = kp['id'] return self.request('GET', '/keypairs/{0}'.format(kp)) def get_keypairs(self, filter=None): """Return a list with all keypairs. The *filter* argument can be used to return only a subset of the keypairs. See the description of the *cond* argument to :meth:`wait_for`. """ kps = self.request('GET', '/keypairs') if filter is not None: kps = _match_filter(kps, filter) return kps def create_keypair(self, kp): """Create a new keypair. The *kp* parameter must be a dict describing the keypair to create. The new blueprint is returned. """ return self.request('POST', '/keypairs', kp) def update_keypair(self, kp): """Update an existing keypair. The *kp* parameter must be the updated keypair. The updated keypair is returned. """ return self.request('PUT', '/keypairs/{0}'.format(kp['id']), kp) def delete_keypair(self, kp): """Delete the keypair with ID *kp*.""" if isinstance(kp, dict): kp = kp['id'] self.request('DELETE', '/keypairs/{0}'.format(kp)) def generate_keypair(self): """Generate a new keypair and return it.""" return self.request('POST', '/keypairs/generate') def get_user(self, user): """Return the user with ID *user*, or None if it does not exist.""" if isinstance(user, dict): user = user['id'] return self.request('GET', '/users/{0}'.format(user)) def get_users(self, filter=None): """Return a list with all users. The *filter* argument can be used to return only a subset of the users. See the description of the *cond* argument to :meth:`wait_for`. """ users = self.request('GET', '/users') if filter is not None: users = _match_filter(users, filter) return users def create_user(self, user): """Invite a new user to organization. The *user* parameter must be a dict describing the user to invite. The new user is returned. """ org = self.get_organization()['id'] return self.request('POST', '/organizations/{0}/users'.format(org), user) def update_user(self, user, userId): """Update an existing user. The *user* parameter must be the updated user. The way to update a user (or any other resource) is to first retrieve it, make the updates client-side, and then use this method to make the update. In this case, note however that you can only provide email, name, roles, and surname (and email cannot be changed). The updated user is returned. """ return self.request('PUT', '/users/{0}'.format(userId), user) def delete_user(self, user): """Delete a user with ID *user*.""" if isinstance(user, dict): user = user['id'] self.request('DELETE', '/users/{0}'.format(user)) def changepw_user(self, passwords, user): """Change the password of a user with ID *user*. The *passwords* parameter must be a dict describing the existing and new passwords. """ return self.request('PUT', '/users/{0}/changepw'.format(user), passwords) def get_billing(self, filter=None): """Return a list with all applications' charges incurred since beginning of the month. The *filter* argument can be used to return only a subset of the applications. See the description of the *cond* argument to :meth:`wait_for`. """ billing = self.request('GET', '/billing') if filter is not None: billing = _match_filter(billing, filter) return billing def get_billing_for_month(self, year, month): """Return a list with all applications' charges incurred during the specified month and year. """ return self.request('GET', '/billing?year={0}&month={1}'.format(year, month)) def get_events(self): """Return a list of all possible event names.""" return self.request('GET', '/events') def get_alerts(self): """Return a list of all alerts that user is registered to. If user is an administrator, list contains all alerts that the organization is registered too. """ return self.request('GET', '/userAlerts') def create_alert(self, eventName, userId=None): """Registers a user to an alert. User must be an administrator to specify a *userId*. """ req = {'eventName': eventName} if isinstance(userId, int): req['userId'] = userId return self.request('POST', '/userAlerts', req) def delete_alert(self, alertId): """Delete a specific userAlert. Specifiy an *alertId* to unregister a user from it. """ return self.request('DELETE', '/userAlerts/{0}'.format(alertId)) def search_notifications(self, query): """Return list of notifications regarding given criteria. The *query* parameter must be a dict describing the notifications to match. Technically, all 4 of the following params are optional: appId, notificationLevel, maxResults, dateRange """ return self.request('POST', '/notifications/search', query) def get_organization(self, org=None): """Return the authenticated user organization's details. The *org* parameter can be used to instead return details according to organization ID. """ if isinstance(org, dict): org = org['id'] if org is None: org = '' else: org = 's/{0}'.format(org) return self.request('GET', '/organization{0}'.format(org)) def update_organization(self, org): """Update an organization's details. The *org* parameter must be the updated organization. The way to update an organization (or any other resource) is to first retrieve it, make the updates client-side, and then use this method to make the update. The updated organization is returned. """ return self.request('PUT', '/organizations/{0}'.format(org['id']), org) def get_permgroup(self, pg): """Return the permission group with ID *pg*, or None if it does not exist.""" if isinstance(pg, dict): pg = pg['id'] return self.request('GET', '/permissionsGroups/{0}'.format(pg)) def get_permgroups(self, filter=None): """Return a list with all permission groups. The *filter* argument can be used to return only a subset of the permission groups. See the description of the *cond* argument to :meth:`wait_for`. """ pgs = self.request('GET', '/permissionsGroups') if filter is not None: pgs = _match_filter(pgs, filter) return pgs def create_permgroup(self, pg): """Create a new permission group. The *pg* parameter must be a dict describing the permission group to create. The new permission group is returned. """ return self.request('POST', '/permissionsGroups', pg) def update_permgroup(self, pg): """Update an existing permission group. The *pg* parameter must be the updated permission group. The way to update a permission group (or any other resource) is to first retrieve it, make the updates client-side, and then use this method to make the update. The updated permission group is returned. """ return self.request('PUT', '/permissionsGroups/{0}'.format(pg['id']), pg) def delete_permgroup(self, pg): """Delete a permission group with ID *pg*.""" if isinstance(pg, dict): pg = pg['id'] self.request('DELETE', '/permissionsGroups/{0}'.format(pg)) def get_users_in_permgroup(self, pg): """List all of the users in a permission group.""" if isinstance(pg, dict): pg = pg['id'] return self.request('GET', '/permissionsGroups/{0}/users'.format(pg)) def add_user_to_permgroup(self, pg, user): """Add a user to a permission group. The *user* parameter must be a valid user id. """ if isinstance(pg, dict): pg = pg['id'] req = {'userId': user} return self.request('POST', '/permissionsGroups/{0}/users'.format(pg), req) def del_user_from_permgroup(self, pg, user): """Delete a user from a permission group. The *user* parameter must be a valid user id. """ if isinstance(pg, dict): pg = pg['id'] return self.request('DELETE', '/permissionsGroups/{0}/users/{1}'.format(pg, user)) def get_permgroup_descriptors(self): """Return a list of resource permission descriptors.""" return self.request('GET', '/permissionsGroups/describe')
def post(self): form = RequestDataForm() data = form.data.data lines = data.splitlines(True) if len(lines) < 3: return 'data less 3 lines' origin_headers = [] body = [] body_start = False for index, line in enumerate(lines): if index == 0: method, path, _ = line.split(' ') continue if not line.split(): body_start = True continue if body_start: body.append(line) else: line = line.strip() key, value = line.split(': ', 1) origin_headers.append((key, value)) # for get header value header_dict = CaseInsensitiveDict(origin_headers) method = method.lower() body = ''.join(body) content_type = header_dict.get('Content-Type', '') # set headers headers = [] origin_host = header_dict.get('Host') if form.host.data and origin_host and form.host.data != origin_host: headers.append(('Host', header_dict.get('Host'))) user_agent = header_dict.get('User-Agent') referer = header_dict.get('Referer') if user_agent: headers.append(('User-Agent', user_agent)) if referer: headers.append(('Referer', referer)) # set cookie cookies = [] cookie = header_dict.get('Cookie') C = SimpleCookie(cookie) for morsel in C.values(): cookies.append((morsel.key, morsel.coded_value)) host = form.host.data or header_dict.get('Host') p = urlsplit(path) url = urljoin('http://{}'.format(host), p.path) params = [(x, repr_value(y)) for x, y in parse_qsl(p.query)] if not content_type: pass elif 'x-www-form-urlencoded' in content_type: body = [(x, repr_value(y)) for x, y in parse_qsl(body)] elif 'json' in content_type: body = [(x, repr_value(y)) for x, y in json.loads(body).items()] else: headers.append(('Content-Type', content_type)) code = render_template( 'code.html', method=method, url=url, params=params, body=body, headers=headers, cookies=cookies, content_type=content_type ) return render_template('code-page.html', code=code)
class Response(object): """ Response is an object for describing a HTTP response. Every view is responsible for either returning a response or raising an exception. """ status_code = 200 """The HTTP status code for the response.""" def __init__(self, content='', status=None, content_type='text/html; charset=utf8'): self.content = content if status: self.status_code = status self.headers = {'Content-Type': content_type} self.cookies = SimpleCookie() def __str__(self): headers = ['%s: %s' % (key, value) for key, value in self.headers.items()] return '\n'.join(headers) + '\n\n' + self.content def set_cookie(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=False): """ Sets a cookie. The parameters are the same as in the Cookie.Morsel object in the Python standard library. """ self.cookies[key] = value if max_age is not None: self.cookies[key]['max-age'] = max_age if expires is not None: if isinstance(expires, datetime.datetime): expires = expires.strftime('%a, %d %b %Y %H:%M:%S') self.cookies[key]['expires'] = expires if path is not None: self.cookies[key]['path'] = path if domain is not None: self.cookies[key]['domain'] = domain if secure: self.cookies[key]['secure'] = True def delete_cookie(self, key, path='/', domain=None): """ Deletes the cookie with the given key. Fails silently if the key doesn't exist """ self.set_cookie(key, max_age=0, path=path, domain=domain, expires='Thu, 01-Jan-1970 00:00:00 GMT') def headers_items(self): headers = [(k, v) for k, v in self.headers.items()] for cookie in self.cookies.values(): headers.append(('Set-Cookie', str(cookie.output(header='')))) return headers
class Response: __slots__ = [ "req", "status_code", "content", "encoding", "media", "headers", "formats", "cookies", "session", "mimetype", "_stream", ] text = content_setter("text/plain") html = content_setter("text/html") def __init__(self, req, *, formats): self.req = req self.status_code = None #: The HTTP Status Code to use for the Response. self.content = None #: A bytes representation of the response body. self.mimetype = None self.encoding = DEFAULT_ENCODING self.media = ( None ) #: A Python object that will be content-negotiated and sent back to the client. Typically, in JSON formatting. self._stream = None self.headers = ( {} ) #: A Python dictionary of ``{key: value}``, representing the headers of the response. self.formats = formats self.cookies = SimpleCookie() #: The cookies set in the Response self.session = ( req.session ) #: The cookie-based session data, in dict form, to add to the Response. # Property or func/dec def stream(self, func, *args, **kwargs): assert inspect.isasyncgenfunction(func) self._stream = functools.partial(func, *args, **kwargs) return func def redirect(self, location, *, set_text=True, status_code=HTTP_301): self.status_code = status_code if set_text: self.text = f"Redirecting to: {location}" self.headers.update({"Location": location}) @property async def body(self): if self._stream is not None: return (self._stream(), {}) if self.content is not None: headers = {} content = self.content if self.mimetype is not None: headers["Content-Type"] = self.mimetype if self.mimetype == "text/plain" and self.encoding is not None: headers["Encoding"] = self.encoding content = content.encode(self.encoding) return (content, headers) for format in self.formats: if self.req.accepts(format): return (await self.formats[format](self, encode=True)), {} # Default to JSON anyway. return ( await self.formats["json"](self, encode=True), { "Content-Type": "application/json" }, ) def set_cookie( self, key, value="", expires=None, path="/", domain=None, max_age=None, secure=False, httponly=True, ): self.cookies[key] = value morsel = self.cookies[key] if expires is not None: morsel["expires"] = expires if path is not None: morsel["path"] = path if domain is not None: morsel["domain"] = domain if max_age is not None: morsel["max-age"] = max_age morsel["secure"] = secure morsel["httponly"] = httponly def _prepare_cookies(self, starlette_response): cookie_header = ((b"set-cookie", morsel.output(header="").lstrip().encode("latin-1")) for morsel in self.cookies.values()) starlette_response.raw_headers.extend(cookie_header) async def __call__(self, scope, receive, send): body, headers = await self.body if self.headers: headers.update(self.headers) if self._stream is not None: response_cls = StarletteStreamingResponse else: response_cls = StarletteResponse response = response_cls(body, status_code=self.status_code, headers=headers) self._prepare_cookies(response) await response(scope, receive, send)
class BaseResponse: """Base class for Response""" default_status = 200 default_content_type = 'text/plain;' def __init__(self, body=b'', status=None, headers=None): self.headers = Headers() self._body = body self._status_code = status or self.default_status self._cookies = SimpleCookie() if headers: for name, value in headers.items(): self.headers.add_header(name, value) @property def body(self): return [self._body] @property def status_code(self): """ The HTTP status code as an integer (e.g. 404).""" return self._status_code @property def status(self): """ The HTTP status line as a string (e.g. ``404 Not Found``).""" if not 100 <= self._status_code <= 999: raise ValueError('Status code out of range.') status = _HTTP_STATUS_LINES.get(self._status_code) return str(status or ('{} Unknown'.format(self._status_code))) @status.setter def status(self, status_code): if not 100 <= status_code <= 999: raise ValueError('Status code out of range.') self._status_code = status_code @property def headerlist(self): """ WSGI conform list of (header, value) tuples. """ if 'Content-Type' not in self.headers: self.headers.add_header('Content-Type', self.default_content_type) if self._cookies: for c in self._cookies.values(): self.headers.add_header('Set-Cookie', c.OutputString()) return self.headers.items() def set_cookie(self, key, value, expires=None, max_age=None, path=None, secret=None, digestmod=hashlib.sha256): if secret: if isinstance(secret, str): secret = secret.encode('utf-8') encoded = base64.b64encode(pickle.dumps((key, value), pickle.HIGHEST_PROTOCOL)) sig = base64.b64encode(hmac.new(secret, encoded, digestmod=digestmod).digest()) value_bytes = b'!' + sig + b'?' + encoded value = value_bytes.decode('utf-8') self._cookies[key] = value if len(key) + len(value) > 3800: raise ValueError('Content does not fit into a cookie.') if max_age is not None: if isinstance(max_age, int): max_age_value = max_age else: max_age_value = max_age.seconds + max_age.days * 24 * 3600 self._cookies[key]['max-age'] = max_age_value if expires is not None: if isinstance(expires, int): expires_value = expires else: expires_value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", expires.timetuple()) self._cookies[key]['expires'] = expires_value if path: self._cookies[key]['path'] = path def delete_cookie(self, key, **kwargs): kwargs['max_age'] = -1 kwargs['expires'] = 0 self.set_cookie(key, '', **kwargs)
class Response(object): charset = 'utf-8' def __init__(self, output, headers=None, status=200, content_type='text/html', wrapped=False): self.output, self.status, self.wrapped = output, status, wrapped self.headers = HTTPHeaders(headers) if status != 304 and 'Content-Type' not in self.headers: if ';' not in content_type and ( content_type.startswith('text/') or content_type == 'application/xml' or (content_type.startswith('application/') and content_type.endswith('+xml'))): content_type += '; charset=' + self.charset self.headers['Content-Type'] = content_type def set_cookie(self, name, value, domain=None, expires=None, path="/", expires_days=None, signed=None, **kwargs): """Set the given cookie name/value with the given options.""" name = str(name) value = value if isinstance(value, str) else value.encode('utf-8') if re.search(r"[\x00-\x20]", name + value): raise ValueError("Invalid cookie %r: %r" % (name, value)) if expires_days is not None and not expires: expires = datetime.utcnow() + timedelta(days=expires_days) attrs = [("domain", domain), ("path", path), ("expires", expires and format_timestamp(expires))] if "max_age" in kwargs: attrs.append(("max-age", kwargs.pop("max_age"))) if not hasattr(self, "_new_cookie"): self._new_cookie = SimpleCookie() elif name in self._new_cookie: del self._new_cookie[name] self._new_cookie[name] = value if not signed else "" morsel = self._new_cookie[name] for (key, val) in attrs + list(kwargs.items()): morsel[key] = val or "" morsel._signed = signed and value def clear_cookie(self, name, path="/", domain=None): """Delete the cookie with the given name.""" self.set_cookie( name, value="", path=path, expires_days=(-365), domain=domain) def set_secure_cookie(self, name, value, expires_days=30, **kwargs): """Sign and timestamp a cookie so it cannot be forged.""" self.set_cookie( name, value, expires_days=expires_days, signed=True, **kwargs) def send(self, environ, start_response): """Send the headers and return the body of the response.""" status = "%d %s" % (self.status, HTTP_CODES.get(self.status)) body = (self.output if self.wrapped else [tobytes(self.output)]) if self.output else [] if not self.wrapped: self.headers['Content-Length'] = str(body and len(body[0]) or 0) if hasattr(self, "_new_cookie"): app = environ['fiole.app'] for cookie in self._new_cookie.values(): if cookie._signed is not None: self._new_cookie[cookie.key] = ( app.encode_signed(cookie.key, cookie._signed)) self.headers.add("Set-Cookie", cookie.OutputString(None)) start_response(status, self.headers.to_list()) if environ['REQUEST_METHOD'] != 'HEAD': return body if hasattr(body, 'close'): body.close() return []
def format_cookie(text): '''将字符串转换为字典形式的cookies''' cookie = SimpleCookie(text) return {i.key: i.value for i in cookie.values()}
class HttpResponse(HttpResponseException, metaclass=abc.ABCMeta): """ A basic HTTP response, with content and dictionary-accessed headers. """ @abc.abstractproperty def status_code(self): pass @abc.abstractproperty def status_text(self): pass def __init__(self, content='', content_type=None): # _headers is a mapping of the lower-case name to the original # case of the header and the header value. self._headers = {} # Content-Type self.charset = 'utf-8' # TODO settings self.set_content(content, content_type) # Cookies self._cookies = SimpleCookie() def set_content(self, content, content_type=None, content_encoding=None): self._content = content or '' if content_type: # Parsing the string self.content_type, pdict = cgi.parse_header(content_type) self.charset = pdict.get('charset', self.charset) else: self.content_type = 'text/html' # TODO settings if type(content) == str: content_type = "%s; charset=%s" % (self.content_type, self.charset) else: content_type = self.content_type # Setting header Content-Type self['Content-Type'] = content_type # Setting header Content-Encoding if content_encoding: self['Content-Encoding'] = content_encoding def set_cookies(self, cookies): self._cookies.load(cookies.output(header='')) @property def status(self): return '%s %s' % (self.status_code, self.status_text) @property def headers(self): response_headers = [ (key, value) for key, value in self._headers.values() ] response_headers += [ ('Set-Cookie', morsel.output(header='').strip()) for morsel in self._cookies.values() ] return response_headers @property def content(self): if type(self._content) == str: content = self._content.encode(self.charset) else: content = self._content return [ content ] def __setitem__(self, header, value): self._headers[header.lower()] = (header, value) def __delitem__(self, header): try: del self._headers[header.lower()] except KeyError: pass def __getitem__(self, header): return self._headers[header.lower()][1] def __str__(self): return 'HttpResponse <%s, %s>' % (self.status_code, self.status_text)
class StreamResponse(HeadersMixin): def __init__(self, *, status=200, reason=None, headers=None): self._body = None self._keep_alive = None self._chunked = False self._chunk_size = None self._compression = False self._compression_force = False self._headers = CIMultiDict() self._cookies = SimpleCookie() self._req = None self._resp_impl = None self._eof_sent = False self._task = None if headers is not None: # TODO: optimize CIMultiDict extending self._headers.extend(headers) self._headers.setdefault(hdrs.CONTENT_TYPE, 'application/octet-stream') self.set_status(status, reason) @property def prepared(self): return self._resp_impl is not None @property def started(self): warnings.warn('use Response.prepared instead', DeprecationWarning) return self.prepared @property def task(self): return self._task @property def status(self): return self._status @property def chunked(self): return self._chunked @property def compression(self): return self._compression @property def reason(self): return self._reason def set_status(self, status, reason=None): if self.prepared: raise RuntimeError("Cannot change the response status code after " "the headers have been sent") self._status = int(status) if reason is None: reason = ResponseImpl.calc_reason(status) self._reason = reason @property def keep_alive(self): return self._keep_alive def force_close(self): self._keep_alive = False @property def body_length(self): return self._resp_impl.body_length @property def output_length(self): return self._resp_impl.output_length def enable_chunked_encoding(self, chunk_size=None): """Enables automatic chunked transfer encoding.""" self._chunked = True self._chunk_size = chunk_size def enable_compression(self, force=None): """Enables response compression encoding.""" # Backwards compatibility for when force was a bool <0.17. if type(force) == bool: force = ContentCoding.deflate if force else ContentCoding.identity elif force is not None: assert isinstance(force, ContentCoding), ("force should one of " "None, bool or " "ContentEncoding") self._compression = True self._compression_force = force @property def headers(self): return self._headers @property def cookies(self): return self._cookies def set_cookie(self, name, value, *, expires=None, domain=None, max_age=None, path='/', secure=None, httponly=None, version=None): """Set or update response cookie. Sets new cookie or updates existent with new value. Also updates only those params which are not None. """ old = self._cookies.get(name) if old is not None and old.coded_value == '': # deleted cookie self._cookies.pop(name, None) self._cookies[name] = value c = self._cookies[name] if expires is not None: c['expires'] = expires elif c.get('expires') == 'Thu, 01 Jan 1970 00:00:00 GMT': del c['expires'] if domain is not None: c['domain'] = domain if max_age is not None: c['max-age'] = max_age elif 'max-age' in c: del c['max-age'] c['path'] = path if secure is not None: c['secure'] = secure if httponly is not None: c['httponly'] = httponly if version is not None: c['version'] = version def del_cookie(self, name, *, domain=None, path='/'): """Delete cookie. Creates new empty expired cookie. """ # TODO: do we need domain/path here? self._cookies.pop(name, None) self.set_cookie(name, '', max_age=0, expires="Thu, 01 Jan 1970 00:00:00 GMT", domain=domain, path=path) @property def content_length(self): # Just a placeholder for adding setter return super().content_length @content_length.setter def content_length(self, value): if value is not None: value = int(value) # TODO: raise error if chunked enabled self.headers[hdrs.CONTENT_LENGTH] = str(value) else: self.headers.pop(hdrs.CONTENT_LENGTH, None) @property def content_type(self): # Just a placeholder for adding setter return super().content_type @content_type.setter def content_type(self, value): self.content_type # read header values if needed self._content_type = str(value) self._generate_content_type_header() @property def charset(self): # Just a placeholder for adding setter return super().charset @charset.setter def charset(self, value): ctype = self.content_type # read header values if needed if ctype == 'application/octet-stream': raise RuntimeError("Setting charset for application/octet-stream " "doesn't make sense, setup content_type first") if value is None: self._content_dict.pop('charset', None) else: self._content_dict['charset'] = str(value).lower() self._generate_content_type_header() @property def last_modified(self, _LAST_MODIFIED=hdrs.LAST_MODIFIED): """The value of Last-Modified HTTP header, or None. This header is represented as a `datetime` object. """ httpdate = self.headers.get(_LAST_MODIFIED) if httpdate is not None: timetuple = parsedate(httpdate) if timetuple is not None: return datetime.datetime(*timetuple[:6], tzinfo=datetime.timezone.utc) return None @last_modified.setter def last_modified(self, value): if value is None: self.headers.pop(hdrs.LAST_MODIFIED, None) elif isinstance(value, (int, float)): self.headers[hdrs.LAST_MODIFIED] = time.strftime( "%a, %d %b %Y %H:%M:%S GMT", time.gmtime(math.ceil(value))) elif isinstance(value, datetime.datetime): self.headers[hdrs.LAST_MODIFIED] = time.strftime( "%a, %d %b %Y %H:%M:%S GMT", value.utctimetuple()) elif isinstance(value, str): self.headers[hdrs.LAST_MODIFIED] = value @property def tcp_nodelay(self): resp_impl = self._resp_impl if resp_impl is None: raise RuntimeError("Cannot get tcp_nodelay for " "not prepared response") return resp_impl.transport.tcp_nodelay def set_tcp_nodelay(self, value): resp_impl = self._resp_impl if resp_impl is None: raise RuntimeError("Cannot set tcp_nodelay for " "not prepared response") resp_impl.transport.set_tcp_nodelay(value) @property def tcp_cork(self): resp_impl = self._resp_impl if resp_impl is None: raise RuntimeError("Cannot get tcp_cork for " "not prepared response") return resp_impl.transport.tcp_cork def set_tcp_cork(self, value): resp_impl = self._resp_impl if resp_impl is None: raise RuntimeError("Cannot set tcp_cork for " "not prepared response") resp_impl.transport.set_tcp_cork(value) def _generate_content_type_header(self, CONTENT_TYPE=hdrs.CONTENT_TYPE): params = '; '.join("%s=%s" % i for i in self._content_dict.items()) if params: ctype = self._content_type + '; ' + params else: ctype = self._content_type self.headers[CONTENT_TYPE] = ctype def _start_pre_check(self, request): if self._resp_impl is not None: if self._req is not request: raise RuntimeError( "Response has been started with different request.") else: return self._resp_impl else: return None def _do_start_compression(self, coding): if coding != ContentCoding.identity: self.headers[hdrs.CONTENT_ENCODING] = coding.value self._resp_impl.add_compression_filter(coding.value) self.content_length = None def _start_compression(self, request): if self._compression_force: self._do_start_compression(self._compression_force) else: accept_encoding = request.headers.get( hdrs.ACCEPT_ENCODING, '').lower() for coding in ContentCoding: if coding.value in accept_encoding: self._do_start_compression(coding) return def start(self, request): warnings.warn('use .prepare(request) instead', DeprecationWarning) resp_impl = self._start_pre_check(request) if resp_impl is not None: return resp_impl return self._start(request) @asyncio.coroutine def prepare(self, request): resp_impl = self._start_pre_check(request) if resp_impl is not None: return resp_impl yield from request._prepare_hook(self) return self._start(request) def _start(self, request, HttpVersion10=HttpVersion10, HttpVersion11=HttpVersion11, CONNECTION=hdrs.CONNECTION, DATE=hdrs.DATE, SERVER=hdrs.SERVER, SET_COOKIE=hdrs.SET_COOKIE, TRANSFER_ENCODING=hdrs.TRANSFER_ENCODING): self._req = request keep_alive = self._keep_alive if keep_alive is None: keep_alive = request.keep_alive self._keep_alive = keep_alive version = request.version resp_impl = self._resp_impl = ResponseImpl( request._writer, self._status, version, not keep_alive, self._reason) headers = self.headers for cookie in self._cookies.values(): value = cookie.output(header='')[1:] headers.add(SET_COOKIE, value) if self._compression: self._start_compression(request) if self._chunked: if request.version != HttpVersion11: raise RuntimeError("Using chunked encoding is forbidden " "for HTTP/{0.major}.{0.minor}".format( request.version)) resp_impl.chunked = True if self._chunk_size: resp_impl.add_chunking_filter(self._chunk_size) headers[TRANSFER_ENCODING] = 'chunked' else: resp_impl.length = self.content_length headers.setdefault(DATE, request.time_service.strtime()) headers.setdefault(SERVER, resp_impl.SERVER_SOFTWARE) if CONNECTION not in headers: if keep_alive: if version == HttpVersion10: headers[CONNECTION] = 'keep-alive' else: if version == HttpVersion11: headers[CONNECTION] = 'close' resp_impl.headers = headers self._send_headers(resp_impl) self._task = request._task return resp_impl def _send_headers(self, resp_impl): # Durty hack required for # https://github.com/KeepSafe/aiohttp/issues/1093 # File sender may override it resp_impl.send_headers() def write(self, data): assert isinstance(data, (bytes, bytearray, memoryview)), \ "data argument must be byte-ish (%r)" % type(data) if self._eof_sent: raise RuntimeError("Cannot call write() after write_eof()") if self._resp_impl is None: raise RuntimeError("Cannot call write() before start()") if data: return self._resp_impl.write(data) else: return () @asyncio.coroutine def drain(self): if self._resp_impl is None: raise RuntimeError("Response has not been started") yield from self._resp_impl.transport.drain() @asyncio.coroutine def write_eof(self): if self._eof_sent: return if self._resp_impl is None: raise RuntimeError("Response has not been started") yield from self._resp_impl.write_eof() self._eof_sent = True def __repr__(self): if self.started: info = "{} {} ".format(self._req.method, self._req.path) else: info = "not started" return "<{} {} {}>".format(self.__class__.__name__, self.reason, info)