ry / ebb fork watch download tarball
public this repo is viewable by everyone
Description: web server
Homepage: http://ebb.rubyforge.org
Clone URL: git://github.com/ry/ebb.git
Support keep-alive in python. readme changes.
ryah (author)
about 1 month ago
commit  cbef11050d5cde7478f45abe5f5bd3f97923f933
tree    fd39e021622be2d9823ae00a30bd878d79348df3
parent  dc4a7e28337306378a008aa9d7e205a306e79934
...
53
54
55
56
 
57
58
59
...
70
71
72
73
74
75
 
 
76
77
78
79
80
81
82
83
 
84
85
86
...
53
54
55
 
56
57
58
59
...
70
71
72
 
 
 
73
74
75
76
77
78
79
 
 
 
80
81
82
83
0
@@ -53,7 +53,7 @@ hacking at the moment! :)
0
 
0
 ## Speed
0
 
0
-Because Ebb-Ruby handles most of the processing in C, it is able to do work
0
+Because Ebb handles most of the processing in C, it is able to do work
0
 often times more efficiently than other Ruby language web servers.
0
 
0
 ![Benchmark](http://s3.amazonaws.com/four.livejournal/20080311/ebb.png)
0
@@ -70,17 +70,14 @@ Contributions (patches, criticism, advice) are very welcome!
0
 Please send all to to
0
 [the mailing list](http://groups.google.com/group/ebbebb).
0
 
0
-The source code
0
-is hosted [github](http://github.com/ry/ebb/tree/master). It can be retrieved
0
-by executing
0
+The source code is hosted [github](http://github.com/ry/ebb/tree/master). It
0
+can be retrieved by executing
0
 
0
     git clone git://github.com/ry/ebb.git
0
 
0
 Here are some features that I would like to add:
0
 * HTTP 1.1 Expect/Continue (RFC 2616, sections 8.2.3 and 10.1.1)
0
-* A parser for multipart/form-data
0
-* Optimize and clean up upload handling
0
-* Option to listen on unix sockets instead of TCP
0
+* A parser for multipart/form-data (only for optimization - this functionality is currently handled at the framework level)
0
 * Python binding
0
 
0
 ## (The MIT) License
...
1
2
3
 
4
5
 
 
6
7
 
8
9
10
11
12
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
15
16
 
 
 
 
 
 
 
 
 
 
 
 
17
18
 
 
 
19
20
 
 
 
 
 
 
21
22
 
 
23
24
25
26
27
28
29
30
31
32
...
37
38
39
40
41
 
42
...
1
2
 
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 
 
36
37
38
39
40
41
42
43
44
45
46
47
48
 
49
50
51
52
 
53
54
55
56
57
58
59
 
60
61
62
63
64
65
66
67
 
68
69
70
...
75
76
77
 
78
79
80
0
@@ -1,32 +1,70 @@
0
 """ebb
0
 
0
-A WSGI web server!
0
+A WSGI web server
0
 """
0
 import ebb_ffi
0
+import re
0
+from headers import *
0
 from signal import *
0
 
0
+# TODO fix me
0
 def interupt_handler(signum, frame):
0
   ebb_ffi.server_stop()
0
   
0
 signal(SIGINT, interupt_handler)
0
 signal(SIGTERM, interupt_handler)
0
 
0
+def body_length(body):
0
+ if len(body) == 1:
0
+ return len(body[0])
0
+ else:
0
+ # TODO
0
+ raise Exception, "Not implemented"
0
+
0
+def should_keep_alive(env):
0
+ if env['HTTP_VERSION'] == 'HTTP/1.0':
0
+ if env.has_key('HTTP_CONNECTION') and env['HTTP_CONNECTION'].upper() == 'KEEP-ALIVE':
0
+ return True
0
+ else:
0
+ if env.has_key('HTTP_CONNECTION'):
0
+ if env['HTTP_CONNECTION'].upper() != 'CLOSE':
0
+ return True
0
+ else:
0
+ return True
0
+ return False
0
 
0
-def wsgi2_request_cb(app, client):
0
- status_string, headers, body = app.__call__(client.env())
0
+# For WSGI 2.0
0
+def request_cb(app, client):
0
+ status_string, header_list, body = app.__call__(client.env())
0
+
0
+ # status_string should be something like "200 OK" or "404 Not Found"
0
+ # need to split this into an integer and human readable string for ebb
0
+ match = re.search('(\d+) (.*)', status_string)
0
+ status = int(match.group(1))
0
+ status_human = match.group(2)
0
+
0
+ # write the status
0
+ client.write_status(status, status_human)
0
   
0
- status, status_human = status_string.split(" ")
0
+ headers = Headers(header_list)
0
+ if not headers.has_key('Content-Length'):
0
+ headers['Content-Length'] = str(body_length(body))
0
   
0
- client.write_status(int(status), status_human)
0
+ # Decide if we should keep the connection alive or not
0
+ if not headers.has_key('Connection'):
0
+ if headers.has_key('Content-Length') and should_keep_alive(client.env()):
0
+ headers['Connection'] = 'Keep-Alive'
0
+ else:
0
+ headers['Connection'] = 'close'
0
   
0
- for field, value in headers:
0
+ # write the headers
0
+ for field, value in headers.items():
0
     client.write_header(field, value)
0
     
0
   for part in body:
0
     client.write_body(part)
0
   
0
   client.release()
0
- return True
0
   
0
   
0
 def start_server(app, args = {}):
0
@@ -37,4 +75,4 @@ def start_server(app, args = {}):
0
     exit(1)
0
   
0
   print "Ebb listening on port %d" % args['port']
0
- ebb_ffi.process_connections(app, wsgi2_request_cb)
0
\ No newline at end of file
0
+ ebb_ffi.process_connections(app, request_cb)
0
\ No newline at end of file
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
0
@@ -0,0 +1,139 @@
0
+# Stolen from Django
0
+from types import ListType, StringType
0
+
0
+class Headers(object):
0
+ """Manage a collection of HTTP response headers"""
0
+ def __init__(self,headers):
0
+ if type(headers) is not ListType:
0
+ raise TypeError("Headers must be a list of name/value tuples")
0
+ self._headers = headers
0
+
0
+ def __len__(self):
0
+ """Return the total number of headers, including duplicates."""
0
+ return len(self._headers)
0
+
0
+ def __setitem__(self, name, val):
0
+ """Set the value of a header."""
0
+ del self[name]
0
+ self._headers.append((name, val))
0
+
0
+ def __delitem__(self,name):
0
+ """Delete all occurrences of a header, if present.
0
+
0
+ Does *not* raise an exception if the header is missing.
0
+ """
0
+ name = name.lower()
0
+ self._headers[:] = [kv for kv in self._headers if kv[0].lower()<>name]
0
+
0
+ def __getitem__(self,name):
0
+ """Get the first header value for 'name'
0
+
0
+ Return None if the header is missing instead of raising an exception.
0
+
0
+ Note that if the header appeared multiple times, the first exactly which
0
+ occurrance gets returned is undefined. Use getall() to get all
0
+ the values matching a header field name.
0
+ """
0
+ return self.get(name)
0
+
0
+ def has_key(self, name):
0
+ """Return true if the message contains the header."""
0
+ return self.get(name) is not None
0
+
0
+ __contains__ = has_key
0
+
0
+ def get_all(self, name):
0
+ """Return a list of all the values for the named field.
0
+
0
+ These will be sorted in the order they appeared in the original header
0
+ list or were added to this instance, and may contain duplicates. Any
0
+ fields deleted and re-inserted are always appended to the header list.
0
+ If no fields exist with the given name, returns an empty list.
0
+ """
0
+ name = name.lower()
0
+ return [kv[1] for kv in self._headers if kv[0].lower()==name]
0
+
0
+
0
+ def get(self,name,default=None):
0
+ """Get the first header value for 'name', or return 'default'"""
0
+ name = name.lower()
0
+ for k,v in self._headers:
0
+ if k.lower()==name:
0
+ return v
0
+ return default
0
+
0
+ def keys(self):
0
+ """Return a list of all the header field names.
0
+
0
+ These will be sorted in the order they appeared in the original header
0
+ list, or were added to this instance, and may contain duplicates.
0
+ Any fields deleted and re-inserted are always appended to the header
0
+ list.
0
+ """
0
+ return [k for k, v in self._headers]
0
+
0
+ def values(self):
0
+ """Return a list of all header values.
0
+
0
+ These will be sorted in the order they appeared in the original header
0
+ list, or were added to this instance, and may contain duplicates.
0
+ Any fields deleted and re-inserted are always appended to the header
0
+ list.
0
+ """
0
+ return [v for k, v in self._headers]
0
+
0
+ def items(self):
0
+ """Get all the header fields and values.
0
+
0
+ These will be sorted in the order they were in the original header
0
+ list, or were added to this instance, and may contain duplicates.
0
+ Any fields deleted and re-inserted are always appended to the header
0
+ list.
0
+ """
0
+ return self._headers[:]
0
+
0
+ def __repr__(self):
0
+ return "Headers(%s)" % `self._headers`
0
+
0
+ def __str__(self):
0
+ """str() returns the formatted headers, complete with end line,
0
+ suitable for direct HTTP transmission."""
0
+ return '\r\n'.join(["%s: %s" % kv for kv in self._headers]+['',''])
0
+
0
+ def setdefault(self,name,value):
0
+ """Return first matching header value for 'name', or 'value'
0
+
0
+ If there is no header named 'name', add a new header with name 'name'
0
+ and value 'value'."""
0
+ result = self.get(name)
0
+ if result is None:
0
+ self._headers.append((name,value))
0
+ return value
0
+ else:
0
+ return result
0
+
0
+ def add_header(self, _name, _value, **_params):
0
+ """Extended header setting.
0
+
0
+ _name is the header field to add. keyword arguments can be used to set
0
+ additional parameters for the header field, with underscores converted
0
+ to dashes. Normally the parameter will be added as key="value" unless
0
+ value is None, in which case only the key will be added.
0
+
0
+ Example:
0
+
0
+ h.add_header('content-disposition', 'attachment', filename='bud.gif')
0
+
0
+ Note that unlike the corresponding 'email.Message' method, this does
0
+ *not* handle '(charset, language, value)' tuples: all values must be
0
+ strings or None.
0
+ """
0
+ parts = []
0
+ if _value is not None:
0
+ parts.append(_value)
0
+ for k, v in _params.items():
0
+ if v is None:
0
+ parts.append(k.replace('_', '-'))
0
+ else:
0
+ parts.append(_formatparam(k.replace('_', '-'), v))
0
+ self._headers.append((_name, "; ".join(parts)))
0
\ No newline at end of file
...
328
329
330
331
332
333
334
335
 
 
 
 
336
337
338
...
728
729
730
731
732
 
 
 
 
 
 
 
 
 
 
 
 
...
328
329
330
 
 
 
331
332
333
334
335
336
337
338
339
...
729
730
731
 
732
733
734
735
736
737
738
739
740
741
742
743
744
0
@@ -328,11 +328,12 @@ static void client_init(ebb_client *client)
0
     client->fd = fd;
0
   }
0
   
0
- /* Address */
0
- client->ip = inet_ntoa(client->sockaddr.sin_addr);
0
-
0
   set_nonblock(client->fd);
0
   
0
+ /* IP Address */
0
+ if(client->server->port)
0
+ client->ip = inet_ntoa(client->sockaddr.sin_addr);
0
+
0
   /* INITIALIZE http_parser */
0
   http_parser_init(&client->parser);
0
   client->parser.data = client;
0
@@ -728,4 +729,15 @@ int ebb_client_read(ebb_client *client, char *buffer, int length)
0
     client->nread_from_body += read;
0
     return read;
0
   }
0
-}
0
\ No newline at end of file
0
+}
0
+
0
+// int ebb_client_should_keep_alive(ebb_client*)
0
+// {
0
+// /* TODO - return boolean */
0
+// if env['HTTP_VERSION'] == 'HTTP/1.0'
0
+// return true if env['HTTP_CONNECTION'] =~ /Keep-Alive/i
0
+// else
0
+// return true unless env['HTTP_CONNECTION'] =~ /close/i
0
+// end
0
+// false
0
+// }
...
31
32
33
 
