Skip to content
This repository has been archived by the owner on Nov 16, 2019. It is now read-only.

Commit

Permalink
major refactor looking towards implementing curl_multi functionality.
Browse files Browse the repository at this point in the history
  • Loading branch information
John Ledbetter committed Jan 4, 2013
1 parent 7aea03d commit c200645
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 108 deletions.
64 changes: 62 additions & 2 deletions ext/curly/native.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
#define INITIAL_BODY_CAPACITY 4096
#define INITIAL_HEAD_CAPACITY 512

int native_curly_alloc(native_curly* n, CURL* handle)
static size_t header_callback(void* buffer, size_t size, size_t count, void* n_);
static size_t data_callback (void* buffer, size_t size, size_t count, void* n_);

int native_curly_alloc(native_curly* n)
{
n->curl_rc = 0;
n->handle = handle;
n->handle = curl_easy_init();

if (exstr_alloc(&n->body, INITIAL_BODY_CAPACITY) < 0) {
return -1;
Expand All @@ -18,11 +21,68 @@ int native_curly_alloc(native_curly* n, CURL* handle)
return -1;
}

n->req_headers = NULL;

return 0;
}

void native_curly_free(native_curly* n)
{
curl_easy_cleanup(n->handle);
curl_slist_free_all(n->req_headers);

exstr_free(&n->body);
exstr_free(&n->head);
}

void native_curly_add_header(native_curly* n, const char* hdr)
{
n->req_headers = curl_slist_append(n->req_headers, hdr);
}

void native_curly_prepare(native_curly* n, const char* url, long timeout,
const char* body)
{
/* invoke header_callback when a header is received and pass `resp` */
curl_easy_setopt(n->handle, CURLOPT_HEADERFUNCTION, header_callback);
curl_easy_setopt(n->handle, CURLOPT_WRITEHEADER, n);

/* invoke data_callback when a chunk of data is received and pass `resp` */
curl_easy_setopt(n->handle, CURLOPT_WRITEFUNCTION, data_callback);
curl_easy_setopt(n->handle, CURLOPT_WRITEDATA, n);

curl_easy_setopt(n->handle, CURLOPT_URL, url);
curl_easy_setopt(n->handle, CURLOPT_HTTPHEADER, n->req_headers);

if (timeout > 0) {
curl_easy_setopt(n->handle, CURLOPT_TIMEOUT_MS, timeout);
}

if (body) {
curl_easy_setopt(n->handle, CURLOPT_POSTFIELDS, body);
}

}

void native_curly_run_simple(native_curly* n)
{
n->curl_rc = curl_easy_perform(n->handle);
}

/* invoked by curl_easy_perform when a header is available */
static size_t header_callback(void* buffer, size_t size, size_t count, void* n_)
{
native_curly* n = (native_curly*)n_;
exstr_append(&n->head, buffer, size * count);

return size * count;
}

/* invoked by curl_easy_perform when data is available */
static size_t data_callback(void* buffer, size_t size, size_t count, void* n_)
{
native_curly* n = (native_curly*)n_;
exstr_append(&n->body, buffer, size * count);

return size * count;
}
8 changes: 7 additions & 1 deletion ext/curly/native.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ typedef struct native_curly_ {
int curl_rc;
exstr body;
exstr head;

struct curl_slist* req_headers;
} native_curly;

int native_curly_alloc(native_curly* n, CURL* handle);
int native_curly_alloc(native_curly* n);
void native_curly_free(native_curly* n);
void native_curly_run_simple(native_curly* n);
void native_curly_add_header(native_curly* n, const char* hdr);
void native_curly_prepare(native_curly* n, const char* url, long timeout,
const char* body);


#endif
142 changes: 37 additions & 105 deletions ext/curly/request.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ static VALUE request_run(VALUE self);
/* internal helpers */
static VALUE request_alloc(VALUE self);
static VALUE request_perform(VALUE self, CURL* c, VALUE url);
static struct curl_slist* build_headers(CURL* c, VALUE headers);
static int request_add_header(VALUE key, VALUE val, VALUE in);
static VALUE build_query_string(VALUE params);

