ebb 0.0.2 → 0.0.3
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 +3 -5
- data/VERSION +1 -1
- data/bin/ebb_rails +9 -18
- data/ruby_lib/daemonizable.rb +34 -70
- data/ruby_lib/ebb.rb +16 -6
- data/src/ebb.c +82 -70
- data/src/ebb.h +29 -18
- data/src/ebb_ruby.c +69 -61
- data/src/extconf.rb +2 -1
- data/src/parser.c +871 -538
- data/src/parser.h +2 -2
- data/{benchmark/test.rb → test/basic_test.rb} +72 -19
- data/test/echo_server.rb +16 -0
- data/test/env_test.rb +114 -0
- metadata +12 -11
- data/src/parser.rl +0 -200
data/README
CHANGED
@@ -74,10 +74,8 @@ allows Ebb to continue to process other clients while the upload is in
|
|
74
74
|
progress. The cliff at 40k here is because Ebb's internal request
|
75
75
|
buffer is set at 40 kilobytes before it writes to file.
|
76
76
|
|
77
|
-
|
78
77
|
## Contributions
|
79
78
|
|
80
|
-
|
81
79
|
Contributions (patches, criticism, advice) are very welcome! The source code
|
82
80
|
is hosted at [repo.or.cz](http://repo.or.cz/w/ebb.git). It can be retrieved
|
83
81
|
by executing
|
@@ -86,9 +84,9 @@ by executing
|
|
86
84
|
|
87
85
|
I intend to keep the C code base very small, so do email me before writing any
|
88
86
|
large additions. Here are some features that I would like to add:
|
89
|
-
|
90
|
-
*
|
91
|
-
*
|
87
|
+
* Streaming responses!
|
88
|
+
* HTTP 1.1 Expect/Continue (RFC 2616, sections 8.2.3 and 10.1.1)
|
89
|
+
* A parser for multipart/form-data
|
92
90
|
* Optimize and clean up upload handling
|
93
91
|
* Option to listen on unix sockets instead of TCP
|
94
92
|
* Python binding
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
data/bin/ebb_rails
CHANGED
@@ -15,9 +15,7 @@ options = {
|
|
15
15
|
:env => 'development',
|
16
16
|
:hort => '0.0.0.0',
|
17
17
|
:port => 3000,
|
18
|
-
:timeout => 60
|
19
|
-
:log_file => 'log/ebb.log',
|
20
|
-
:pid_file => 'tmp/pids/ebb.pid'
|
18
|
+
:timeout => 60
|
21
19
|
}
|
22
20
|
|
23
21
|
|
@@ -27,20 +25,16 @@ opts = OptionParser.new do |opts|
|
|
27
25
|
opts.separator ""
|
28
26
|
opts.separator "Server options:"
|
29
27
|
|
30
|
-
opts.on("-s", "--socket SOCKET", "listen on socket") { |socket| options[:socket] = socket }
|
28
|
+
# opts.on("-s", "--socket SOCKET", "listen on socket") { |socket| options[:socket] = socket }
|
31
29
|
opts.on("-p", "--port PORT", "use PORT (default: 3000)") { |port| options[:port] = port }
|
32
30
|
opts.on("-e", "--env ENV", "Rails environment (default: development)") { |env| options[:env] = env }
|
33
31
|
opts.on("-c", "--chdir PATH", "Rails root dir (default: current dir)") { |dir| options[:root] = dir }
|
34
32
|
opts.on("-d", "--daemonize", "Daemonize") { options[:daemonize] = true }
|
35
|
-
opts.on("-l", "--log-file FILE", "File to redirect output"
|
36
|
-
|
37
|
-
opts.on("-P", "--pid-file FILE", "File to store PID",
|
38
|
-
"(default: #{options[:pid_file]})") { |file| options[:pid_file] = file }
|
33
|
+
opts.on("-l", "--log-file FILE", "File to redirect output") { |file| options[:log_file] = file }
|
34
|
+
opts.on("-P", "--pid-file FILE", "File to store PID") { |file| options[:pid_file] = file }
|
39
35
|
opts.on("-t", "--timeout SEC", "Request or command timeout in sec",
|
40
|
-
"(default: #{options[:timeout]})") { |sec| options[:timeout] = sec
|
41
|
-
|
42
|
-
opts.on("-g", "--group NAME", "Group to run daemon as (use with -u)") { |group| options[:group] = group }
|
43
|
-
|
36
|
+
"(default: #{options[:timeout]})") { |sec| options[:timeout] = sec }
|
37
|
+
|
44
38
|
opts.separator ""
|
45
39
|
opts.separator "Common options:"
|
46
40
|
|
@@ -65,13 +59,10 @@ when 'start'
|
|
65
59
|
|
66
60
|
server.pid_file = options[:pid_file]
|
67
61
|
server.log_file = options[:log_file]
|
68
|
-
|
62
|
+
server.timeout = options[:timeout]
|
63
|
+
|
64
|
+
server.daemonize if options[:daemonize]
|
69
65
|
|
70
|
-
if options[:daemonize]
|
71
|
-
server.change_privilege options[:user], options[:group] if options[:user] && options[:group]
|
72
|
-
server.daemonize
|
73
|
-
end
|
74
|
-
|
75
66
|
server.start
|
76
67
|
|
77
68
|
when 'stop'
|
data/ruby_lib/daemonizable.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
#
|
2
|
-
require 'etc'
|
1
|
+
# Simplified version of Thin::Daemonizable by Marc-André Cournoyer
|
3
2
|
|
4
3
|
module Kernel
|
5
4
|
unless respond_to? :daemonize # Already part of Ruby 1.9, yeah!
|
@@ -29,27 +28,13 @@ module Process
|
|
29
28
|
module_function :running?
|
30
29
|
end
|
31
30
|
|
32
|
-
#
|
31
|
+
# Moadule included in classes that can be turned into a daemon.
|
33
32
|
# Handle stuff like:
|
34
33
|
# * storing the PID in a file
|
35
34
|
# * redirecting output to the log file
|
36
|
-
# * changing processs privileges
|
37
35
|
# * killing the process gracefully
|
38
36
|
module Daemonizable
|
39
|
-
attr_accessor :pid_file, :log_file
|
40
|
-
|
41
|
-
def self.included(base)
|
42
|
-
base.extend ClassMethods
|
43
|
-
end
|
44
|
-
|
45
|
-
def daemonizable_init(options)
|
46
|
-
pid_file = options[:pid_file]
|
47
|
-
log_file = options[:log_file]
|
48
|
-
if options[:daemonize]
|
49
|
-
change_privilege options[:user], options[:group] if options[:user] && options[:group]
|
50
|
-
daemonize
|
51
|
-
end
|
52
|
-
end
|
37
|
+
attr_accessor :pid_file, :log_file, :timeout
|
53
38
|
|
54
39
|
def pid
|
55
40
|
File.exist?(pid_file) ? open(pid_file).read : nil
|
@@ -60,11 +45,11 @@ module Daemonizable
|
|
60
45
|
raise ArgumentError, 'You must specify a pid_file to deamonize' unless @pid_file
|
61
46
|
|
62
47
|
pwd = Dir.pwd # Current directory is changed during daemonization, so store it
|
63
|
-
|
48
|
+
Kernel.daemonize
|
64
49
|
Dir.chdir pwd
|
65
50
|
|
66
51
|
trap('HUP', 'IGNORE') # Don't die upon logout
|
67
|
-
|
52
|
+
|
68
53
|
# Redirect output to the logfile
|
69
54
|
[STDOUT, STDERR].each { |f| f.reopen @log_file, 'a' } if @log_file
|
70
55
|
|
@@ -75,60 +60,39 @@ module Daemonizable
|
|
75
60
|
end
|
76
61
|
end
|
77
62
|
|
78
|
-
#
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
if uid != target_uid || gid != target_gid
|
88
|
-
# Change process ownership
|
89
|
-
Process.initgroups(user, target_gid)
|
90
|
-
Process::GID.change_privilege(target_gid)
|
91
|
-
Process::UID.change_privilege(target_uid)
|
92
|
-
end
|
93
|
-
rescue Errno::EPERM => e
|
94
|
-
log "Couldn't change user and group to #{user}:#{group}: #{e}"
|
95
|
-
end
|
96
|
-
|
97
|
-
module ClassMethods
|
98
|
-
# Kill the process which PID is stored in +pid_file+.
|
99
|
-
def kill(pid_file, timeout=60)
|
100
|
-
if pid = open(pid_file).read
|
101
|
-
pid = pid.to_i
|
102
|
-
print "Sending INT signal to process #{pid} ... "
|
103
|
-
begin
|
104
|
-
Process.kill('INT', pid)
|
105
|
-
Timeout.timeout(timeout) do
|
106
|
-
sleep 0.1 while Process.running?(pid)
|
107
|
-
end
|
108
|
-
rescue Timeout::Error
|
109
|
-
print "timeout, Sending KILL signal ... "
|
110
|
-
Process.kill('KILL', pid)
|
63
|
+
# Kill the process which PID is stored in +pid_file+.
|
64
|
+
def self.kill(pid_file, timeout=60)
|
65
|
+
if pid = open(pid_file).read
|
66
|
+
pid = pid.to_i
|
67
|
+
print "Sending INT signal to process #{pid} ... "
|
68
|
+
begin
|
69
|
+
Process.kill('INT', pid)
|
70
|
+
Timeout.timeout(timeout) do
|
71
|
+
sleep 0.1 while Process.running?(pid)
|
111
72
|
end
|
112
|
-
|
113
|
-
|
114
|
-
|
73
|
+
rescue Timeout::Error
|
74
|
+
print "timeout, Sending KILL signal ... "
|
75
|
+
Process.kill('KILL', pid)
|
115
76
|
end
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
File.delete(pid_file) rescue nil
|
77
|
+
puts "stopped!"
|
78
|
+
else
|
79
|
+
puts "Can't stop process, no PID found in #{@pid_file}"
|
120
80
|
end
|
81
|
+
rescue Errno::ESRCH # No such process
|
82
|
+
puts "process not found!"
|
83
|
+
ensure
|
84
|
+
File.delete(pid_file) rescue nil
|
121
85
|
end
|
122
86
|
|
123
87
|
private
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
88
|
+
|
89
|
+
def remove_pid_file
|
90
|
+
File.delete(@pid_file) if @pid_file && File.exists?(@pid_file)
|
91
|
+
end
|
92
|
+
|
93
|
+
def write_pid_file
|
94
|
+
log ">> Writing PID to #{@pid_file}"
|
95
|
+
open(@pid_file,"w+") { |f| f.write(Process.pid) }
|
96
|
+
File.chmod(0644, @pid_file)
|
97
|
+
end
|
134
98
|
end
|
data/ruby_lib/ebb.rb
CHANGED
@@ -39,6 +39,14 @@ module Ebb
|
|
39
39
|
def write(data)
|
40
40
|
FFI::client_write(self, data)
|
41
41
|
end
|
42
|
+
|
43
|
+
def write_status(status)
|
44
|
+
FFI::client_write_status(self, status.to_i, HTTP_STATUS_CODES[status])
|
45
|
+
end
|
46
|
+
|
47
|
+
def write_header(field, value)
|
48
|
+
FFI::client_write_header(self, field.to_s, value.to_s)
|
49
|
+
end
|
42
50
|
end
|
43
51
|
|
44
52
|
class RequestBody
|
@@ -73,9 +81,6 @@ module Ebb
|
|
73
81
|
@port = (options[:port] || 4001).to_i
|
74
82
|
@timeout = options[:timeout]
|
75
83
|
@app = app
|
76
|
-
|
77
|
-
daemonizable_init(options)
|
78
|
-
FFI::server_initialize(self)
|
79
84
|
end
|
80
85
|
|
81
86
|
def start
|
@@ -116,14 +121,15 @@ module Ebb
|
|
116
121
|
body = "Internal Server Error\n"
|
117
122
|
end
|
118
123
|
|
119
|
-
client.
|
124
|
+
client.write_status(status)
|
120
125
|
|
121
126
|
if body.respond_to? :length and status != 304
|
122
|
-
|
127
|
+
headers['Connection'] = 'close'
|
123
128
|
headers['Content-Length'] = body.length
|
124
129
|
end
|
125
130
|
|
126
|
-
headers.each { |k, v| client.
|
131
|
+
headers.each { |k, v| client.write_header(k,v) }
|
132
|
+
|
127
133
|
client.write "\r\n"
|
128
134
|
|
129
135
|
# Not many apps use streaming yet so i'll hold off on that feature
|
@@ -135,6 +141,10 @@ module Ebb
|
|
135
141
|
end
|
136
142
|
client.finished
|
137
143
|
end
|
144
|
+
|
145
|
+
def log(msg)
|
146
|
+
puts msg
|
147
|
+
end
|
138
148
|
end
|
139
149
|
|
140
150
|
HTTP_STATUS_CODES = {
|
data/src/ebb.c
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
/* Ebb Web Server
|
2
|
-
* Copyright (c)
|
3
|
-
*
|
1
|
+
/* The Ebb Web Server
|
2
|
+
* Copyright (c) 2008 Ry Dahl. This software is released under the MIT
|
3
|
+
* License. See README file for details.
|
4
4
|
*/
|
5
5
|
#include <unistd.h>
|
6
6
|
#include <fcntl.h>
|
@@ -32,54 +32,40 @@
|
|
32
32
|
static int server_socket(const int port);
|
33
33
|
static int server_socket_unix(const char *path, int access_mask);
|
34
34
|
|
35
|
-
|
36
|
-
client->env_fields[client->env_size] = field; \
|
37
|
-
client->env_field_lengths[client->env_size] = flen; \
|
38
|
-
client->env_values[client->env_size] = value; \
|
39
|
-
client->env_value_lengths[client->env_size] = vlen; \
|
40
|
-
client->env_size += 1;
|
41
|
-
#define env_add_const(client,field,value,vlen) \
|
42
|
-
client->env_fields[client->env_size] = NULL; \
|
43
|
-
client->env_field_lengths[client->env_size] = field; \
|
44
|
-
client->env_values[client->env_size] = value; \
|
45
|
-
client->env_value_lengths[client->env_size] = vlen; \
|
46
|
-
client->env_size += 1;
|
47
|
-
#define env_error(client) \
|
48
|
-
client->env_fields[client->env_size] = NULL; \
|
49
|
-
client->env_field_lengths[client->env_size] = -1; \
|
50
|
-
client->env_values[client->env_size] = NULL; \
|
51
|
-
client->env_value_lengths[client->env_size] = -1; \
|
52
|
-
client->env_size += 1;
|
53
|
-
|
54
|
-
int env_has_error(ebb_client *client)
|
35
|
+
void env_add(ebb_client *client, const char *field, int flen, const char *value, int vlen)
|
55
36
|
{
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
37
|
+
if(client->env_size >= EBB_MAX_ENV) {
|
38
|
+
client->parser.overflow_error = TRUE;
|
39
|
+
return;
|
40
|
+
}
|
41
|
+
client->env[client->env_size].type = EBB_FIELD_VALUE_PAIR;
|
42
|
+
client->env[client->env_size].field = field;
|
43
|
+
client->env[client->env_size].field_length = flen;
|
44
|
+
client->env[client->env_size].value = value;
|
45
|
+
client->env[client->env_size].value_length = vlen;
|
46
|
+
client->env_size += 1;
|
61
47
|
}
|
62
48
|
|
63
|
-
/** Defines common length and error messages for input length validation. */
|
64
|
-
#define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP Parse Error: HTTP element " # N " is longer than the " # length " allowed length."
|
65
|
-
|
66
|
-
/** Validates the max length of given input and throws an exception if over. */
|
67
|
-
#define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { env_error(client); g_message(MAX_##N##_LENGTH_ERR); }
|
68
49
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
50
|
+
void env_add_const(ebb_client *client, int type, const char *value, int vlen)
|
51
|
+
{
|
52
|
+
if(client->env_size >= EBB_MAX_ENV) {
|
53
|
+
client->parser.overflow_error = TRUE;
|
54
|
+
return;
|
55
|
+
}
|
56
|
+
client->env[client->env_size].type = type;
|
57
|
+
client->env[client->env_size].field = NULL;
|
58
|
+
client->env[client->env_size].field_length = -1;
|
59
|
+
client->env[client->env_size].value = value;
|
60
|
+
client->env[client->env_size].value_length = vlen;
|
61
|
+
client->env_size += 1;
|
62
|
+
}
|
77
63
|
|
78
64
|
void http_field_cb(void *data, const char *field, size_t flen, const char *value, size_t vlen)
|
79
65
|
{
|
80
66
|
ebb_client *client = (ebb_client*)(data);
|
81
|
-
|
82
|
-
|
67
|
+
assert(field != NULL);
|
68
|
+
assert(value != NULL);
|
83
69
|
env_add(client, field, flen, value, vlen);
|
84
70
|
}
|
85
71
|
|
@@ -94,7 +80,6 @@ void request_method_cb(void *data, const char *at, size_t length)
|
|
94
80
|
void request_uri_cb(void *data, const char *at, size_t length)
|
95
81
|
{
|
96
82
|
ebb_client *client = (ebb_client*)(data);
|
97
|
-
VALIDATE_MAX_LENGTH(length, REQUEST_URI);
|
98
83
|
env_add_const(client, EBB_REQUEST_URI, at, length);
|
99
84
|
}
|
100
85
|
|
@@ -102,7 +87,6 @@ void request_uri_cb(void *data, const char *at, size_t length)
|
|
102
87
|
void fragment_cb(void *data, const char *at, size_t length)
|
103
88
|
{
|
104
89
|
ebb_client *client = (ebb_client*)(data);
|
105
|
-
VALIDATE_MAX_LENGTH(length, FRAGMENT);
|
106
90
|
env_add_const(client, EBB_FRAGMENT, at, length);
|
107
91
|
}
|
108
92
|
|
@@ -110,7 +94,6 @@ void fragment_cb(void *data, const char *at, size_t length)
|
|
110
94
|
void request_path_cb(void *data, const char *at, size_t length)
|
111
95
|
{
|
112
96
|
ebb_client *client = (ebb_client*)(data);
|
113
|
-
VALIDATE_MAX_LENGTH(length, REQUEST_PATH);
|
114
97
|
env_add_const(client, EBB_REQUEST_PATH, at, length);
|
115
98
|
}
|
116
99
|
|
@@ -118,7 +101,6 @@ void request_path_cb(void *data, const char *at, size_t length)
|
|
118
101
|
void query_string_cb(void *data, const char *at, size_t length)
|
119
102
|
{
|
120
103
|
ebb_client *client = (ebb_client*)(data);
|
121
|
-
VALIDATE_MAX_LENGTH(length, QUERY_STRING);
|
122
104
|
env_add_const(client, EBB_QUERY_STRING, at, length);
|
123
105
|
}
|
124
106
|
|
@@ -246,6 +228,7 @@ void* read_body_into_file(void *_client)
|
|
246
228
|
}
|
247
229
|
rewind(tmpfile);
|
248
230
|
// g_debug("%d bytes written to file %s", written, client->upload_file_filename);
|
231
|
+
dispatch(client);
|
249
232
|
return NULL;
|
250
233
|
error:
|
251
234
|
ebb_client_close(client);
|
@@ -264,15 +247,17 @@ void on_readable(struct ev_loop *loop, ev_io *watcher, int revents)
|
|
264
247
|
|
265
248
|
ssize_t read = recv( client->fd
|
266
249
|
, client->request_buffer + client->read
|
267
|
-
, EBB_BUFFERSIZE - client->read
|
250
|
+
, EBB_BUFFERSIZE - client->read
|
268
251
|
, 0
|
269
252
|
);
|
270
|
-
if(read
|
253
|
+
if(read < 0) goto error; /* XXX is this the right action to take for read==0 ? */
|
254
|
+
if(read == 0) return;
|
271
255
|
client->read += read;
|
272
256
|
ev_timer_again(loop, &client->timeout_watcher);
|
273
257
|
|
258
|
+
if(client->read == EBB_BUFFERSIZE) goto error;
|
259
|
+
|
274
260
|
if(FALSE == client_finished_parsing) {
|
275
|
-
client->request_buffer[client->read] = '\0'; /* make ragel happy */
|
276
261
|
http_parser_execute( &client->parser
|
277
262
|
, client->request_buffer
|
278
263
|
, client->read
|
@@ -281,21 +266,21 @@ void on_readable(struct ev_loop *loop, ev_io *watcher, int revents)
|
|
281
266
|
if(http_parser_has_error(&client->parser)) goto error;
|
282
267
|
}
|
283
268
|
|
284
|
-
if(
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
269
|
+
if(client_finished_parsing) {
|
270
|
+
if(total_request_size == client->read) {
|
271
|
+
ev_io_stop(loop, watcher);
|
272
|
+
client->nread_from_body = 0;
|
273
|
+
dispatch(client);
|
274
|
+
return;
|
275
|
+
}
|
276
|
+
if(total_request_size > EBB_BUFFERSIZE ) {
|
277
|
+
/* read body into file - in a thread */
|
278
|
+
ev_io_stop(loop, watcher);
|
279
|
+
pthread_t thread;
|
280
|
+
assert(0 <= pthread_create(&thread, NULL, read_body_into_file, client));
|
281
|
+
pthread_detach(thread);
|
282
|
+
return;
|
283
|
+
}
|
299
284
|
}
|
300
285
|
return;
|
301
286
|
error:
|
@@ -338,7 +323,12 @@ void on_request(struct ev_loop *loop, ev_io *watcher, int revents)
|
|
338
323
|
for(i = 0; i < EBB_MAX_CLIENTS; i++)
|
339
324
|
if(server->clients[i].open) count += 1;
|
340
325
|
g_debug("%d open connections", count);
|
326
|
+
|
327
|
+
/* does ragel fuck up if request buffer isn't null? */
|
328
|
+
for(i=0; i< EBB_BUFFERSIZE; i++)
|
329
|
+
client->request_buffer[i] = 'A';
|
341
330
|
#endif
|
331
|
+
|
342
332
|
client->open = TRUE;
|
343
333
|
client->server = server;
|
344
334
|
|
@@ -362,11 +352,16 @@ void on_request(struct ev_loop *loop, ev_io *watcher, int revents)
|
|
362
352
|
client->parser.content_length = content_length_cb;
|
363
353
|
|
364
354
|
/* OTHER */
|
355
|
+
|
365
356
|
client->env_size = 0;
|
366
357
|
client->read = client->nread_from_body = 0;
|
367
358
|
client->response_buffer->len = 0; /* see note in ebb_client_close */
|
368
359
|
client->content_length = 0;
|
369
360
|
|
361
|
+
client->status_sent = FALSE;
|
362
|
+
client->headers_sent = FALSE;
|
363
|
+
client->body_sent = FALSE;
|
364
|
+
|
370
365
|
/* SETUP READ AND TIMEOUT WATCHERS */
|
371
366
|
client->read_watcher.data = client;
|
372
367
|
ev_init(&client->read_watcher, on_readable);
|
@@ -426,11 +421,8 @@ void ebb_server_free(ebb_server *server)
|
|
426
421
|
void ebb_server_unlisten(ebb_server *server)
|
427
422
|
{
|
428
423
|
if(server->open) {
|
429
|
-
//g_message("Stopping Ebb server");
|
430
424
|
int i;
|
431
425
|
ebb_client *client;
|
432
|
-
//for(i=0; i < EBB_MAX_CLIENTS; i++)
|
433
|
-
// ebb_client_close(client);
|
434
426
|
ev_io_stop(server->loop, &server->request_watcher);
|
435
427
|
close(server->fd);
|
436
428
|
if(server->socketpath)
|
@@ -538,6 +530,27 @@ void on_client_writable(struct ev_loop *loop, ev_io *watcher, int revents)
|
|
538
530
|
ebb_client_close(client);
|
539
531
|
}
|
540
532
|
|
533
|
+
void ebb_client_write_status(ebb_client *client, int status, const char *human_status)
|
534
|
+
{
|
535
|
+
assert(client->status_sent == FALSE);
|
536
|
+
g_string_append_printf( client->response_buffer
|
537
|
+
, "HTTP/1.1 %d %s\r\n"
|
538
|
+
, status
|
539
|
+
, human_status
|
540
|
+
);
|
541
|
+
client->status_sent = TRUE;
|
542
|
+
}
|
543
|
+
|
544
|
+
void ebb_client_write_header(ebb_client *client, const char *field, const char *value)
|
545
|
+
{
|
546
|
+
assert(client->status_sent == TRUE);
|
547
|
+
assert(client->headers_sent == FALSE);
|
548
|
+
g_string_append_printf( client->response_buffer
|
549
|
+
, "%s: %s\r\n"
|
550
|
+
, field
|
551
|
+
, value
|
552
|
+
);
|
553
|
+
}
|
541
554
|
|
542
555
|
void ebb_client_write(ebb_client *client, const char *data, int length)
|
543
556
|
{
|
@@ -705,5 +718,4 @@ static int server_socket_unix(const char *path, int access_mask) {
|
|
705
718
|
return -1;
|
706
719
|
}
|
707
720
|
return sfd;
|
708
|
-
}
|
709
|
-
|
721
|
+
}
|