Merge pull request #98 from channelcat/cookies
Adding cookie capabilities for issue #74
This commit is contained in:
		| @@ -50,6 +50,7 @@ app.run(host="0.0.0.0", port=8000) | ||||
|  * [Middleware](docs/middleware.md) | ||||
|  * [Exceptions](docs/exceptions.md) | ||||
|  * [Blueprints](docs/blueprints.md) | ||||
|  * [Cookies](docs/cookies.md) | ||||
|  * [Deploying](docs/deploying.md) | ||||
|  * [Contributing](docs/contributing.md) | ||||
|  * [License](LICENSE) | ||||
|   | ||||
							
								
								
									
										50
									
								
								docs/cookies.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								docs/cookies.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| # Cookies | ||||
|  | ||||
| ## Request | ||||
|  | ||||
| Request cookies can be accessed via the request.cookie dictionary | ||||
|  | ||||
| ### Example | ||||
|  | ||||
| ```python | ||||
| from sanic import Sanic | ||||
| from sanic.response import text | ||||
|  | ||||
| @app.route("/cookie") | ||||
| async def test(request): | ||||
|     test_cookie = request.cookies.get('test') | ||||
|     return text("Test cookie set to: {}".format(test_cookie)) | ||||
| ``` | ||||
|  | ||||
| ## Response | ||||
|  | ||||
| Response cookies can be set like dictionary values and  | ||||
| have the following parameters available: | ||||
|  | ||||
| * expires - datetime - Time for cookie to expire on the client's browser | ||||
| * path - string - The Path attribute specifies the subset of URLs to  | ||||
|          which this cookie applies | ||||
| * comment - string - Cookie comment (metadata) | ||||
| * domain - string - Specifies the domain for which the | ||||
|            cookie is valid.  An explicitly specified domain must always  | ||||
|            start with a dot. | ||||
| * max-age - number - Number of seconds the cookie should live for | ||||
| * secure - boolean - Specifies whether the cookie will only be sent via | ||||
|            HTTPS | ||||
| * httponly - boolean - Specifies whether the cookie cannot be read | ||||
|              by javascript | ||||
|  | ||||
| ### Example | ||||
|  | ||||
| ```python | ||||
| from sanic import Sanic | ||||
| from sanic.response import text | ||||
|  | ||||
| @app.route("/cookie") | ||||
| async def test(request): | ||||
|     response = text("There's a cookie up in this response") | ||||
|     response.cookies['test'] = 'It worked!' | ||||
|     response.cookies['test']['domain'] = '.gotta-go-fast.com' | ||||
|     response.cookies['test']['httponly'] = True | ||||
|     return response | ||||
| ``` | ||||
| @@ -1,5 +1,6 @@ | ||||
| from cgi import parse_header | ||||
| from collections import namedtuple | ||||
| from http.cookies import SimpleCookie | ||||
| from httptools import parse_url | ||||
| from urllib.parse import parse_qs | ||||
| from ujson import loads as json_loads | ||||
| @@ -30,7 +31,7 @@ class Request: | ||||
|     Properties of an HTTP request such as URL, headers, etc. | ||||
|     """ | ||||
|     __slots__ = ( | ||||
|         'url', 'headers', 'version', 'method', | ||||
|         'url', 'headers', 'version', 'method', '_cookies', | ||||
|         'query_string', 'body', | ||||
|         'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files', | ||||
|     ) | ||||
| @@ -52,6 +53,7 @@ class Request: | ||||
|         self.parsed_form = None | ||||
|         self.parsed_files = None | ||||
|         self.parsed_args = None | ||||
|         self._cookies = None | ||||
|  | ||||
|     @property | ||||
|     def json(self): | ||||
| @@ -105,6 +107,18 @@ class Request: | ||||
|  | ||||
|         return self.parsed_args | ||||
|  | ||||
|     @property | ||||
|     def cookies(self): | ||||
|         if self._cookies is None: | ||||
|             if 'Cookie' in self.headers: | ||||
|                 cookies = SimpleCookie() | ||||
|                 cookies.load(self.headers['Cookie']) | ||||
|                 self._cookies = {name: cookie.value | ||||
|                                  for name, cookie in cookies.items()} | ||||
|             else: | ||||
|                 self._cookies = {} | ||||
|         return self._cookies | ||||
|  | ||||
|  | ||||
| File = namedtuple('File', ['type', 'body', 'name']) | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| from datetime import datetime | ||||
| from http.cookies import SimpleCookie | ||||
| import ujson | ||||
|  | ||||
| COMMON_STATUS_CODES = { | ||||
| @@ -68,7 +70,7 @@ ALL_STATUS_CODES = { | ||||
|  | ||||
|  | ||||
| class HTTPResponse: | ||||
|     __slots__ = ('body', 'status', 'content_type', 'headers') | ||||
|     __slots__ = ('body', 'status', 'content_type', 'headers', '_cookies') | ||||
|  | ||||
|     def __init__(self, body=None, status=200, headers=None, | ||||
|                  content_type='text/plain', body_bytes=b''): | ||||
| @@ -81,6 +83,7 @@ class HTTPResponse: | ||||
|  | ||||
|         self.status = status | ||||
|         self.headers = headers or {} | ||||
|         self._cookies = None | ||||
|  | ||||
|     def output(self, version="1.1", keep_alive=False, keep_alive_timeout=None): | ||||
|         # This is all returned in a kind-of funky way | ||||
| @@ -95,6 +98,12 @@ class HTTPResponse: | ||||
|                 b'%b: %b\r\n' % (name.encode(), value.encode('utf-8')) | ||||
|                 for name, value in self.headers.items() | ||||
|             ) | ||||
|         if self._cookies: | ||||
|             for cookie in self._cookies.values(): | ||||
|                 if type(cookie['expires']) is datetime: | ||||
|                     cookie['expires'] = \ | ||||
|                         cookie['expires'].strftime("%a, %d-%b-%Y %T GMT") | ||||
|             headers += (str(self._cookies) + "\r\n").encode('utf-8') | ||||
|  | ||||
|         # Try to pull from the common codes first | ||||
|         # Speeds up response rate 6% over pulling from all | ||||
| @@ -119,6 +128,12 @@ class HTTPResponse: | ||||
|             self.body | ||||
|         ) | ||||
|  | ||||
|     @property | ||||
|     def cookies(self): | ||||
|         if self._cookies is None: | ||||
|             self._cookies = SimpleCookie() | ||||
|         return self._cookies | ||||
|  | ||||
|  | ||||
| def json(body, status=200, headers=None): | ||||
|     return HTTPResponse(ujson.dumps(body), headers=headers, status=status, | ||||
|   | ||||
| @@ -5,10 +5,10 @@ HOST = '127.0.0.1' | ||||
| PORT = 42101 | ||||
|  | ||||
|  | ||||
| async def local_request(method, uri, *args, **kwargs): | ||||
| async def local_request(method, uri, cookies=None, *args, **kwargs): | ||||
|     url = 'http://{host}:{port}{uri}'.format(host=HOST, port=PORT, uri=uri) | ||||
|     log.info(url) | ||||
|     async with aiohttp.ClientSession() as session: | ||||
|     async with aiohttp.ClientSession(cookies=cookies) as session: | ||||
|         async with getattr(session, method)(url, *args, **kwargs) as response: | ||||
|             response.text = await response.text() | ||||
|             return response | ||||
|   | ||||
							
								
								
									
										44
									
								
								tests/test_cookies.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								tests/test_cookies.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| from datetime import datetime, timedelta | ||||
| from http.cookies import SimpleCookie | ||||
| from sanic import Sanic | ||||
| from sanic.response import json, text | ||||
| from sanic.utils import sanic_endpoint_test | ||||
|  | ||||
|  | ||||
| # ------------------------------------------------------------ # | ||||
| #  GET | ||||
| # ------------------------------------------------------------ # | ||||
|  | ||||
| def test_cookies(): | ||||
|     app = Sanic('test_text') | ||||
|  | ||||
|     @app.route('/') | ||||
|     def handler(request): | ||||
|         response = text('Cookies are: {}'.format(request.cookies['test'])) | ||||
|         response.cookies['right_back'] = 'at you' | ||||
|         return response | ||||
|  | ||||
|     request, response = sanic_endpoint_test(app, cookies={"test": "working!"}) | ||||
|     response_cookies = SimpleCookie() | ||||
|     response_cookies.load(response.headers.get('Set-Cookie', {})) | ||||
|  | ||||
|     assert response.text == 'Cookies are: working!' | ||||
|     assert response_cookies['right_back'].value == 'at you' | ||||
|  | ||||
| def test_cookie_options(): | ||||
|     app = Sanic('test_text') | ||||
|  | ||||
|     @app.route('/') | ||||
|     def handler(request): | ||||
|         response = text("OK") | ||||
|         response.cookies['test'] = 'at you' | ||||
|         response.cookies['test']['httponly'] = True | ||||
|         response.cookies['test']['expires'] = datetime.now() + timedelta(seconds=10) | ||||
|         return response | ||||
|  | ||||
|     request, response = sanic_endpoint_test(app) | ||||
|     response_cookies = SimpleCookie() | ||||
|     response_cookies.load(response.headers.get('Set-Cookie', {})) | ||||
|  | ||||
|     assert response_cookies['test'].value == 'at you' | ||||
|     assert response_cookies['test']['httponly'] == True | ||||
		Reference in New Issue
	
	Block a user
	 Channel Cat
					Channel Cat