/* curl callbacks */
Expand All @@ -35,145 +33,79 @@ void Init_curly_request(VALUE curly_mod)
syms.delete = ID2SYM(rb_intern("delete"));
}

static VALUE request_run(VALUE self)
static int request_add_header(VALUE key, VALUE val, VALUE ptr)
{
VALUE url = rb_iv_get(self, "@url");
VALUE method = rb_iv_get(self, "@method");
CURL* c = curl_easy_init();

VALUE body, resp;

method = (method == Qnil ? syms.get : rb_funcall(method, rb_intern("to_sym"), 0));

if (method == syms.get) {
curl_easy_setopt(c, CURLOPT_HTTPGET, 1L);
} else if (method == syms.post) {
curl_easy_setopt(c, CURLOPT_HTTPPOST, NULL);
} else if (method == syms.put) {
curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, "PUT");
} else if (method == syms.delete) {
curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, "DELETE");
} else {
/* TODO: rb_raise_whatever */
}

if ((body = rb_iv_get(self, "@body")) != Qnil) {
/* TODO: handle case where `body` is a hash. */
curl_easy_setopt(c, CURLOPT_POSTFIELDS, RSTRING_PTR(StringValue(body)));
}

resp = request_perform(self, c, url);
native_curly* n = (native_curly*)ptr;
VALUE text = rb_str_plus(StringValue(key), rb_str_new2(": "));
rb_str_append(text, StringValue(val));

curl_easy_cleanup(c);
native_curly_add_header(n, RSTRING_PTR(text));

return resp;
return ST_CONTINUE;
}

static VALUE curl_easy_perform_wrapper(void* args)
static VALUE unlock_vm_wrapper(void* arg)
{
native_curly* n = (native_curly*)args;

n->curl_rc = curl_easy_perform(n->handle);
native_curly* n = (native_curly*)arg;
native_curly_run_simple(n);

return Qnil;
}

static VALUE request_perform(VALUE self, CURL* c, VALUE url)
static VALUE request_run(VALUE self)
{
VALUE url = rb_funcall(self, rb_intern("effective_url"), 0);
VALUE method = rb_iv_get(self, "@method");
VALUE body = rb_iv_get(self, "@body");
VALUE timeout = rb_iv_get(self, "@timeout");
VALUE headers = rb_iv_get(self, "@headers");
VALUE resp = response_new();
long code;
VALUE timeout, params, headers;
VALUE resp = response_new();
native_curly n;
struct curl_slist* header_list = NULL;

/* invoke header_callback when a header is received and pass `resp` */
curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, header_callback);
curl_easy_setopt(c, CURLOPT_WRITEHEADER, &n);
native_curly_alloc(&n);

/* invoke data_callback when a chunk of data is received and pass `resp` */
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, data_callback);
curl_easy_setopt(c, CURLOPT_WRITEDATA, &n);

if ((headers = rb_iv_get(self, "@headers")) != Qnil) {
header_list = build_headers(c, headers);
}
method = (method == Qnil ? syms.get : rb_funcall(method, rb_intern("to_sym"), 0));