34
35
36
37
38
39
40
 
 
 
 
 
41
42
43
...
31
32
33
34
35
36
 
 
 
 
 
37
38
39
40
41
42
43
44
0
@@ -31,13 +31,14 @@ int ebb_client_read(ebb_client *client, char *buffer, int length);
0
 void ebb_client_write_status(ebb_client*, int status, const char *human_status);
0
 void ebb_client_write_header(ebb_client*, const char *field, const char *value);
0
 void ebb_client_write_body(ebb_client*, const char *data, int length);
0
+/* int ebb_client_should_keep_alive(ebb_client*); */
0
 
0
 struct ebb_env_item {
0
- int type;
0
- const char *field;
0
- int field_length;
0
- const char *value;
0
- int value_length;
0
+ int type;
0
+ const char *field;
0
+ int field_length;
0
+ const char *value;
0
+ int value_length;
0
 };
0
 
0
 struct ebb_client {
...
7
8
9
10
 
11
12
13
14
...
7
8
9
 
10
11
12
13
14
0
@@ -7,7 +7,7 @@ def simple_app(environ):
0
   """Simplest possible application object"""
0
   status = '200 OK'
0
   headers = [('Content-type','text/plain')]
0
- body = ["Hello world!\n", "Something else!", "blllllaaaaaaaah\n"]
0
+ body = ["Hello world!\n"]
0
   return([status, headers, body])
0
 
0
 ebb.start_server(simple_app, {'port': 4001})
0
\ No newline at end of file

Comments

    No one has commented yet.