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 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
- * Multipart parser
91
- * Streaming responses
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.2
1
+ 0.0.3
@@ -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
- "(default: #{options[:log_file]})") { |file| options[:log_file] = file }
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; raise NotImplementedError } # TODO: fix me
41
- opts.on("-u", "--user NAME", "User to run daemon as (use with -g)") { |user| options[:user] = user }
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
- # server.timeout = options[:timeout]
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'
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2007 Marc-André Cournoyer
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
- # Module included in classes that can be turned into a daemon.
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
- super # Calls Kernel#daemonize
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
- # Change privileges of the process
79
- # to the specified user and group.
80
- def change_privilege(user, group=user)
81
- log ">> Changing process privilege to #{user}:#{group}"
82
-
83
- uid, gid = Process.euid, Process.egid
84
- target_uid = Etc.getpwnam(user).uid
85
- target_gid = Etc.getgrnam(group).gid
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
- puts "stopped!"
113
- else
114
- puts "Can't stop process, no PID found in #{@pid_file}"
73
+ rescue Timeout::Error
74
+ print "timeout, Sending KILL signal ... "
75
+ Process.kill('KILL', pid)
115
76
  end
116
- rescue Errno::ESRCH # No such process
117
- puts "process not found!"
118
- ensure
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
- def remove_pid_file
125
- File.delete(@pid_file) if @pid_file && File.exists?(@pid_file)
126
- end
127
-
128
- def write_pid_file
129
- log ">> Writing PID to #{@pid_file}"
130
- FileUtils.mkdir_p File.dirname(@pid_file)
131
- open(@pid_file,"w") { |f| f.write(Process.pid) }
132
- File.chmod(0644, @pid_file)
133
- end
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
@@ -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.write "HTTP/1.1 %d %s\r\n" % [status, HTTP_STATUS_CODES[status]]
124
+ client.write_status(status)
120
125
 
121
126
  if body.respond_to? :length and status != 304
122
- client.write "Connection: close\r\n"
127
+ headers['Connection'] = 'close'
123
128
  headers['Content-Length'] = body.length
124
129
  end
125
130
 
126
- headers.each { |k, v| client.write "#{k}: #{v}\r\n" }
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) 2007 Ry Dahl
3
- * This software is released under the "MIT License". See README file for details.
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
- #define env_add(client, field,flen,value,vlen) \
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
- int i;
57
- for(i = 0; i < client->env_size; i++)
58
- if(client->env_field_lengths[i] < 0)
59
- return TRUE;
60
- return FALSE;
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
- /* Defines the maximum allowed lengths for various input elements.*/
70
- DEF_MAX_LENGTH(FIELD_NAME, 256);
71
- DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
72
- DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
73
- DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
74
- DEF_MAX_LENGTH(REQUEST_PATH, 1024);
75
- DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
76
- DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
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
- VALIDATE_MAX_LENGTH(flen, FIELD_NAME);
82
- VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE);
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 - 1 /* -1 is for making ragel happy below */
250
+ , EBB_BUFFERSIZE - client->read
268
251
  , 0
269
252
  );
270
- if(read <= 0) goto error; /* XXX is this the right action to take for read==0 ? */
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(total_request_size == client->read) {
285
- ev_io_stop(loop, watcher);
286
- client->nread_from_body = 0;
287
- dispatch(client);
288
- return;
289
- }
290
-
291
- if(client_finished_parsing && total_request_size > EBB_BUFFERSIZE ) {
292
- /* read body into file - in a thread */
293
- pthread_t thread;
294
- ev_io_stop(loop, watcher);
295
- assert(0 <= pthread_create(&thread, NULL, read_body_into_file, client));
296
- pthread_join(thread, NULL);
297
- dispatch(client);
298
- return;
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
+ }