if ((params = rb_iv_get(self, "@params")) != Qnil) {
url = rb_str_plus(url, rb_str_new2("?"));
url = rb_str_append(url, build_query_string(params));
if (method == syms.get) {
curl_easy_setopt(n.handle, CURLOPT_HTTPGET, 1L);
} else if (method == syms.post) {
curl_easy_setopt(n.handle, CURLOPT_HTTPPOST, NULL);
} else if (method == syms.put) {
curl_easy_setopt(n.handle, CURLOPT_CUSTOMREQUEST, "PUT");
} else if (method == syms.delete) {
curl_easy_setopt(n.handle, CURLOPT_CUSTOMREQUEST, "DELETE");
} else {
/* TODO: rb_raise_whatever */
}

if ((timeout = rb_iv_get(self, "@timeout")) != Qnil) {
curl_easy_setopt(c, CURLOPT_TIMEOUT_MS, NUM2LONG(timeout));
if (headers != Qnil) {
rb_hash_foreach(headers, request_add_header, (VALUE)&n);
}

curl_easy_setopt(c, CURLOPT_URL, RSTRING_PTR(StringValue(url)));
native_curly_prepare(&n, RSTRING_PTR(url),
timeout != Qnil ? NUM2LONG(timeout) : -1,
body != Qnil ? RSTRING_PTR(StringValue(body)) : NULL
);

native_curly_alloc(&n, c);

rb_thread_blocking_region(curl_easy_perform_wrapper, &n, NULL, NULL);
rb_thread_blocking_region(unlock_vm_wrapper, &n, NULL, NULL);

rb_iv_set(resp, "@body", rb_str_new(n.body.value, n.body.length));
rb_iv_set(resp, "@head", rb_str_new(n.head.value, n.head.length));
rb_iv_set(resp, "@curl_code", INT2NUM(n.curl_rc));

if (n.curl_rc == CURLE_OK) {
curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &code);
rb_iv_set(resp, "@status", INT2NUM(code));
curl_easy_getinfo(n.handle, CURLINFO_RESPONSE_CODE, &code);
rb_iv_set(resp, "@status", LONG2NUM(code));
} else {
rb_iv_set(resp, "@curl_error", rb_str_new2(curl_easy_strerror(n.curl_rc)));
}

curl_slist_free_all(header_list);
native_curly_free(&n);

return resp;
}

static int request_add_header(VALUE key, VALUE val, VALUE in)
{
struct curl_slist** list = (struct curl_slist**)in;

/* build the 'Header: Value' string */
VALUE text = rb_str_plus(StringValue(key), rb_str_new2(": "));
rb_str_append(text, StringValue(val));

/* update the head of the list so that the next item is appended correctly */
*list = curl_slist_append(*list, RSTRING_PTR(text));

return ST_CONTINUE;
}

static struct curl_slist* build_headers(CURL* c, VALUE headers)
{
struct curl_slist* list = NULL;

if (TYPE(headers) == T_HASH) {
rb_hash_foreach(headers, request_add_header, (VALUE)&list);
curl_easy_setopt(c, CURLOPT_HTTPHEADER, list);
}

return list;
}

/* invoked by curl_easy_perform when a header is available */
static size_t header_callback(void* buffer, size_t size, size_t count, void* arg)
{
native_curly* n = (native_curly*)arg;
exstr_append(&n->head, buffer, size * count);

return size * count;
}

/* invoked by curl_easy_perform when data is available */
static size_t data_callback(void* buffer, size_t size, size_t count, void* arg)
{
native_curly* n = (native_curly*)arg;
exstr_append(&n->body, buffer, size * count);

return size * count;
}

/* given a set of key/value pairs, build a url encoded string of the form
* key=value&key2=value2 using ActiveSupport's to_query if available or
* our own compatible implementation */
Expand Down
11 changes: 11 additions & 0 deletions lib/curly/request.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'uri'

class Curly::Request
class << self

Expand Down Expand Up @@ -34,4 +36,13 @@ def initialize(url, opts = {})
@params = opts[:params]
@timeout = opts[:timeout]
end

def effective_url
return @url unless @params

uri = URI.parse(@url)
sep = uri.query ? "&" : "?"

"#{@url}#{sep}#{Curly::Parameterize.query_string(@params)}"
end
end
9 changes: 9 additions & 0 deletions spec/easy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@
JSON.parse(resp.body).should eq(params)
end

it "handles combination of params in URL and passed in" do

resp = Curly::Request.get("#{TEST_URL}/params-test?x=a&test=hello+world",
:params => { 'y' => 'b'}
)

JSON.parse(resp.body).should eq(params)
end

it "handles setting headers" do
resp = Curly::Request.get("#{TEST_URL}/headers-test",
:headers => {
Expand Down

0 comments on commit c200645

Please sign in to comment.