Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

add header writing to ebb-core

  • Loading branch information...
commit eb823db6bb07b9c2f411f0aa62b923d3f8198838 1 parent 9bccca5
Ryan Dahl authored
View
2  Rakefile
@@ -43,7 +43,7 @@ task(:wc) { sh "wc -l ruby_lib/*.rb src/ebb*.{c,h}" }
task(:test => :compile)
Rake::TestTask.new do |t|
- t.test_files = 'test/test.rb'
+ t.test_files = 'test/basic_test.rb'
t.verbose = true
end
View
15 ruby_lib/ebb.rb
@@ -39,6 +39,14 @@ def finished
def write(data)
FFI::client_write(self, data)
end
+
+ def write_status(status)
+ FFI::client_write_status(self, status.to_i, HTTP_STATUS_CODES[status])
+ end
+
+ def write_header(field, value)
+ FFI::client_write_header(self, field.to_s, value.to_s)
+ end
end
class RequestBody
@@ -113,14 +121,15 @@ def process_client(client)
body = "Internal Server Error\n"
end
- client.write "HTTP/1.1 %d %s\r\n" % [status, HTTP_STATUS_CODES[status]]
+ client.write_status(status)
if body.respond_to? :length and status != 304
- client.write "Connection: close\r\n"
+ headers['Connection'] = 'close'
headers['Content-Length'] = body.length
end
- headers.each { |k, v| client.write "#{k}: #{v}\r\n" }
+ headers.each { |k, v| client.write_header(k,v) }
+
client.write "\r\n"
# Not many apps use streaming yet so i'll hold off on that feature
View
25 src/ebb.c
@@ -358,6 +358,10 @@ void on_request(struct ev_loop *loop, ev_io *watcher, int revents)
client->response_buffer->len = 0; /* see note in ebb_client_close */
client->content_length = 0;
+ client->status_sent = FALSE;
+ client->headers_sent = FALSE;
+ client->body_sent = FALSE;
+
/* SETUP READ AND TIMEOUT WATCHERS */
client->read_watcher.data = client;
ev_init(&client->read_watcher, on_readable);
@@ -526,6 +530,27 @@ void on_client_writable(struct ev_loop *loop, ev_io *watcher, int revents)
ebb_client_close(client);
}
+void ebb_client_write_status(ebb_client *client, int status, const char *human_status)
+{
+ assert(client->status_sent == FALSE);
+ g_string_append_printf( client->response_buffer
+ , "HTTP/1.1 %d %s\r\n"
+ , status
+ , human_status
+ );
+ client->status_sent = TRUE;
+}
+
+void ebb_client_write_header(ebb_client *client, const char *field, const char *value)
+{
+ assert(client->status_sent == TRUE);
+ assert(client->headers_sent == FALSE);
+ g_string_append_printf( client->response_buffer
+ , "%s: %s\r\n"
+ , field
+ , value
+ );
+}
void ebb_client_write(ebb_client *client, const char *data, int length)
{
View
6 src/ebb.h
@@ -30,6 +30,8 @@ typedef struct ebb_client ebb_client;
/*** Ebb Client ***/
void ebb_client_close(ebb_client*);
int ebb_client_read(ebb_client *client, char *buffer, int length);
+void ebb_client_write_status(ebb_client*, int status, const char *human_status);
+void ebb_client_write_header(ebb_client*, const char *field, const char *value);
void ebb_client_write(ebb_client*, const char *data, int length);
void ebb_client_finished( ebb_client *client);
@@ -73,6 +75,10 @@ struct ebb_client {
ev_timer timeout_watcher;
+ int status_sent;
+ int headers_sent;
+ int body_sent;
+
/* the ENV structure */
int env_size;
struct ebb_env_item env[EBB_MAX_ENV];
View
15 src/ebb_python.c
@@ -11,6 +11,19 @@ typedef struct {
ebb_server *server;
} Server;
+void Server_alloc()
+{
+
+}
+
+static void
+Server_dealloc(Server* self)
+{
+ ebb_server_free(self->server);
+ self->ob_type->tp_free((PyObject*)self);
+}
+
+
static PyMethodDef Server_methods[] = {
{"name", (PyCFunction)Noddy_name, METH_NOARGS,
"Return the name, combining the first and last name"
@@ -56,7 +69,7 @@ static PyTypeObject ServerType = {
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)Server_init, /* tp_init */
- 0, /* tp_alloc */
+ Server_alloc, /* tp_alloc */
Server_new, /* tp_new */
};
View
45 src/ebb_ruby.c
@@ -10,8 +10,6 @@
static VALUE cServer;
static VALUE cClient;
-static VALUE eParserError;
-
static VALUE global_http_prefix;
static VALUE global_request_method;
static VALUE global_request_uri;
@@ -26,15 +24,17 @@ static VALUE global_path_info;
static VALUE global_content_length;
static VALUE global_http_host;
+/* Variables with a leading underscore are C-level variables */
+
#define ASCII_UPPER(ch) ('a' <= ch && ch <= 'z' ? ch - 'a' + 'A' : ch)
VALUE client_new(ebb_client *_client)
{
VALUE client = Data_Wrap_Struct(cClient, 0, 0, _client);
- // rb_iv_set(client, "@content_length", INT2FIX(_client->content_length));
return client;
}
+
void request_cb(ebb_client *_client, void *data)
{
VALUE server = (VALUE)data;
@@ -45,6 +45,7 @@ void request_cb(ebb_client *_client, void *data)
rb_ary_push(waiting_clients, client);
}
+
VALUE server_alloc(VALUE self)
{
struct ev_loop *loop = ev_default_loop (0);
@@ -56,16 +57,6 @@ VALUE server_alloc(VALUE self)
}
-// VALUE server_initialize(VALUE x, VALUE server)
-// {
-// struct ev_loop *loop = ev_default_loop (0);
-// ebb_server *_server;
-//
-// Data_Get_Struct(server, ebb_server, _server);
-// return Qnil;
-// }
-
-
VALUE server_listen_on_port(VALUE x, VALUE server, VALUE port)
{
ebb_server *_server;
@@ -111,6 +102,7 @@ VALUE server_process_connections(VALUE x, VALUE server)
return Qfalse;
}
+
VALUE server_unlisten(VALUE x, VALUE server)
{
ebb_server *_server;
@@ -119,7 +111,7 @@ VALUE server_unlisten(VALUE x, VALUE server)
return Qnil;
}
-/* Variables with an underscore are C-level variables */
+
VALUE env_field(struct ebb_env_item *item)
{
VALUE f;
@@ -150,6 +142,7 @@ VALUE env_field(struct ebb_env_item *item)
return Qnil;
}
+
VALUE env_value(struct ebb_env_item *item)
{
if(item->value_length > 0)
@@ -158,6 +151,7 @@ VALUE env_value(struct ebb_env_item *item)
return Qnil;
}
+
VALUE client_env(VALUE x, VALUE client)
{
ebb_client *_client;
@@ -198,6 +192,21 @@ VALUE client_read_input(VALUE x, VALUE client, VALUE size)
return string;
}
+VALUE client_write_status(VALUE x, VALUE client, VALUE status, VALUE human_status)
+{
+ ebb_client *_client;
+ Data_Get_Struct(client, ebb_client, _client);
+ ebb_client_write_status(_client, FIX2INT(status), StringValuePtr(human_status));
+ return Qnil;
+}
+
+VALUE client_write_header(VALUE x, VALUE client, VALUE field, VALUE value)
+{
+ ebb_client *_client;
+ Data_Get_Struct(client, ebb_client, _client);
+ ebb_client_write_header(_client, StringValuePtr(field), StringValuePtr(value));
+ return Qnil;
+}
VALUE client_write(VALUE x, VALUE client, VALUE string)
{
@@ -222,10 +231,6 @@ void Init_ebb_ext()
VALUE mEbb = rb_define_module("Ebb");
VALUE mFFI = rb_define_module_under(mEbb, "FFI");
-
- eParserError = rb_define_class_under(mEbb, "ParserError", rb_eIOError);
-
-
/** Defines global strings in the init method. */
#define DEF_GLOBAL(N, val) global_##N = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&global_##N)
DEF_GLOBAL(http_prefix, "HTTP_");
@@ -244,7 +249,6 @@ void Init_ebb_ext()
cServer = rb_define_class_under(mEbb, "Server", rb_cObject);
rb_define_alloc_func(cServer, server_alloc);
- // rb_define_singleton_method(mFFI, "server_initialize", server_initialize, 1);
rb_define_singleton_method(mFFI, "server_process_connections", server_process_connections, 1);
rb_define_singleton_method(mFFI, "server_listen_on_port", server_listen_on_port, 2);
rb_define_singleton_method(mFFI, "server_listen_on_socket", server_listen_on_socket, 2);
@@ -252,8 +256,9 @@ void Init_ebb_ext()
cClient = rb_define_class_under(mEbb, "Client", rb_cObject);
rb_define_singleton_method(mFFI, "client_read_input", client_read_input, 2);
+ rb_define_singleton_method(mFFI, "client_write_status", client_write_status, 3);
+ rb_define_singleton_method(mFFI, "client_write_header", client_write_header, 3);
rb_define_singleton_method(mFFI, "client_write", client_write, 2);
rb_define_singleton_method(mFFI, "client_finished", client_finished, 1);
rb_define_singleton_method(mFFI, "client_env", client_env, 1);
-
}
View
26 test/basic_test.rb
@@ -1,7 +1,6 @@
require File.dirname(__FILE__) + '/../ruby_lib/ebb'
require 'test/unit'
require 'net/http'
-require 'base64'
require 'socket'
require 'rubygems'
require 'json'
@@ -40,12 +39,6 @@ def call(env)
raise "bytes called with n <= 0" if n <= 0
body = @@responses[n] || "C"*n
status = 200
-
- elsif commands.include?('env')
- env.delete('rack.input') # delete this because it's hard to marshal
- env.delete('rack.errors')
- body = Base64.encode64(Marshal.dump(env))
- status = 200
elsif commands.include?('test_post_length')
input_body = ""
@@ -105,27 +98,8 @@ def test_large_post
assert_equal 200, response.code.to_i, response.body
end
end
-
- def test_env
- response = get('/env')
- env = Marshal.load(Base64.decode64(response.body))
- assert_equal '/env', env['PATH_INFO']
- assert_equal '/env', env['REQUEST_PATH']
- assert_equal 'HTTP/1.1', env['SERVER_PROTOCOL']
- assert_equal 'CGI/1.2', env['GATEWAY_INTERFACE']
- assert_equal '0.0.0.0', env['SERVER_NAME']
- assert_equal PORT.to_s, env['SERVER_PORT']
- assert_equal 'GET', env['REQUEST_METHOD']
- end
-
-
-
end
-[
-
-]
-
class EnvTest < Test::Unit::TestCase
def call(env)
env.delete('rack.input')
View
7 test/env_test.rb
@@ -5,6 +5,10 @@
PORT = 4037
+# This test depends on echo_server running at port 4037. I do this so that
+# I can run a Python server at that port with a similar application and reuse
+# these tests.
+
def send_request(request_string)
socket = TCPSocket.new("0.0.0.0", PORT)
socket.write(request_string)
@@ -33,7 +37,7 @@ def drops_request?(request_string)
end
class HttpParserTest < Test::Unit::TestCase
-
+
def test_parse_simple
env = send_request("GET / HTTP/1.1\r\n\r\n")
@@ -93,6 +97,7 @@ def test_horrible_queries
10.times do |c|
req = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
assert drops_request?(req), "large mangled field values are caught"
+ ### XXX this is broken! fix me. this test should drop the request.
end
# then large headers are rejected too
Please sign in to comment.
Something went wrong with that request. Please try again.