ebb 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|