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