lateefj / Frisky (http://src.hackingthought.com/projects/frisky/)

Frisky is an Asynchronous WSGI web server. Cross-platform, high performance, caching and compression API's included, and lightweight. Test server running Frisky: http://src.hackingthought.com/

Clone this repository (size: 498.5 KB): HTTPS / SSH
$ hg clone http://bitbucket.org/lateefj/frisky/
commit 6: 7d32ad212ad5
parent 5: ac05bec4f8c8
branch: default
Writing lots of code to improve WSGI support
Lateef Jackson / lateefj
14 months ago

Changed (Δ2.4 KB):

raw changeset »

.hgignore (1 lines added, 1 lines removed)

TODO.txt (42 lines added, 2 lines removed)

frisky/httpd.py (37 lines added, 14 lines removed)

frisky/oracle/io.py (1 lines added, 1 lines removed)

frisky/request.py (2 lines added, 2 lines removed)

samples/pylons_sample/run.py (1 lines added, 1 lines removed)

samples/selector_sample/run.py (6 lines added, 0 lines removed)

Up to file-list .hgignore:

@@ -5,4 +5,4 @@ syntax: glob
5
5
*~
6
6
nohup.out
7
7
log
8
krisky.kpf
8
kfrisky.kpf

Up to file-list TODO.txt:

1
WSGI support:
2
 * SELECTOR 
1
Server Configuration:
2
 * Static files
3
   * regex configuration which can be integrated from older code
4
   * cache and compression 
5
 * URL mapping
6
   * Map urls to web frameworks
7
   * Domain aliases
8
   * Redirect using regex
9
 * Other
10
   * Logging
11
   * Port
12
   * Restart using memento
13
     * import memento
14
     * app = memento.Assassin("frisky:frisky", ["frisky"])
15
   
16
WSGI Frameworks:
3
17
 * Pylons
18
   * Controller support
19
   * Database support
20
   * Read the configuration file for serving static files
21
 * Django
22
   * Basic startup
23
   * Controller (whatever) support
24
   * Database support
25
   * Admin tool
26
   * read configuration for serving static files
27
 * TurboGears
28
   * Basic startup
29
   * Controller support
30
   * Database support
31
   * read configuration for serving static files (should be same as Pylons)
32
33
Optimization:
34
 * Pyevent (could use python implementation for getting started (http://code.google.com/p/registeredeventlistener/))
35
   * pyprocessing using additional processes to increase peformance
36
 * Cython
37
   * Profile and use Cython on slowest/most used code
38
   * Use for more direct access to pyevent/libevent
39
 * Caching
40
   * Use high level cache
41
 * Static files
42
   * Use async push file system to server files
43
   
4
44
 
5
45
Caching:
6
46
Need to support cache invalidation programmatically and cache invalidation timeouts

Up to file-list frisky/httpd.py:

@@ -276,9 +276,9 @@ def test_handler(environ, reponse):
276
276
    return ['Hello World from test']
277
277
    
278
278
from frisky import request
279
#import memento
280
#app = memento.Assassin("frisky:frisky", ["frisky"])
281
279
import logging
280
281
APP_MEMENTO = None
282
282
class WSGIHandler(RequestHandler):
283
283
    """
284
284
    The only thing that this class must do is setup the 
@@ -288,29 +288,45 @@ class WSGIHandler(RequestHandler):
288
288
289
289
    def handle_data(self):
290
290
        """
291
        Env vars needed
292
         * SCRIPT_NAME
293
         * QUERY_STING
294
         
291
        It basically does a brute force to populate the environ.
295
292
        """
296
293
        self.rfile.seek(0)
294
        # OPTIMIZATION: This could be done faster somehow
295
        content_length = len(self.rfile.read())
296
        # Parse the path to find the query string
297
        delimiter = self.path.find('?')
298
        query_string = ''
299
        if delimiter > 0:
300
            query_string = self.path[delimiter+1:]
301
            
302
        self.rfile.seek(0)
297
303
        lines = self.rfile.readlines()
298
        env = {'PATH_INFO':self.path, }
304
        env = {'PATH_INFO':self.path, 'CONTENT_LENGTH':content_length, 
305
            'QUERY_STING':query_string}
306
        # OPTIMIZATION: The environ population could be made faster 
307
        # with a better algorithm?
299
308
        headers = {}
300
309
        for l in lines:
301
310
            parts = l.split(':')
302
311
            if len(parts) > 1:
312
                # Special WSGI population variable
303
313
                if parts[0] == 'Host':
304
                    env['SERVER_NAME'] = parts[1]
314
                    env['SERVER_NAME'] = parts[1].strip()
305
315
                    if len(parts) > 2:
306
316
                        env['SERVER_PORT'] = parts[2]
307
317
                    else:
308
318
                        env['SERVER_PORT'] = 80
309
                headers[parts[0]] = parts[1]
319
                elif len(parts) > 2:
320
                    # if value has embeded : in it
321
                     headers[parts[0]] = ':'.join(parts[1:len(parts)]).strip()
322
                else:
323
                    # If just simple name:value 
324
                    headers[parts[0]] = parts[1].strip()
310
325
        
311
326
        environ = request.Environ()
327
        environ.update_method({'REQUEST_METHOD':self.command})
312
328
        environ.update_uri(env)
313
        #print('headers %s' % self.headers)
329
        #print('env %s' % env)
314
330
        environ.update_headers(headers)
315
331
        environ.update_headers(self.headers)
316
332
        #print('request line is %s' % self.parse_request())
@@ -319,18 +335,25 @@ class WSGIHandler(RequestHandler):
319
335
            'SCRIPT_NAME':self.path,
320
336
            'SERVER_PROTOCOL':self.request_version,
321
337
            })
322
        environ.update_method({'REQUEST_METHOD':self.command})
338
        
323
339
        
324
340
        response = request.StartResponse()
325
341
        res = self.callback(environ, response)
342
        
343
        self.wfile.write(str(response))
344
        
326
345
        for r in res:
327
346
            self.wfile.write(r)
328
        #print('Reqeust added to queue')
347
        
329
348
        
330
349
331
def start(host='0.0.0.0', port=7777, callback=None):
350
def start(host='0.0.0.0', port=7777, callback=None, packages=[]):
351
    """
352
    import memento
353
    for p in packages:
354
        APP_MEMENTO = memento.Assassin("%s:%s" % (p, p), ["%s" % p])
355
    """
332
356
    # launch the server on the specified port
333
    port = 7777
334
357
    s = Server(host, port, WSGIHandler, callback=callback)
335
358
    print "SimpleAsyncHTTPServer running on port %s" %port
336
359
    try:

Up to file-list frisky/oracle/io.py:

@@ -4,7 +4,7 @@ The io module is design to improve perfo
4
4
import cStringIO
5
5
import gzip
6
6
7
from frisky import cache
7
from frisky.oracle import cache
8
8
9
9
CACHE_TYPE = cache.RAM
10
10

Up to file-list frisky/request.py:

@@ -133,8 +133,8 @@ class StartResponse:
133
133
            self.cookies[key] = ''
134
134
        self.cookies[key]['max-age'] = "0"
135
135
    def __str__(self):
136
        #res = "HTTP/1.1 %s %s\r\n" % (self.status_code, self.status_reasons)
137
        res=""
136
        res = "HTTP/1.1 %s %s\r\n" % (self.status_code, self.status_reasons)
137
        #res=""
138
138
        for key, val in self.response_headers.items():
139
139
            if key.upper() == "SET-COOKIE":
140
140
                res += "%s\r\n" % val

Up to file-list samples/pylons_sample/run.py:

@@ -8,5 +8,5 @@ from frisky import httpd
8
8
9
9
10
10
11
httpd.start(callback=application)
11
httpd.start(callback=application, packages=['pylons_sample'])
12
12

Up to file-list samples/selector_sample/run.py:

@@ -8,7 +8,13 @@ Note: Yaro performance is horrible so no
8
8
import selector
9
9
10
10
from frisky import httpd
11
from frisky.oracle.io import optimize
11
12
13
# It seems to have a small performance impact 
14
# not sure why but it is a bit slower using the cache
15
# might just be more code in the way.
16
# This is such an unrealistic example
17
@optimize(compress=False, cache=True, cache_key='hello_name')
12
18
def hello_name(environ, start_response):
13
19
    name = environ['selector.vars']['name']
14
20
    return ["Hello %s!" % name]