ebb 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +10 -44
- data/Rakefile +123 -0
- data/ext/ebb.c +794 -0
- data/ext/ebb.h +130 -0
- data/ext/ebb_ffi.c +575 -0
- data/ext/ebb_request_parser.c +5339 -0
- data/ext/ebb_request_parser.h +97 -0
- data/ext/ebb_request_parser.rl +513 -0
- data/{src → ext}/extconf.rb +12 -8
- data/ext/rbtree.c +408 -0
- data/ext/rbtree.h +54 -0
- data/lib/ebb.rb +311 -0
- data/lib/ebb/version.rb +4 -0
- data/libev/ev++.h +803 -0
- data/libev/ev.c +24 -6
- data/libev/ev.h +4 -0
- data/libev/ev_select.c +50 -15
- data/libev/ev_vars.h +3 -0
- data/libev/ev_win32.c +3 -0
- data/libev/ev_wrap.h +2 -0
- data/libev/event.c +403 -0
- data/libev/event.h +152 -0
- metadata +26 -40
- data/benchmark/application.rb +0 -93
- data/benchmark/server_test.rb +0 -193
- data/bin/ebb_rails +0 -4
- data/ruby_lib/ebb.rb +0 -257
- data/ruby_lib/ebb/runner.rb +0 -134
- data/ruby_lib/ebb/runner/rails.rb +0 -31
- data/ruby_lib/rack/adapter/rails.rb +0 -159
- data/src/ebb.c +0 -627
- data/src/ebb.h +0 -102
- data/src/ebb_ruby.c +0 -306
- data/src/parser.c +0 -2860
- data/src/parser.h +0 -53
- data/test/basic_test.rb +0 -46
- data/test/ebb_rails_test.rb +0 -34
- data/test/env_test.rb +0 -110
- data/test/helper.rb +0 -138
data/README
CHANGED
@@ -1,20 +1,11 @@
|
|
1
|
+
(this is only for HEAD - old docs are found in the git repo)
|
2
|
+
|
1
3
|
# A Web Server Called *Ebb*
|
2
4
|
|
3
5
|
Ebb aims to be a small and fast web server specifically for hosting
|
4
|
-
dynamic web applications.
|
5
|
-
like Lighttpd, Apache, or Nginx. Rather it should be used in multiplicity
|
6
|
-
behind a load balancer and a front-end server. It is not meant to serve static files in production.
|
7
|
-
|
8
|
-
At one level Ebb is a minimalist C library that ties together the
|
9
|
-
[Mongrel state machine](http://mongrel.rubyforge.org/browser/tags/rel_1-0-1/ext/http11/http11_parser.rl)
|
10
|
-
and [libev](http://software.schmorp.de/pkg/libev.html) event loop. One can use
|
11
|
-
this library to drive a web application written in C. (Perhaps for embedded
|
12
|
-
devices?) However, most people will be interested in the binding of this
|
13
|
-
library to the Ruby programming language. The binding provides a
|
14
|
-
[Rack](http://rack.rubyforge.org/) server interface that allows it to host
|
15
|
-
Rails, Merb, or other frameworks.
|
6
|
+
dynamic Ruby language web applications.
|
16
7
|
|
17
|
-
|
8
|
+
It is a binding to [libebb](http://tinyclouds.org/libebb)
|
18
9
|
|
19
10
|
## Install
|
20
11
|
|
@@ -22,37 +13,17 @@ The Ruby binding is available as a Ruby Gem. It can be install by executing
|
|
22
13
|
|
23
14
|
gem install ebb
|
24
15
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
port install glib2
|
29
|
-
|
30
|
-
Downloads are available at
|
31
|
-
the [RubyForge project page](http://rubyforge.org/frs/?group_id=5640).
|
16
|
+
If you want SSL support you must install GnuTLS.
|
17
|
+
Ebb has no other dependencies.
|
32
18
|
|
33
19
|
## Running
|
34
20
|
|
35
|
-
|
36
|
-
`ebb_rails -h` to see all of the options but to start one can try
|
37
|
-
|
38
|
-
cd my_rails_project/
|
39
|
-
ebb_rails start
|
40
|
-
|
41
|
-
When using `ebb_rails` from monit, the monitrc entry might look like this:
|
42
|
-
|
43
|
-
check process myApp4000
|
44
|
-
with pidfile /home/webuser/myApp/current/tmp/ebb.4000.pid
|
45
|
-
start program = "/usr/bin/ruby /usr/bin/ebb_rails start -d -e production -p 4000 -P /home/webuser/myApp/current/tmp/ebb.4000.pid -c /home/webuser/myApp/current" as uid webuser and gid webuser
|
46
|
-
stop program = "/usr/bin/ruby /usr/bin/ebb_rails stop -P /home/webuser/myApp/current/tmp/ebb.4000.pid" as uid webuser and gid webuser
|
47
|
-
if totalmem > 120.0 MB for 2 cycles then restart
|
48
|
-
if loadavg(5min) greater than 10 for 8 cycles then restart
|
49
|
-
group myApp
|
50
|
-
|
51
|
-
To use Ebb with a different framework you will have to do a small amount of
|
52
|
-
hacking at the moment! :)
|
21
|
+
Use Ebb.start_server()
|
53
22
|
|
54
23
|
## Speed
|
55
24
|
|
25
|
+
(these stats are out of date)
|
26
|
+
|
56
27
|
Because Ebb handles most of the processing in C, it is able to do work
|
57
28
|
often times more efficiently than other Ruby language web servers.
|
58
29
|
|
@@ -75,14 +46,9 @@ can be retrieved by executing
|
|
75
46
|
|
76
47
|
git clone git://github.com/ry/ebb.git
|
77
48
|
|
78
|
-
Here are some features that I would like to add:
|
79
|
-
* HTTP 1.1 Expect/Continue (RFC 2616, sections 8.2.3 and 10.1.1)
|
80
|
-
* A parser for multipart/form-data (only for optimization - this functionality is currently handled at the framework level)
|
81
|
-
* Python binding
|
82
|
-
|
83
49
|
## (The MIT) License
|
84
50
|
|
85
|
-
Copyright
|
51
|
+
Copyright (c) 2008 [Ryah Dahl](http://tinyclouds.org) (ry at tiny clouds dot org)
|
86
52
|
|
87
53
|
<div id="license">
|
88
54
|
Permission is hereby granted, free of charge, to any person obtaining
|
data/Rakefile
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/clean'
|
5
|
+
|
6
|
+
class String
|
7
|
+
def /(other)
|
8
|
+
File.join(self, other)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'lib/ebb/version'
|
13
|
+
|
14
|
+
libev_dist = "http://dist.schmorp.de/libev/Attic"
|
15
|
+
libev_release = "libev-3.43.tar.gz"
|
16
|
+
libev_url = File.join(libev_dist, libev_release)
|
17
|
+
|
18
|
+
LIBEBBFILES = ['ebb.c', 'ebb.h',
|
19
|
+
'ebb_request_parser.rl', 'ebb_request_parser.c', 'ebb_request_parser.h',
|
20
|
+
'rbtree.c', 'rbtree.h']
|
21
|
+
SRCEBBFILES = LIBEBBFILES.map { |f| "ext" / f }
|
22
|
+
|
23
|
+
DISTFILES = FileList.new('libev/*.{c,h}', 'lib/**/*.rb', 'ext/*.{rb,rl,c,h}', 'README', 'Rakefile') + SRCEBBFILES
|
24
|
+
CLEAN.add ["**/*.{o,bundle,so,obj,pdb,lib,def,exp}", "benchmark/*.dump", 'site/index.html']
|
25
|
+
CLOBBER.add ['ext/Makefile', 'ext/mkmf.log'] + SRCEBBFILES
|
26
|
+
|
27
|
+
Rake::TestTask.new do |t|
|
28
|
+
t.test_files = FileList.new("test/*.rb")
|
29
|
+
t.verbose = true
|
30
|
+
end
|
31
|
+
|
32
|
+
LIBEBBFILES.each do |f|
|
33
|
+
file(".libebb"/f => ".libebb")
|
34
|
+
file("ext"/f => ".libebb"/f) do |t|
|
35
|
+
sh "cp .libebb/#{f} ext/#{f}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
task(:default => [:compile])
|
40
|
+
|
41
|
+
task(:compile => ['ext/Makefile','libev'] + SRCEBBFILES) do
|
42
|
+
sh "cd ext && make"
|
43
|
+
end
|
44
|
+
|
45
|
+
file "libev" do
|
46
|
+
puts "downloading libev"
|
47
|
+
sh "wget #{libev_url}" do |ok, res|
|
48
|
+
if ! ok
|
49
|
+
puts "Couldn't download libev. Please put #{libev_url} in here and try again"
|
50
|
+
exit 1
|
51
|
+
end
|
52
|
+
end
|
53
|
+
sh "tar -zxf #{libev_release}"
|
54
|
+
sh "mv #{libev_release.sub('.tar.gz', '')} libev"
|
55
|
+
end
|
56
|
+
|
57
|
+
file ".libebb" do
|
58
|
+
sh "git clone git://github.com/ry/libebb.git .libebb"
|
59
|
+
end
|
60
|
+
|
61
|
+
file('ext/Makefile' => 'ext/extconf.rb') do
|
62
|
+
sh "cd ext && ruby extconf.rb"
|
63
|
+
end
|
64
|
+
|
65
|
+
file(".libebb/ebb_request_parser.c" => '.libebb/ebb_request_parser.rl') do
|
66
|
+
sh 'ragel -s -G2 .libebb/ebb_request_parser.rl'
|
67
|
+
end
|
68
|
+
|
69
|
+
task(:test => DISTFILES)
|
70
|
+
Rake::TestTask.new do |t|
|
71
|
+
t.test_files = 'test/basic_test.rb'
|
72
|
+
t.verbose = true
|
73
|
+
end
|
74
|
+
|
75
|
+
task(:site_upload => :site) do
|
76
|
+
sh 'scp -r site/* rydahl@rubyforge.org:/var/www/gforge-projects/ebb/'
|
77
|
+
end
|
78
|
+
task(:site => 'site/index.html')
|
79
|
+
file('site/index.html' => %w{README site/style.css}) do
|
80
|
+
require 'rubygems'
|
81
|
+
require 'bluecloth'
|
82
|
+
doc = BlueCloth.new(File.read('README'))
|
83
|
+
template = <<-HEREDOC
|
84
|
+
<html>
|
85
|
+
<head>
|
86
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
87
|
+
<title>Ebb</title>
|
88
|
+
<link rel="alternate" href="http://max.kanat.us/tag-syndicate/?user=four&tag=ebb" title="RSS Feed" type="application/rss+xml" />
|
89
|
+
<link type="text/css" rel="stylesheet" href="style.css" media="screen"/>
|
90
|
+
</head>
|
91
|
+
<body>
|
92
|
+
<div id="content">CONTENT</div>
|
93
|
+
</body>
|
94
|
+
</html>
|
95
|
+
HEREDOC
|
96
|
+
|
97
|
+
File.open('site/index.html', "w+") do |f|
|
98
|
+
f.write template.sub('CONTENT', doc.to_html)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
spec = Gem::Specification.new do |s|
|
103
|
+
s.platform = Gem::Platform::RUBY
|
104
|
+
s.summary = "A Web Server"
|
105
|
+
s.description = ''
|
106
|
+
s.name = 'ebb'
|
107
|
+
s.author = 'ry dahl'
|
108
|
+
s.email = 'ry at tiny clouds dot org'
|
109
|
+
s.homepage = 'http://ebb.rubyforge.org'
|
110
|
+
s.version = Ebb::VERSION
|
111
|
+
s.rubyforge_project = 'ebb'
|
112
|
+
|
113
|
+
s.required_ruby_version = '>= 1.8.4'
|
114
|
+
|
115
|
+
s.require_path = 'lib'
|
116
|
+
s.extensions = 'ext/extconf.rb'
|
117
|
+
|
118
|
+
s.files = DISTFILES
|
119
|
+
end
|
120
|
+
|
121
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
122
|
+
pkg.need_zip = true
|
123
|
+
end
|
data/ext/ebb.c
ADDED
@@ -0,0 +1,794 @@
|
|
1
|
+
/* libebb web server library
|
2
|
+
* Copyright 2008 ryah dahl, ry at tiny clouds punkt org
|
3
|
+
*
|
4
|
+
* This software may be distributed under the "MIT" license included in the
|
5
|
+
* README
|
6
|
+
*/
|
7
|
+
|
8
|
+
#include <assert.h>
|
9
|
+
#include <string.h>
|
10
|
+
#include <fcntl.h>
|
11
|
+
#include <sys/types.h>
|
12
|
+
#include <sys/socket.h>
|
13
|
+
#include <netinet/tcp.h> /* TCP_NODELAY */
|
14
|
+
#include <netinet/in.h> /* inet_ntoa */
|
15
|
+
#include <arpa/inet.h> /* inet_ntoa */
|
16
|
+
#include <unistd.h>
|
17
|
+
#include <stdio.h> /* perror */
|
18
|
+
#include <errno.h> /* perror */
|
19
|
+
#include <stdlib.h> /* for the default methods */
|
20
|
+
#include <ev.h>
|
21
|
+
|
22
|
+
#include "ebb.h"
|
23
|
+
#include "ebb_request_parser.h"
|
24
|
+
#ifdef HAVE_GNUTLS
|
25
|
+
# include <gnutls/gnutls.h>
|
26
|
+
# include "rbtree.h" /* for session_cache */
|
27
|
+
#endif
|
28
|
+
|
29
|
+
#ifndef TRUE
|
30
|
+
# define TRUE 1
|
31
|
+
#endif
|
32
|
+
#ifndef FALSE
|
33
|
+
# define FALSE 0
|
34
|
+
#endif
|
35
|
+
#ifndef MIN
|
36
|
+
# define MIN(a,b) (a < b ? a : b)
|
37
|
+
#endif
|
38
|
+
|
39
|
+
#define error(FORMAT, ...) fprintf(stderr, "error: " FORMAT "\n", ##__VA_ARGS__)
|
40
|
+
|
41
|
+
#define CONNECTION_HAS_SOMETHING_TO_WRITE (connection->to_write != NULL)
|
42
|
+
|
43
|
+
static void
|
44
|
+
set_nonblock (int fd)
|
45
|
+
{
|
46
|
+
int flags = fcntl(fd, F_GETFL, 0);
|
47
|
+
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
48
|
+
assert(0 <= r && "Setting socket non-block failed!");
|
49
|
+
}
|
50
|
+
|
51
|
+
static ssize_t
|
52
|
+
nosigpipe_push(void *data, const void *buf, size_t len)
|
53
|
+
{
|
54
|
+
int fd = (int)data;
|
55
|
+
int flags = 0;
|
56
|
+
#ifdef MSG_NOSIGNAL
|
57
|
+
flags = MSG_NOSIGNAL;
|
58
|
+
#endif
|
59
|
+
return send(fd, buf, len, flags);
|
60
|
+
}
|
61
|
+
|
62
|
+
static void
|
63
|
+
close_connection(ebb_connection *connection)
|
64
|
+
{
|
65
|
+
#ifdef HAVE_GNUTLS
|
66
|
+
if(connection->server->secure)
|
67
|
+
ev_io_stop(connection->server->loop, &connection->handshake_watcher);
|
68
|
+
#endif
|
69
|
+
ev_io_stop(connection->server->loop, &connection->read_watcher);
|
70
|
+
ev_io_stop(connection->server->loop, &connection->write_watcher);
|
71
|
+
ev_timer_stop(connection->server->loop, &connection->timeout_watcher);
|
72
|
+
|
73
|
+
if(0 > close(connection->fd))
|
74
|
+
error("problem closing connection fd");
|
75
|
+
|
76
|
+
connection->open = FALSE;
|
77
|
+
|
78
|
+
if(connection->on_close)
|
79
|
+
connection->on_close(connection);
|
80
|
+
/* No access to the connection past this point!
|
81
|
+
* The user is allowed to free in the callback
|
82
|
+
*/
|
83
|
+
}
|
84
|
+
|
85
|
+
#ifdef HAVE_GNUTLS
|
86
|
+
#define GNUTLS_NEED_WRITE (gnutls_record_get_direction(connection->session) == 1)
|
87
|
+
#define GNUTLS_NEED_READ (gnutls_record_get_direction(connection->session) == 0)
|
88
|
+
|
89
|
+
#define EBB_MAX_SESSION_KEY 32
|
90
|
+
#define EBB_MAX_SESSION_VALUE 512
|
91
|
+
|
92
|
+
struct session_cache {
|
93
|
+
struct rbtree_node_t node;
|
94
|
+
|
95
|
+
gnutls_datum_t key;
|
96
|
+
gnutls_datum_t value;
|
97
|
+
|
98
|
+
char key_storage[EBB_MAX_SESSION_KEY];
|
99
|
+
char value_storage[EBB_MAX_SESSION_VALUE];
|
100
|
+
};
|
101
|
+
|
102
|
+
static int
|
103
|
+
session_cache_compare (void *left, void *right)
|
104
|
+
{
|
105
|
+
gnutls_datum_t *left_key = left;
|
106
|
+
gnutls_datum_t *right_key = right;
|
107
|
+
if(left_key->size < right_key->size)
|
108
|
+
return -1;
|
109
|
+
else if(left_key->size > right_key->size)
|
110
|
+
return 1;
|
111
|
+
else
|
112
|
+
return memcmp( left_key->data
|
113
|
+
, right_key->data
|
114
|
+
, MIN(left_key->size, right_key->size)
|
115
|
+
);
|
116
|
+
}
|
117
|
+
|
118
|
+
static int
|
119
|
+
session_cache_store(void *data, gnutls_datum_t key, gnutls_datum_t value)
|
120
|
+
{
|
121
|
+
rbtree tree = data;
|
122
|
+
|
123
|
+
if( tree == NULL
|
124
|
+
|| key.size > EBB_MAX_SESSION_KEY
|
125
|
+
|| value.size > EBB_MAX_SESSION_VALUE
|
126
|
+
) return -1;
|
127
|
+
|
128
|
+
struct session_cache *cache = gnutls_malloc(sizeof(struct session_cache));
|
129
|
+
|
130
|
+
memcpy (cache->key_storage, key.data, key.size);
|
131
|
+
cache->key.size = key.size;
|
132
|
+
cache->key.data = (void*)cache->key_storage;
|
133
|
+
|
134
|
+
memcpy (cache->value_storage, value.data, value.size);
|
135
|
+
cache->value.size = value.size;
|
136
|
+
cache->value.data = (void*)cache->value_storage;
|
137
|
+
|
138
|
+
cache->node.key = &cache->key;
|
139
|
+
cache->node.value = &cache;
|
140
|
+
|
141
|
+
rbtree_insert(tree, (rbtree_node)cache);
|
142
|
+
|
143
|
+
//printf("session_cache_store\n");
|
144
|
+
|
145
|
+
return 0;
|
146
|
+
}
|
147
|
+
|
148
|
+
static gnutls_datum_t
|
149
|
+
session_cache_retrieve (void *data, gnutls_datum_t key)
|
150
|
+
{
|
151
|
+
rbtree tree = data;
|
152
|
+
gnutls_datum_t res = { NULL, 0 };
|
153
|
+
struct session_cache *cache = rbtree_lookup(tree, &key);
|
154
|
+
|
155
|
+
if(cache == NULL)
|
156
|
+
return res;
|
157
|
+
|
158
|
+
res.size = cache->value.size;
|
159
|
+
res.data = gnutls_malloc (res.size);
|
160
|
+
if(res.data == NULL)
|
161
|
+
return res;
|
162
|
+
|
163
|
+
memcpy(res.data, cache->value.data, res.size);
|
164
|
+
|
165
|
+
//printf("session_cache_retrieve\n");
|
166
|
+
|
167
|
+
return res;
|
168
|
+
}
|
169
|
+
|
170
|
+
static int
|
171
|
+
session_cache_remove (void *data, gnutls_datum_t key)
|
172
|
+
{
|
173
|
+
rbtree tree = data;
|
174
|
+
|
175
|
+
if(tree == NULL)
|
176
|
+
return -1;
|
177
|
+
|
178
|
+
struct session_cache *cache = (struct session_cache *)rbtree_delete(tree, &key);
|
179
|
+
if(cache == NULL)
|
180
|
+
return -1;
|
181
|
+
|
182
|
+
gnutls_free(cache);
|
183
|
+
|
184
|
+
//printf("session_cache_remove\n");
|
185
|
+
|
186
|
+
return 0;
|
187
|
+
}
|
188
|
+
|
189
|
+
static void
|
190
|
+
on_handshake(struct ev_loop *loop ,ev_io *watcher, int revents)
|
191
|
+
{
|
192
|
+
ebb_connection *connection = watcher->data;
|
193
|
+
|
194
|
+
//printf("on_handshake\n");
|
195
|
+
|
196
|
+
assert(ev_is_active(&connection->timeout_watcher));
|
197
|
+
assert(!ev_is_active(&connection->read_watcher));
|
198
|
+
assert(!ev_is_active(&connection->write_watcher));
|
199
|
+
|
200
|
+
if(EV_ERROR & revents) {
|
201
|
+
error("on_handshake() got error event, closing connection.n");
|
202
|
+
goto error;
|
203
|
+
}
|
204
|
+
|
205
|
+
int r = gnutls_handshake(connection->session);
|
206
|
+
if(r < 0) {
|
207
|
+
if(gnutls_error_is_fatal(r)) goto error;
|
208
|
+
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN)
|
209
|
+
ev_io_set( watcher
|
210
|
+
, connection->fd
|
211
|
+
, EV_ERROR | (GNUTLS_NEED_WRITE ? EV_WRITE : EV_READ)
|
212
|
+
);
|
213
|
+
return;
|
214
|
+
}
|
215
|
+
|
216
|
+
ebb_connection_reset_timeout(connection);
|
217
|
+
ev_io_stop(loop, watcher);
|
218
|
+
|
219
|
+
ev_io_start(loop, &connection->read_watcher);
|
220
|
+
if(CONNECTION_HAS_SOMETHING_TO_WRITE)
|
221
|
+
ev_io_start(loop, &connection->write_watcher);
|
222
|
+
|
223
|
+
return;
|
224
|
+
error:
|
225
|
+
close_connection(connection);
|
226
|
+
}
|
227
|
+
|
228
|
+
#endif /* HAVE_GNUTLS */
|
229
|
+
|
230
|
+
|
231
|
+
/* Internal callback
|
232
|
+
* called by connection->timeout_watcher
|
233
|
+
*/
|
234
|
+
static void
|
235
|
+
on_timeout(struct ev_loop *loop, ev_timer *watcher, int revents)
|
236
|
+
{
|
237
|
+
ebb_connection *connection = watcher->data;
|
238
|
+
|
239
|
+
assert(watcher == &connection->timeout_watcher);
|
240
|
+
|
241
|
+
//printf("on_timeout\n");
|
242
|
+
|
243
|
+
/* if on_timeout returns true, we don't time out */
|
244
|
+
if(connection->on_timeout) {
|
245
|
+
int r = connection->on_timeout(connection);
|
246
|
+
|
247
|
+
if(r == EBB_AGAIN) {
|
248
|
+
ebb_connection_reset_timeout(connection);
|
249
|
+
return;
|
250
|
+
}
|
251
|
+
}
|
252
|
+
|
253
|
+
ebb_connection_schedule_close(connection);
|
254
|
+
}
|
255
|
+
|
256
|
+
/* Internal callback
|
257
|
+
* called by connection->read_watcher
|
258
|
+
*/
|
259
|
+
static void
|
260
|
+
on_readable(struct ev_loop *loop, ev_io *watcher, int revents)
|
261
|
+
{
|
262
|
+
ebb_connection *connection = watcher->data;
|
263
|
+
char base[TCP_MAXWIN];
|
264
|
+
char *recv_buffer = base;
|
265
|
+
size_t recv_buffer_size = TCP_MAXWIN;
|
266
|
+
ssize_t recved;
|
267
|
+
|
268
|
+
//printf("on_readable\n");
|
269
|
+
|
270
|
+
// TODO -- why is this broken?
|
271
|
+
//assert(ev_is_active(&connection->timeout_watcher));
|
272
|
+
assert(watcher == &connection->read_watcher);
|
273
|
+
|
274
|
+
if(EV_ERROR & revents) {
|
275
|
+
error("on_readable() got error event, closing connection.");
|
276
|
+
goto error;
|
277
|
+
}
|
278
|
+
|
279
|
+
ebb_buf *buf = NULL;
|
280
|
+
if(connection->new_buf) {
|
281
|
+
buf = connection->new_buf(connection);
|
282
|
+
if(buf == NULL) return;
|
283
|
+
recv_buffer = buf->base;
|
284
|
+
recv_buffer_size = buf->len;
|
285
|
+
}
|
286
|
+
|
287
|
+
#ifdef HAVE_GNUTLS
|
288
|
+
assert(!ev_is_active(&connection->handshake_watcher));
|
289
|
+
|
290
|
+
if(connection->server->secure) {
|
291
|
+
recved = gnutls_record_recv( connection->session
|
292
|
+
, recv_buffer
|
293
|
+
, recv_buffer_size
|
294
|
+
);
|
295
|
+
if(recved <= 0) {
|
296
|
+
if(gnutls_error_is_fatal(recved)) goto error;
|
297
|
+
if( (recved == GNUTLS_E_INTERRUPTED || recved == GNUTLS_E_AGAIN)
|
298
|
+
&& GNUTLS_NEED_WRITE
|
299
|
+
) ev_io_start(loop, &connection->write_watcher);
|
300
|
+
return;
|
301
|
+
}
|
302
|
+
} else {
|
303
|
+
#endif /* HAVE_GNUTLS */
|
304
|
+
|
305
|
+
recved = recv(connection->fd, recv_buffer, recv_buffer_size, 0);
|
306
|
+
if(recved < 0) goto error;
|
307
|
+
if(recved == 0) return;
|
308
|
+
|
309
|
+
#ifdef HAVE_GNUTLS
|
310
|
+
}
|
311
|
+
#endif /* HAVE_GNUTLS */
|
312
|
+
|
313
|
+
ebb_connection_reset_timeout(connection);
|
314
|
+
|
315
|
+
ebb_request_parser_execute(&connection->parser, recv_buffer, recved);
|
316
|
+
|
317
|
+
/* parse error? just drop the client. screw the 400 response */
|
318
|
+
if(ebb_request_parser_has_error(&connection->parser)) goto error;
|
319
|
+
|
320
|
+
if(buf && buf->on_release)
|
321
|
+
buf->on_release(buf);
|
322
|
+
|
323
|
+
return;
|
324
|
+
error:
|
325
|
+
ebb_connection_schedule_close(connection);
|
326
|
+
}
|
327
|
+
|
328
|
+
/* Internal callback
|
329
|
+
* called by connection->write_watcher
|
330
|
+
*/
|
331
|
+
static void
|
332
|
+
on_writable(struct ev_loop *loop, ev_io *watcher, int revents)
|
333
|
+
{
|
334
|
+
ebb_connection *connection = watcher->data;
|
335
|
+
ssize_t sent;
|
336
|
+
|
337
|
+
//printf("on_writable\n");
|
338
|
+
|
339
|
+
assert(CONNECTION_HAS_SOMETHING_TO_WRITE);
|
340
|
+
assert(connection->written <= connection->to_write_len);
|
341
|
+
// TODO -- why is this broken?
|
342
|
+
//assert(ev_is_active(&connection->timeout_watcher));
|
343
|
+
assert(watcher == &connection->write_watcher);
|
344
|
+
|
345
|
+
#ifdef HAVE_GNUTLS
|
346
|
+
assert(!ev_is_active(&connection->handshake_watcher));
|
347
|
+
|
348
|
+
if(connection->server->secure) {
|
349
|
+
sent = gnutls_record_send( connection->session
|
350
|
+
, connection->to_write + connection->written
|
351
|
+
, connection->to_write_len - connection->written
|
352
|
+
);
|
353
|
+
if(sent <= 0) {
|
354
|
+
if(gnutls_error_is_fatal(sent)) goto error;
|
355
|
+
if( (sent == GNUTLS_E_INTERRUPTED || sent == GNUTLS_E_AGAIN)
|
356
|
+
&& GNUTLS_NEED_READ
|
357
|
+
) ev_io_stop(loop, watcher);
|
358
|
+
return;
|
359
|
+
}
|
360
|
+
} else {
|
361
|
+
#endif /* HAVE_GNUTLS */
|
362
|
+
|
363
|
+
sent = nosigpipe_push( (void*)connection->fd
|
364
|
+
, connection->to_write + connection->written
|
365
|
+
, connection->to_write_len - connection->written
|
366
|
+
);
|
367
|
+
if(sent < 0) goto error;
|
368
|
+
if(sent == 0) return;
|
369
|
+
|
370
|
+
#ifdef HAVE_GNUTLS
|
371
|
+
}
|
372
|
+
#endif /* HAVE_GNUTLS */
|
373
|
+
|
374
|
+
ebb_connection_reset_timeout(connection);
|
375
|
+
|
376
|
+
connection->written += sent;
|
377
|
+
|
378
|
+
if(connection->written == connection->to_write_len) {
|
379
|
+
ev_io_stop(loop, watcher);
|
380
|
+
connection->to_write = NULL;
|
381
|
+
|
382
|
+
if(connection->after_write_cb)
|
383
|
+
connection->after_write_cb(connection);
|
384
|
+
}
|
385
|
+
return;
|
386
|
+
error:
|
387
|
+
error("close connection on write.");
|
388
|
+
ebb_connection_schedule_close(connection);
|
389
|
+
}
|
390
|
+
|
391
|
+
#ifdef HAVE_GNUTLS
|
392
|
+
|
393
|
+
static void
|
394
|
+
on_goodbye_tls(struct ev_loop *loop, ev_io *watcher, int revents)
|
395
|
+
{
|
396
|
+
ebb_connection *connection = watcher->data;
|
397
|
+
assert(watcher == &connection->goodbye_tls_watcher);
|
398
|
+
|
399
|
+
if(EV_ERROR & revents) {
|
400
|
+
error("on_goodbye() got error event, closing connection.");
|
401
|
+
goto die;
|
402
|
+
}
|
403
|
+
|
404
|
+
int r = gnutls_bye(connection->session, GNUTLS_SHUT_RDWR);
|
405
|
+
if(r < 0) {
|
406
|
+
if(gnutls_error_is_fatal(r)) goto die;
|
407
|
+
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN)
|
408
|
+
ev_io_set( watcher
|
409
|
+
, connection->fd
|
410
|
+
, EV_ERROR | (GNUTLS_NEED_WRITE ? EV_WRITE : EV_READ)
|
411
|
+
);
|
412
|
+
return;
|
413
|
+
}
|
414
|
+
|
415
|
+
die:
|
416
|
+
ev_io_stop(loop, watcher);
|
417
|
+
if(connection->session)
|
418
|
+
gnutls_deinit(connection->session);
|
419
|
+
close_connection(connection);
|
420
|
+
}
|
421
|
+
#endif /* HAVE_GNUTLS*/
|
422
|
+
|
423
|
+
static void
|
424
|
+
on_goodbye(struct ev_loop *loop, ev_timer *watcher, int revents)
|
425
|
+
{
|
426
|
+
ebb_connection *connection = watcher->data;
|
427
|
+
assert(watcher == &connection->goodbye_watcher);
|
428
|
+
|
429
|
+
close_connection(connection);
|
430
|
+
}
|
431
|
+
|
432
|
+
|
433
|
+
static ebb_request*
|
434
|
+
new_request_wrapper(void *data)
|
435
|
+
{
|
436
|
+
ebb_connection *connection = data;
|
437
|
+
if(connection->new_request)
|
438
|
+
return connection->new_request(connection);
|
439
|
+
return NULL;
|
440
|
+
}
|
441
|
+
|
442
|
+
/* Internal callback
|
443
|
+
* Called by server->connection_watcher.
|
444
|
+
*/
|
445
|
+
static void
|
446
|
+
on_connection(struct ev_loop *loop, ev_io *watcher, int revents)
|
447
|
+
{
|
448
|
+
ebb_server *server = watcher->data;
|
449
|
+
|
450
|
+
//printf("on connection!\n");
|
451
|
+
|
452
|
+
assert(server->listening);
|
453
|
+
assert(server->loop == loop);
|
454
|
+
assert(&server->connection_watcher == watcher);
|
455
|
+
|
456
|
+
if(EV_ERROR & revents) {
|
457
|
+
error("on_connection() got error event, closing server.");
|
458
|
+
ebb_server_unlisten(server);
|
459
|
+
return;
|
460
|
+
}
|
461
|
+
|
462
|
+
|
463
|
+
struct sockaddr_in addr; // connector's address information
|
464
|
+
socklen_t addr_len = sizeof(addr);
|
465
|
+
int fd = accept( server->fd
|
466
|
+
, (struct sockaddr*) & addr
|
467
|
+
, & addr_len
|
468
|
+
);
|
469
|
+
if(fd < 0) {
|
470
|
+
perror("accept()");
|
471
|
+
return;
|
472
|
+
}
|
473
|
+
|
474
|
+
ebb_connection *connection = NULL;
|
475
|
+
if(server->new_connection)
|
476
|
+
connection = server->new_connection(server, &addr);
|
477
|
+
if(connection == NULL) {
|
478
|
+
close(fd);
|
479
|
+
return;
|
480
|
+
}
|
481
|
+
|
482
|
+
set_nonblock(fd);
|
483
|
+
connection->fd = fd;
|
484
|
+
connection->open = TRUE;
|
485
|
+
connection->server = server;
|
486
|
+
memcpy(&connection->sockaddr, &addr, addr_len);
|
487
|
+
if(server->port[0] != '\0')
|
488
|
+
connection->ip = inet_ntoa(connection->sockaddr.sin_addr);
|
489
|
+
|
490
|
+
#ifdef SO_NOSIGPIPE
|
491
|
+
int arg = 1;
|
492
|
+
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &arg, sizeof(int));
|
493
|
+
#endif
|
494
|
+
|
495
|
+
#ifdef HAVE_GNUTLS
|
496
|
+
if(server->secure) {
|
497
|
+
gnutls_init(&connection->session, GNUTLS_SERVER);
|
498
|
+
gnutls_transport_set_lowat(connection->session, 0);
|
499
|
+
gnutls_set_default_priority(connection->session);
|
500
|
+
gnutls_credentials_set(connection->session, GNUTLS_CRD_CERTIFICATE, connection->server->credentials);
|
501
|
+
|
502
|
+
gnutls_transport_set_ptr(connection->session, (gnutls_transport_ptr) fd);
|
503
|
+
gnutls_transport_set_push_function(connection->session, nosigpipe_push);
|
504
|
+
|
505
|
+
gnutls_db_set_ptr (connection->session, &server->session_cache);
|
506
|
+
gnutls_db_set_store_function (connection->session, session_cache_store);
|
507
|
+
gnutls_db_set_retrieve_function (connection->session, session_cache_retrieve);
|
508
|
+
gnutls_db_set_remove_function (connection->session, session_cache_remove);
|
509
|
+
}
|
510
|
+
|
511
|
+
ev_io_set(&connection->handshake_watcher, connection->fd, EV_READ | EV_WRITE | EV_ERROR);
|
512
|
+
#endif /* HAVE_GNUTLS */
|
513
|
+
|
514
|
+
/* Note: not starting the write watcher until there is data to be written */
|
515
|
+
ev_io_set(&connection->write_watcher, connection->fd, EV_WRITE);
|
516
|
+
ev_io_set(&connection->read_watcher, connection->fd, EV_READ | EV_ERROR);
|
517
|
+
/* XXX: seperate error watcher? */
|
518
|
+
|
519
|
+
ev_timer_start(loop, &connection->timeout_watcher);
|
520
|
+
|
521
|
+
#ifdef HAVE_GNUTLS
|
522
|
+
if(server->secure) {
|
523
|
+
ev_io_start(loop, &connection->handshake_watcher);
|
524
|
+
return;
|
525
|
+
}
|
526
|
+
#endif
|
527
|
+
|
528
|
+
ev_io_start(loop, &connection->read_watcher);
|
529
|
+
}
|
530
|
+
|
531
|
+
/**
|
532
|
+
* Begin the server listening on a file descriptor. This DOES NOT start the
|
533
|
+
* event loop. Start the event loop after making this call.
|
534
|
+
*/
|
535
|
+
int
|
536
|
+
ebb_server_listen_on_fd(ebb_server *server, const int fd)
|
537
|
+
{
|
538
|
+
assert(server->listening == FALSE);
|
539
|
+
|
540
|
+
if (listen(fd, EBB_MAX_CONNECTIONS) < 0) {
|
541
|
+
perror("listen()");
|
542
|
+
return -1;
|
543
|
+
}
|
544
|
+
|
545
|
+
set_nonblock(fd); /* XXX superfluous? */
|
546
|
+
|
547
|
+
server->fd = fd;
|
548
|
+
server->listening = TRUE;
|
549
|
+
|
550
|
+
ev_io_set (&server->connection_watcher, server->fd, EV_READ | EV_ERROR);
|
551
|
+
ev_io_start (server->loop, &server->connection_watcher);
|
552
|
+
|
553
|
+
return server->fd;
|
554
|
+
}
|
555
|
+
|
556
|
+
|
557
|
+
/**
|
558
|
+
* Begin the server listening on a file descriptor This DOES NOT start the
|
559
|
+
* event loop. Start the event loop after making this call.
|
560
|
+
*/
|
561
|
+
int
|
562
|
+
ebb_server_listen_on_port(ebb_server *server, const int port)
|
563
|
+
{
|
564
|
+
int fd = -1;
|
565
|
+
struct linger ling = {0, 0};
|
566
|
+
struct sockaddr_in addr;
|
567
|
+
int flags = 1;
|
568
|
+
|
569
|
+
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
|
570
|
+
perror("socket()");
|
571
|
+
goto error;
|
572
|
+
}
|
573
|
+
|
574
|
+
flags = 1;
|
575
|
+
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags));
|
576
|
+
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags));
|
577
|
+
setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling));
|
578
|
+
|
579
|
+
/* XXX: Sending single byte chunks in a response body? Perhaps there is a
|
580
|
+
* need to enable the Nagel algorithm dynamically. For now disabling.
|
581
|
+
*/
|
582
|
+
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags));
|
583
|
+
|
584
|
+
/* the memset call clears nonstandard fields in some impementations that
|
585
|
+
* otherwise mess things up.
|
586
|
+
*/
|
587
|
+
memset(&addr, 0, sizeof(addr));
|
588
|
+
|
589
|
+
addr.sin_family = AF_INET;
|
590
|
+
addr.sin_port = htons(port);
|
591
|
+
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
592
|
+
|
593
|
+
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
594
|
+
perror("bind()");
|
595
|
+
goto error;
|
596
|
+
}
|
597
|
+
|
598
|
+
int ret = ebb_server_listen_on_fd(server, fd);
|
599
|
+
if (ret >= 0) {
|
600
|
+
sprintf(server->port, "%d", port);
|
601
|
+
}
|
602
|
+
return ret;
|
603
|
+
error:
|
604
|
+
if(fd > 0) close(fd);
|
605
|
+
return -1;
|
606
|
+
}
|
607
|
+
|
608
|
+
/**
|
609
|
+
* Stops the server. Will not accept new connections. Does not drop
|
610
|
+
* existing connections.
|
611
|
+
*/
|
612
|
+
void
|
613
|
+
ebb_server_unlisten(ebb_server *server)
|
614
|
+
{
|
615
|
+
if(server->listening) {
|
616
|
+
ev_io_stop(server->loop, &server->connection_watcher);
|
617
|
+
close(server->fd);
|
618
|
+
server->port[0] = '\0';
|
619
|
+
server->listening = FALSE;
|
620
|
+
}
|
621
|
+
}
|
622
|
+
|
623
|
+
/**
|
624
|
+
* Initialize an ebb_server structure. After calling ebb_server_init set
|
625
|
+
* the callback server->new_connection and, optionally, callback data
|
626
|
+
* server->data. The new connection MUST be initialized with
|
627
|
+
* ebb_connection_init before returning it to the server.
|
628
|
+
*
|
629
|
+
* @param server the server to initialize
|
630
|
+
* @param loop a libev loop
|
631
|
+
*/
|
632
|
+
void
|
633
|
+
ebb_server_init(ebb_server *server, struct ev_loop *loop)
|
634
|
+
{
|
635
|
+
server->loop = loop;
|
636
|
+
server->listening = FALSE;
|
637
|
+
server->port[0] = '\0';
|
638
|
+
server->fd = -1;
|
639
|
+
server->connection_watcher.data = server;
|
640
|
+
ev_init (&server->connection_watcher, on_connection);
|
641
|
+
server->secure = FALSE;
|
642
|
+
|
643
|
+
#ifdef HAVE_GNUTLS
|
644
|
+
rbtree_init(&server->session_cache, session_cache_compare);
|
645
|
+
server->credentials = NULL;
|
646
|
+
#endif
|
647
|
+
|
648
|
+
server->new_connection = NULL;
|
649
|
+
server->data = NULL;
|
650
|
+
}
|
651
|
+
|
652
|
+
|
653
|
+
#ifdef HAVE_GNUTLS
|
654
|
+
/* similar to server_init.
|
655
|
+
*
|
656
|
+
* the user of secure server might want to set additional callbacks from
|
657
|
+
* GNUTLS. In particular
|
658
|
+
* gnutls_global_set_mem_functions()
|
659
|
+
* gnutls_global_set_log_function()
|
660
|
+
* Also see the note above ebb_connection_init() about setting gnutls cache
|
661
|
+
* access functions
|
662
|
+
*
|
663
|
+
* cert_file: the filename of a PEM certificate file
|
664
|
+
*
|
665
|
+
* key_file: the filename of a private key. Currently only PKCS-1 encoded
|
666
|
+
* RSA and DSA private keys are accepted.
|
667
|
+
*/
|
668
|
+
int
|
669
|
+
ebb_server_set_secure (ebb_server *server, const char *cert_file, const char *key_file)
|
670
|
+
{
|
671
|
+
server->secure = TRUE;
|
672
|
+
gnutls_global_init();
|
673
|
+
gnutls_certificate_allocate_credentials(&server->credentials);
|
674
|
+
/* todo gnutls_certificate_free_credentials */
|
675
|
+
int r = gnutls_certificate_set_x509_key_file( server->credentials
|
676
|
+
, cert_file
|
677
|
+
, key_file
|
678
|
+
, GNUTLS_X509_FMT_PEM
|
679
|
+
);
|
680
|
+
if(r < 0) {
|
681
|
+
error("loading certificates");
|
682
|
+
return -1;
|
683
|
+
}
|
684
|
+
return 1;
|
685
|
+
}
|
686
|
+
#endif /* HAVE_GNUTLS */
|
687
|
+
|
688
|
+
/**
|
689
|
+
* Initialize an ebb_connection structure. After calling this function you
|
690
|
+
* must setup callbacks for the different actions the server can take. See
|
691
|
+
* server.h for which callbacks are availible.
|
692
|
+
*
|
693
|
+
* This should be called immediately after allocating space for a new
|
694
|
+
* ebb_connection structure. Most likely, this will only be called within
|
695
|
+
* the ebb_server->new_connection callback which you supply.
|
696
|
+
*
|
697
|
+
* If using SSL do consider setting
|
698
|
+
* gnutls_db_set_retrieve_function (connection->session, _);
|
699
|
+
* gnutls_db_set_remove_function (connection->session, _);
|
700
|
+
* gnutls_db_set_store_function (connection->session, _);
|
701
|
+
* gnutls_db_set_ptr (connection->session, _);
|
702
|
+
* To provide a better means of storing SSL session caches. libebb provides
|
703
|
+
* only a simple default implementation.
|
704
|
+
*
|
705
|
+
* @param connection the connection to initialize
|
706
|
+
* @param timeout the timeout in seconds
|
707
|
+
*/
|
708
|
+
void
|
709
|
+
ebb_connection_init(ebb_connection *connection)
|
710
|
+
{
|
711
|
+
connection->fd = -1;
|
712
|
+
connection->server = NULL;
|
713
|
+
connection->ip = NULL;
|
714
|
+
connection->open = FALSE;
|
715
|
+
|
716
|
+
ebb_request_parser_init( &connection->parser );
|
717
|
+
connection->parser.data = connection;
|
718
|
+
connection->parser.new_request = new_request_wrapper;
|
719
|
+
|
720
|
+
ev_init (&connection->write_watcher, on_writable);
|
721
|
+
connection->write_watcher.data = connection;
|
722
|
+
connection->to_write = NULL;
|
723
|
+
|
724
|
+
ev_init(&connection->read_watcher, on_readable);
|
725
|
+
connection->read_watcher.data = connection;
|
726
|
+
|
727
|
+
#ifdef HAVE_GNUTLS
|
728
|
+
connection->handshake_watcher.data = connection;
|
729
|
+
ev_init(&connection->handshake_watcher, on_handshake);
|
730
|
+
|
731
|
+
ev_init(&connection->goodbye_tls_watcher, on_goodbye_tls);
|
732
|
+
connection->goodbye_tls_watcher.data = connection;
|
733
|
+
|
734
|
+
connection->session = NULL;
|
735
|
+
#endif /* HAVE_GNUTLS */
|
736
|
+
|
737
|
+
ev_timer_init(&connection->goodbye_watcher, on_goodbye, 0., 0.);
|
738
|
+
connection->goodbye_watcher.data = connection;
|
739
|
+
|
740
|
+
ev_timer_init(&connection->timeout_watcher, on_timeout, EBB_DEFAULT_TIMEOUT, 0.);
|
741
|
+
connection->timeout_watcher.data = connection;
|
742
|
+
|
743
|
+
connection->new_buf = NULL;
|
744
|
+
connection->new_request = NULL;
|
745
|
+
connection->on_timeout = NULL;
|
746
|
+
connection->on_close = NULL;
|
747
|
+
connection->data = NULL;
|
748
|
+
}
|
749
|
+
|
750
|
+
void
|
751
|
+
ebb_connection_schedule_close (ebb_connection *connection)
|
752
|
+
{
|
753
|
+
#ifdef HAVE_GNUTLS
|
754
|
+
if(connection->server->secure) {
|
755
|
+
ev_io_set(&connection->goodbye_tls_watcher, connection->fd, EV_ERROR | EV_READ | EV_WRITE);
|
756
|
+
ev_io_start(connection->server->loop, &connection->goodbye_tls_watcher);
|
757
|
+
return;
|
758
|
+
}
|
759
|
+
#endif
|
760
|
+
ev_timer_start(connection->server->loop, &connection->goodbye_watcher);
|
761
|
+
}
|
762
|
+
|
763
|
+
/*
|
764
|
+
* Resets the timeout to stay alive for another connection->timeout seconds
|
765
|
+
*/
|
766
|
+
void
|
767
|
+
ebb_connection_reset_timeout(ebb_connection *connection)
|
768
|
+
{
|
769
|
+
ev_timer_again(connection->server->loop, &connection->timeout_watcher);
|
770
|
+
}
|
771
|
+
|
772
|
+
/**
|
773
|
+
* Writes a string to the socket. This is actually sets a watcher
|
774
|
+
* which may take multiple iterations to write the entire string.
|
775
|
+
*
|
776
|
+
* The buf->on_release() callback will be made when the operation is complete.
|
777
|
+
*
|
778
|
+
* This can only be called once at a time. If you call it again
|
779
|
+
* while the connection is writing another buffer the ebb_connection_write
|
780
|
+
* will return FALSE and ignore the request.
|
781
|
+
*/
|
782
|
+
int
|
783
|
+
ebb_connection_write (ebb_connection *connection, const char *buf, size_t len, ebb_after_write_cb cb)
|
784
|
+
{
|
785
|
+
if(ev_is_active(&connection->write_watcher))
|
786
|
+
return FALSE;
|
787
|
+
assert(!CONNECTION_HAS_SOMETHING_TO_WRITE);
|
788
|
+
connection->to_write = buf;
|
789
|
+
connection->to_write_len = len;
|
790
|
+
connection->written = 0;
|
791
|
+
connection->after_write_cb = cb;
|
792
|
+
ev_io_start(connection->server->loop, &connection->write_watcher);
|
793
|
+
return TRUE;
|
794
|
+
}
|