├── .gitignore
├── LICENSE
├── README.md
├── example
├── app.py
├── config.py
├── content.py
├── get_test.py
├── static
│ ├── .DS_Store
│ ├── assets
│ │ ├── 718a8187d60f95226d056cb9592cd794c2e6a060
│ │ ├── b200badc4b2e97c5ba0743a0cc56d417905965b1
│ │ ├── b4bfb5418f31b780740cd55ded5d61049ef6c5d5
│ │ ├── c9c9f65c5ee31184168045c4d7c616b42188acac
│ │ ├── cf90fe4d393f94b4f9aa2250d8e51b91422dfb17
│ │ ├── e5c78cd533a2bc86b5d6806d857533315de42b77
│ │ ├── e890f16d7e050e63b01bb71db666c2efa69f733d
│ │ └── large.html
│ ├── courses.json
│ ├── nav_items.json
│ └── style.css
└── templates
│ ├── .DS_Store
│ ├── base.html
│ ├── course.html
│ ├── index.html
│ ├── page.html
│ ├── profile.html
│ └── root.html
├── paws
├── __init__.py
├── pahttp.py
├── palog.py
├── paroute.py
└── paws.py
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 |
55 | # Sphinx documentation
56 | docs/_build/
57 |
58 | # PyBuilder
59 | target/
60 |
61 | #Ipython Notebook
62 | .ipynb_checkpoints
63 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Erika Jonell (xevrem)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.o
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #Python Async Web Server (paws) 🐾
2 | A stupidly simple and insanely fast multi-process asyncronous python web server powered by Python 3.5 asyncio
3 |
4 | Installation
5 | ------------
6 | if just using stock python:
7 | ```
8 | pip3 install jinja2
9 | python3 setup.py install
10 | ```
11 | if using uvloop:
12 | ```
13 | pip3 install uvloop jinja2
14 | python3 setup.py install
15 | ```
16 |
17 |
18 | What's New:
19 | -----------
20 | PAWS now has the ability to perform asyncronous requests via new `get`, `put`, `post`, and `delete` connection methods. Additionally, experimental SSL support exists within these methods, but not the main server (yet).
21 |
22 | Older News:
23 | -----------
24 | PAWS has support for [uvloop](http://github.com/magicstack/uvloop) allowing increased speed by replacing the default asyncio loop construct with one running on top of libuv. By default uvloop is NOT used. To use it, pass `use_uvloop=True` in the arguments for `run_server()`
25 |
26 | Example
27 | -------
28 |
29 | ```
30 | from paws import run_server
31 |
32 | async def root(req, res):
33 | res.body = "hello world!"
34 | return res
35 |
36 | def routing(app):
37 | app.add_route("/", root)
38 |
39 | run_server(routing_cb=routing, host="127.0.0.1", port=8080, processes=4)
40 | ```
41 |
42 | Requirements
43 | ------------
44 | default:
45 | - Python >= 3.5
46 | - Jinja2 https://pypi.python.org/pypi/jinja2
47 |
48 | if using uvloop:
49 | - libuv
50 | - [uvloop](http://github.com/magicstack/uvloop) >= 0.4.11
51 |
52 | License
53 | -------
54 | paws is offered under the MIT license
55 |
--------------------------------------------------------------------------------
/example/app.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | '''
3 | The MIT License (MIT)
4 |
5 | Copyright (c) 2016 Erika Jonell (xevrem)
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.o
24 | '''
25 |
26 | import asyncio
27 | import json
28 |
29 | from paws import render_template, run_server, get
30 |
31 | import content
32 | import config
33 |
34 | async def root(request, response):
35 | response.body = render_template('root.html', nav_list= await content.get_nav())
36 | return response
37 |
38 |
39 | async def index(req,res):
40 | res.body = render_template('index.html', nav_list= await content.get_nav(), courses= await content.get_courses())
41 | return res
42 |
43 |
44 | async def static(req, res):
45 | res.body = await content.get_file(config.STATIC_DIR + req.wildcards['filename'])
46 | return res
47 |
48 |
49 | async def course(req, res):
50 | course = await content.get_asset(req.wildcards['aid'])
51 | res.body = render_template('course.html', nav_list= await content.get_nav(), page_data=course, pages=course['pages'])
52 | return res
53 |
54 |
55 | async def page(req,res):
56 | page = await content.get_asset(req.wildcards['aid'])
57 | if page:
58 | hcontent = await content.get_html_asset(page['content'])
59 | res.body = render_template('page.html', nav_list= await content.get_nav(), page_data=page, content=hcontent)
60 | return res
61 | else:
62 | res.body = 'no page'
63 | return res
64 |
65 |
66 | async def profile(req,res):
67 | if req.wildcards.keys():
68 | uid = req.wildcards['uid']
69 | else:
70 | uid = 'none'
71 |
72 | res.body = render_template('profile.html', nav_list= await content.get_nav(), uid=uid)
73 | return res
74 |
75 | async def file(req,res):
76 | res.body = await content.get_html_asset('large.html')
77 | return res
78 |
79 | async def reddit(req,res):
80 | data = await get(url='https://www.reddit.com/', port=443, ssl_context=True, headers={}, debug=True)
81 | parsed = pahttp.http_data_create(data)
82 | res.body = parsed.body
83 | return res
84 |
85 | async def clear_cache():
86 | content.CACHE = {}
87 | print('cache cleared...')
88 |
89 | async def hello(req,res):
90 | res.body = 'hello world'
91 | return res
92 |
93 |
94 | def routing(app):
95 | app.add_route('/', root)
96 | app.add_route('/index', index)
97 | app.add_route('/static/{filename}', static)
98 | app.add_route('/course/{aid}', course)
99 | app.add_route('/page/{aid}', page)
100 | app.add_route('/profile', profile)
101 | app.add_route('/profile/{uid}', profile)
102 | app.add_route('/file', file)
103 | app.add_route('/reddit', reddit)
104 | app.add_route('/hello', hello)
105 |
106 | def main():
107 |
108 | run_server(routing_cb=routing, host='127.0.0.1', port=8080,
109 | processes=8, use_uvloop=False, debug=True)
110 |
111 |
112 | if __name__ == '__main__':
113 | main()
114 |
--------------------------------------------------------------------------------
/example/config.py:
--------------------------------------------------------------------------------
1 | '''
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2016 Erika Jonell (xevrem)
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.o
23 | '''
24 |
25 | TEMPLATE_DIR = 'templates/'
26 | STATIC_DIR = 'static/'
27 | PROFILE_DIR = STATIC_DIR+'profiles/'
28 | ASSET_DIR = STATIC_DIR+'assets/'
29 | TEST_DIR = 'test_assets/'
30 |
--------------------------------------------------------------------------------
/example/content.py:
--------------------------------------------------------------------------------
1 | '''
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2016 Erika Jonell (xevrem)
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.o
23 | '''
24 |
25 | import config
26 | import asyncio
27 | import json
28 |
29 | #stupid cache
30 | CACHE = {}
31 |
32 | async def check_cache(filename):
33 | if filename in CACHE.keys():
34 | #print('cache hit...')
35 | return CACHE[filename]
36 | else:
37 | #print('cache miss...')
38 | return False
39 |
40 |
41 | async def get_file(filename):
42 | #check if it is in the cache
43 | data = await check_cache(filename)
44 | if data:
45 | return data
46 |
47 | #not in cache, retrieve asset
48 | loop = asyncio.get_event_loop()
49 | try:
50 | with open(filename, 'r') as f:
51 | data = await loop.run_in_executor(None,f.read)
52 | CACHE[filename] = data
53 | return data
54 | except:
55 | print('failed to find: {}'.format(filename))
56 | return None
57 |
58 |
59 | async def get_json(filename):
60 | data = await get_file(filename)
61 | if data:
62 | return json.loads(data)
63 | else:
64 | return None
65 |
66 |
67 | async def get_asset(aid):
68 | return await get_json(config.ASSET_DIR+aid)
69 |
70 |
71 | async def get_html_asset(aid):
72 | return await get_file(config.ASSET_DIR+aid)
73 |
74 |
75 | async def get_nav():
76 | nav = await get_json(config.STATIC_DIR +'nav_items.json')
77 | return nav['nav_items']
78 |
79 |
80 | async def get_courses():
81 | courses = await get_json(config.STATIC_DIR+'courses.json')
82 | return courses['course_list']
83 |
--------------------------------------------------------------------------------
/example/get_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | '''
3 | The MIT License (MIT)
4 |
5 | Copyright (c) 2016 Erika Jonell (xevrem)
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 | '''
25 |
26 | import asyncio
27 | from paws.paws import get
28 | from paws.pahttp import HttpRequest
29 | from time import time
30 | import signal
31 | import uvloop
32 | from multiprocessing import Process
33 |
34 | start = 0.0
35 | finish = 0.0
36 | count = 100000
37 | num_procs = 4
38 | num = int(count/num_procs)
39 |
40 | async def do_get():
41 | data = await get(url='http://localhost/hello', port=8080, ssl_context=False,
42 | headers={}, debug=False)
43 | req = HttpRequest(data)
44 | #finish = clock()
45 | #print("req complete...")
46 |
47 |
48 | def do_test():
49 | asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
50 | loop = asyncio.get_event_loop()
51 | for i in range(num):
52 | foo = asyncio.ensure_future(do_get())
53 | loop.run_until_complete(foo)
54 |
55 | def main():
56 | procs = []
57 |
58 | def finished(signum, frame):
59 | total = finish-start
60 | print("total time: {} ms/req".format(total * 1000))
61 |
62 | for proc in procs:
63 | if proc:
64 | proc.terminate()
65 |
66 |
67 | signal.signal(signal.SIGINT, finished)
68 |
69 | start = time()
70 |
71 | for i in range(num_procs):
72 | p = Process(target=do_test)
73 | procs.append(p)
74 | p.start()
75 |
76 | for proc in procs:
77 | proc.join()
78 |
79 | finish = time()
80 |
81 | total = finish-start
82 | print("total time: {} ms".format((total * 1000)))
83 | print("reqs per sec: {}".format((num*num_procs)/total))
84 |
85 | if __name__ == '__main__':
86 | main()
--------------------------------------------------------------------------------
/example/static/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xevrem/paws/75e27650bc56a7763a8cf672fdaf986af0d42439/example/static/.DS_Store
--------------------------------------------------------------------------------
/example/static/assets/718a8187d60f95226d056cb9592cd794c2e6a060:
--------------------------------------------------------------------------------
1 | {
2 | "title":"another test page",
3 | "billboard":"TEST PAGE 2",
4 | "content":"b4bfb5418f31b780740cd55ded5d61049ef6c5d5",
5 | "resource_prev":"1",
6 | "resource_next":"c9c9f65c5ee31184168045c4d7c616b42188acac"
7 | }
8 |
--------------------------------------------------------------------------------
/example/static/assets/b200badc4b2e97c5ba0743a0cc56d417905965b1:
--------------------------------------------------------------------------------
1 | foo
2 | test content goes here test content goes here test content
3 | goes here test content goes here test content goes here test content
4 | goes here test content goes here test content goes here test
5 | content goes here test content goes here test content goes here
6 | test content goes here test content goes here test content goes
7 | here test content goes here
8 |
2 |
--------------------------------------------------------------------------------
/example/static/assets/large.html:
--------------------------------------------------------------------------------
1 | reddit: the front page of the internet
reddit gold gives you extra features and helps keep our servers running. We believe the more reddit can be user-supported, the freer we will be to make reddit the best it can be.
Buy gold for yourself to gain access to extra features and special benefits. A month of gold pays for 231.26 minutes of reddit server time!
Give gold to thank exemplary people and encourage them to post more.
This daily goal updates every 10 minutes and is reset at midnight Pacific Time (18 hours, 16 minutes from now).
Use of this tool is open to all members of reddit.com, and for as little as $5.00 you can advertise in this area. Get started ›
This is a new ad format that we are currently testing. We often try new types of ads in a limited capacity. If you have feedback, please let us know in the ads subreddit.
This area shows new and upcoming links. Vote on links here to help them become popular, and click the forwards and backwards buttons to view more.
Enter a keyword or topic to discover new subreddits around your interests. Be specific!
You can access this tool at any time on the /subreddits/ page.