Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Multi #3

Merged
merged 20 commits into from

1 participant

@ledbettj
Owner
  • Release Ruby VM Lock while libcurl is doing it's thing
  • Add Curly::Multi interface which takes advantage of the libcurl multi API to run multiple transfers simultaneously.
  • Move code into Ruby that doesn't need to be in C.
John Ledbetter and others added some commits
John Ledbetter release global vm lock while waiting for curl_perform to finish. 0ca66e4
John Ledbetter store headers. 7f5e02b
John Ledbetter move magic constants to #defines. 592650b
John Ledbetter move non-ruby C code out into it's own files. 375c593
@ledbettj add file for multi. 2f94092
@ledbettj start stubbing out multi api. d8e8088
@ledbettj move logic to instance methods (initialize and run). convience method…
…s just create a new instance and invoke run.
13f9595
John Ledbetter Merge pull request #1 from ledbettj/unblock
Not quite ready for master, but needs some of the changes in multi.
6fa3b56
John Ledbetter move straight ruby code back out into ruby. 6bb0e57
John Ledbetter move response out of C into ruby. 28c4b53
John Ledbetter pull options out of opts hash when constructing request. 3518b80
John Ledbetter move more request logic out into ruby. a90ea66
John Ledbetter remove unused SELF parameter. 30d883d
John Ledbetter add accessors, sort things alphabetically in case of OCD. 6f58c8c
John Ledbetter Merge pull request #2 from ledbettj/rb-ize
Ruby-ize code that doesn't need to be in C.
f6ddc62
John Ledbetter actually start working on the multi code again. 7aea03d
John Ledbetter major refactor looking towards implementing curl_multi functionality. c200645
John Ledbetter pull curl handle setopts out of run. 0395b04
John Ledbetter first stab at actual multi implementation (currently still holds vm l…
…ock)
a08121f
John Ledbetter spec cleanups. d4963f5
@ledbettj ledbettj merged commit b79fce6 into master
@ledbettj ledbettj deleted the multi branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 2, 2013
  1. store headers.

    John Ledbetter authored
Commits on Jan 4, 2013
  1. move magic constants to #defines.

    John Ledbetter authored
  2. move non-ruby C code out into it's own files.

    John Ledbetter authored
  3. add file for multi.

    authored John Ledbetter committed
  4. start stubbing out multi api.

    authored John Ledbetter committed
  5. move logic to instance methods (initialize and run). convience method…

    authored John Ledbetter committed
    …s just create a new instance and invoke run.
  6. Merge pull request #1 from ledbettj/unblock

    John Ledbetter authored
    Not quite ready for master, but needs some of the changes in multi.
  7. move straight ruby code back out into ruby.

    John Ledbetter authored
  8. move response out of C into ruby.

    John Ledbetter authored
  9. pull options out of opts hash when constructing request.

    John Ledbetter authored
  10. move more request logic out into ruby.

    John Ledbetter authored
  11. remove unused SELF parameter.

    John Ledbetter authored
  12. add accessors, sort things alphabetically in case of OCD.

    John Ledbetter authored
  13. Merge pull request #2 from ledbettj/rb-ize

    John Ledbetter authored
    Ruby-ize code that doesn't need to be in C.
  14. actually start working on the multi code again.

    John Ledbetter authored
  15. pull curl handle setopts out of run.

    John Ledbetter authored
  16. spec cleanups.

    John Ledbetter authored
This page is out of date. Refresh to see the latest.
View
2  ext/curly/curly.c
@@ -1,6 +1,7 @@
#include <ruby.h>
#include "request.h"
#include "response.h"
+#include "multi.h"
void Init_curly(void)
{
@@ -8,4 +9,5 @@ void Init_curly(void)
Init_curly_response(curly_mod);
Init_curly_request(curly_mod);
+ Init_curly_multi(curly_mod);
}
View
53 ext/curly/exstr.c
@@ -0,0 +1,53 @@
+#include "exstr.h"
+#include <stdlib.h>
+#include <string.h>
+
+static int exstr_grow(exstr* s, size_t at_least);
+
+int exstr_alloc(exstr* s, size_t capacity)
+{
+ s->capacity = capacity;
+ s->length = 0;
+ s->value = malloc(capacity);
+
+ return s->value ? 0 : -1;
+}
+
+void exstr_free(exstr* s)
+{
+ free(s->value);
+}
+
+int exstr_append(exstr* s, char* buffer, size_t length)
+{
+ if (exstr_grow(s, s->length + length) < 0) {
+ return -1;
+ }
+
+ memcpy(&s->value[s->length], buffer, length);
+ s->length += length;
+
+ return 0;
+}
+
+static int exstr_grow(exstr* s, size_t at_least)
+{
+ size_t new_capacity;
+ char* new_value;
+
+ if (s->capacity <= at_least) {
+ new_capacity = 2 * at_least;
+ new_value = realloc(s->value, new_capacity);
+
+ if (!new_value) {
+ /* realloc failed, did not grow */
+ return -1;
+ }
+
+ s->value = new_value;
+ s->capacity = new_capacity;
+
+ }
+
+ return 0;
+}
View
14 ext/curly/exstr.h
@@ -0,0 +1,14 @@
+#ifndef _CURLY_EXSTR_H_
+#define _CURLY_EXSTR_H_
+#include <stdlib.h>
+
+typedef struct exstr_ {
+ size_t capacity;
+ size_t length;
+ char* value;
+} exstr;
+
+int exstr_alloc(exstr* s, size_t capacity);
+void exstr_free(exstr* s);
+int exstr_append(exstr* s, char* buffer, size_t length);
+#endif
View
99 ext/curly/multi.c
@@ -0,0 +1,99 @@
+#include "multi.h"
+#include "native.h"
+#include "request.h"
+#include "response.h"
+#include <curl/curl.h>
+#include <unistd.h>
+
+VALUE multi_run(VALUE self);
+
+void Init_curly_multi(VALUE curly)
+{
+ VALUE multi = rb_define_class_under(curly, "Multi", rb_cObject);
+
+ rb_define_method(multi, "run", multi_run, 0);
+}
+
+static VALUE multi_run_unblocked(void* m)
+{
+ CURLM* c = (CURLM*)m;
+ int pending = 0;
+ struct timeval timeout;
+ int rc;
+ fd_set fr, fw, fx;
+ int maxfd;
+ long curl_timeout;
+
+ curl_multi_perform(c, &pending);
+
+ do {
+ FD_ZERO(&fr);
+ FD_ZERO(&fw);
+ FD_ZERO(&fx);
+
+ maxfd = -1;
+ curl_timeout = -1;
+
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+
+ curl_multi_timeout(c, &curl_timeout);
+
+ if (curl_timeout >= 0) {
+ timeout.tv_sec = curl_timeout / 1000;
+ timeout.tv_sec = timeout.tv_sec > 1 ? 1 : (curl_timeout % 1000) * 1000;
+ }
+
+ curl_multi_fdset(c, &fr, &fw, &fx, &maxfd);
+ rc = select(maxfd, &fr, &fw, &fx, &timeout);
+
+ curl_multi_perform(c, &pending);
+
+ } while (pending);
+
+ return Qnil;
+}
+
+VALUE multi_run(VALUE self)
+{
+ CURLM* c = curl_multi_init();
+ VALUE requests = rb_iv_get(self, "@requests");
+ size_t req_len = RARRAY_LEN(requests);
+ VALUE* req_ptr = RARRAY_PTR(requests);
+ size_t i;
+ native_curly* n = malloc(sizeof(native_curly) * req_len);
+ VALUE resp;
+ long code;
+
+ for(i = 0; i < req_len; ++i) {
+ native_curly_alloc(&n[i]);
+ request_prepare(req_ptr[i], &n[i]);
+ curl_multi_add_handle(c, n[i].handle);
+ }
+
+ rb_thread_blocking_region(multi_run_unblocked, c, NULL, NULL);
+
+ /* cleanup */
+ for(i = 0; i < req_len; ++i) {
+ resp = response_new();
+
+ rb_iv_set(resp, "@body", rb_str_new(n[i].body.value, n[i].body.length));
+ rb_iv_set(resp, "@head", rb_str_new(n[i].head.value, n[i].head.length));
+ rb_iv_set(resp, "@curl_code", INT2NUM(n[i].curl_rc));
+
+ if (n[i].curl_rc == CURLE_OK) {
+ curl_easy_getinfo(n[i].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[i].curl_rc)));
+ }
+ rb_iv_set(req_ptr[i], "@response", resp);
+
+ curl_multi_remove_handle(c, n[i].handle);
+ native_curly_free(&n[i]);
+ }
+
+ curl_multi_cleanup(c);
+
+ return Qnil;
+}
View
6 ext/curly/multi.h
@@ -0,0 +1,6 @@
+#ifndef _CURLY_MULTI_H_
+#define _CURLY_MULTI_H_
+#include <ruby.h>
+
+void Init_curly_multi(VALUE curly);
+#endif
View
88 ext/curly/native.c
@@ -0,0 +1,88 @@
+#include <curl/curl.h>
+#include "native.h"
+
+#define INITIAL_BODY_CAPACITY 4096
+#define INITIAL_HEAD_CAPACITY 512
+
+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 = curl_easy_init();
+
+ if (exstr_alloc(&n->body, INITIAL_BODY_CAPACITY) < 0) {
+ return -1;
+ }
+
+ if (exstr_alloc(&n->head, INITIAL_HEAD_CAPACITY) < 0) {
+ exstr_free(&n->body);
+ 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;
+}
View
24 ext/curly/native.h
@@ -0,0 +1,24 @@
+#ifndef _CURLY_NATIVE_H_
+#define _CURLY_NATIVE_H_
+
+#include <curl/curl.h>
+#include "exstr.h"
+
+typedef struct native_curly_ {
+ CURL* handle;
+ int curl_rc;
+ exstr body;
+ exstr head;
+
+ struct curl_slist* req_headers;
+} native_curly;
+
+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
View
294 ext/curly/request.c
@@ -2,18 +2,13 @@
#include <curl/curl.h>
#include "request.h"
#include "response.h"
+#include "native.h"
-/* Request member functions */
-static VALUE request_get (int argc, VALUE* argv, VALUE self);
-static VALUE request_post (int argc, VALUE* argv, VALUE self);
-static VALUE request_put (int argc, VALUE* argv, VALUE self);
-static VALUE request_delete(int argc, VALUE* argv, VALUE self);
+static VALUE request_run(VALUE self);
/* internal helpers */
static VALUE request_alloc(VALUE self);
-static VALUE request_perform(VALUE self, CURL* c, VALUE url, VALUE opts);
-static struct curl_slist* request_build_headers(VALUE self, CURL* c, VALUE opts);
-static int request_add_header(VALUE key, VALUE val, VALUE in);
+static VALUE request_perform(VALUE self, CURL* c, VALUE url);
static VALUE build_query_string(VALUE params);
/* curl callbacks */
@@ -21,246 +16,99 @@ static size_t header_callback(void* buffer, size_t size, size_t count, void* sel
static size_t data_callback (void* buffer, size_t size, size_t count, void* self);
static struct {
- VALUE to_query;
- VALUE params;
- VALUE body;
- VALUE headers;
- VALUE timeout;
+ VALUE method;
+ VALUE get, post, put, delete;
} syms;
-
void Init_curly_request(VALUE curly_mod)
{
VALUE request = rb_define_class_under(curly_mod, "Request", rb_cObject);
- rb_define_singleton_method(request, "get", request_get, -1);
- rb_define_singleton_method(request, "post", request_post, -1);
- rb_define_singleton_method(request, "put", request_put, -1);
- rb_define_singleton_method(request, "delete", request_delete, -1);
+ rb_define_method(request, "run", request_run, 0);
- syms.to_query = ID2SYM(rb_intern("to_query"));
- syms.params = ID2SYM(rb_intern("params"));
- syms.body = ID2SYM(rb_intern("body"));
- syms.headers = ID2SYM(rb_intern("headers"));
- syms.timeout = ID2SYM(rb_intern("timeout"));
+ syms.method = ID2SYM(rb_intern("method"));
+ syms.get = ID2SYM(rb_intern("get"));
+ syms.post = ID2SYM(rb_intern("post"));
+ syms.put = ID2SYM(rb_intern("put"));
+ syms.delete = ID2SYM(rb_intern("delete"));
}
-static VALUE request_perform(VALUE self, CURL* c, VALUE url, VALUE opts)
+static int request_add_header(VALUE key, VALUE val, VALUE ptr)
{
- int rc;
- long code;
- VALUE timeout, params, headers;
- VALUE resp = response_new();
-
- 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, resp);
-
- /* 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, resp);
-
- /* handle headers, query string, timeout, etc. */
- if (TYPE(opts) == T_HASH) {
-
- if ((headers = rb_hash_aref(opts, syms.headers)) != Qnil) {
- header_list = request_build_headers(self, c, headers);
- }
-
- if ((params = rb_hash_aref(opts, syms.params)) != Qnil) {
- url = rb_str_plus(url, rb_str_new2("?"));
- url = rb_str_append(url, build_query_string(params));
- }
-
- if ((timeout = rb_hash_aref(opts, syms.timeout)) != Qnil) {
- curl_easy_setopt(c, CURLOPT_TIMEOUT_MS, NUM2LONG(timeout));
- }
- }
-
- curl_easy_setopt(c, CURLOPT_URL, RSTRING_PTR(StringValue(url)));
-
- rc = curl_easy_perform(c);
- rb_iv_set(resp, "@curl_code", INT2NUM(rc));
-
- if (rc == CURLE_OK) {
- curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &code);
- rb_iv_set(resp, "@status", INT2NUM(code));
- } else {
- rb_iv_set(resp, "@curl_error", rb_str_new2(curl_easy_strerror(rc)));
- }
+ native_curly* n = (native_curly*)ptr;
+ VALUE text = rb_str_plus(StringValue(key), rb_str_new2(": "));
+ rb_str_append(text, StringValue(val));
- curl_slist_free_all(header_list);
+ native_curly_add_header(n, RSTRING_PTR(text));
- return resp;
+ return ST_CONTINUE;
}
-/*
- * call-seq:
- * Request.get(url, options = {}) -> Response
- *
- * Issues a HTTP GET to the specified +url+ using the provided
- * +options+.
- *
- */
-static VALUE request_get(int argc, VALUE* argv, VALUE self)
+static VALUE unlock_vm_wrapper(void* arg)
{
- CURL* c = curl_easy_init();
- VALUE url, rc;
- VALUE opts = Qnil;
-
- rb_scan_args(argc, argv, "11", &url, &opts);
-
- curl_easy_setopt(c, CURLOPT_HTTPGET, 1L);
-
- rc = request_perform(self, c, url, opts);
-
- curl_easy_cleanup(c);
+ native_curly* n = (native_curly*)arg;
+ native_curly_run_simple(n);
- return rc;
+ return Qnil;
}
-/*
- * call-seq:
- * Request.post(url, options = {}) -> Response
- *
- * Issues a HTTP POST to the specified +url+ using the provided
- * +options+.
- *
- */
-static VALUE request_post(int argc, VALUE* argv, VALUE self)
+void request_prepare(VALUE self, native_curly* n)
{
- CURL* c = curl_easy_init();
- VALUE opts = Qnil, body = Qnil;
- VALUE url, rc;
-
- rb_scan_args(argc, argv, "11", &url, &opts);
-
- curl_easy_setopt(c, CURLOPT_HTTPPOST, NULL);
-
- if (opts != Qnil &&
- (body = rb_hash_aref(opts, syms.body)) != Qnil) {
- /* TODO: handle case where `body` is a hash. */
- curl_easy_setopt(c, CURLOPT_POSTFIELDS, RSTRING_PTR(StringValue(body)));
+ 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");
+
+ method = (method == Qnil ? syms.get : rb_funcall(method, rb_intern("to_sym"), 0));
+
+ 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 */
}
- rc = request_perform(self, c, url, opts);
-
- curl_easy_cleanup(c);
-
- return rc;
-}
-
-/*
- * call-seq:
- * Request.put(url, options = {}) -> Response
- *
- * Issues a HTTP PUT to the specified +url+ using the provided
- * +options+.
- *
- */
-static VALUE request_put(int argc, VALUE* argv, VALUE self)
-{
- CURL* c = curl_easy_init();
- VALUE opts = Qnil, body = Qnil;
- VALUE url, rc;
-
- rb_scan_args(argc, argv, "11", &url, &opts);
-
-
- if (opts != Qnil &&
- (body = rb_hash_aref(opts, syms.body)) != Qnil) {
- /* TODO: handle case where `body` is a hash. */
- curl_easy_setopt(c, CURLOPT_POSTFIELDS, RSTRING_PTR(StringValue(body)));
+ if (headers != Qnil) {
+ rb_hash_foreach(headers, request_add_header, (VALUE)n);
}
- curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, "PUT");
-
- rc = request_perform(self, c, url, opts);
-
- curl_easy_cleanup(c);
-
- return rc;
-}
-
-/*
- * call-seq:
- * Request.delete(url, options = {}) -> Curly::Response
- *
- * Issues a HTTP DELETE to the specified +url+ using the provided
- * +options+.
- *
- */
-static VALUE request_delete(int argc, VALUE* argv, VALUE self)
-{
- CURL* c = curl_easy_init();
- VALUE url, rc;
- VALUE opts = Qnil;
-
- rb_scan_args(argc, argv, "11", &url, &opts);
-
- curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, "DELETE");
-
- rc = request_perform(self, c, url, opts);
-
- curl_easy_cleanup(c);
-
- return rc;
-}
-
-
-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;
+ native_curly_prepare(n, RSTRING_PTR(url),
+ timeout != Qnil ? NUM2LONG(timeout) : -1,
+ body != Qnil ? RSTRING_PTR(StringValue(body)) : NULL
+ );
}
-static struct curl_slist* request_build_headers(VALUE self, CURL* c, VALUE headers)
+static VALUE request_run(VALUE self)
{
- 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);
- }
+ native_curly n;
+ VALUE resp = response_new();
+ long code;
- return list;
-}
+ native_curly_alloc(&n);
+ request_prepare(self, &n);
-/* invoked by curl_easy_perform when a header is available */
-static size_t header_callback(void* buffer, size_t size, size_t count, void* self)
-{
- VALUE hash = rb_iv_get((VALUE)self, "@headers");
- VALUE str = rb_str_new(buffer, size * count);
- VALUE arr;
+ rb_thread_blocking_region(unlock_vm_wrapper, &n, NULL, NULL);
- /* make sure we have a string in the form 'Header: Value' then parse it. */
- rb_funcall(str, rb_intern("chomp!"), 0);
- arr = rb_funcall(str, rb_intern("split"), 2, rb_str_new2(": "), INT2NUM(2));
+ 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 (rb_funcall(arr, rb_intern("length"), 0) == INT2NUM(2)) {
- rb_hash_aset(hash, rb_ary_entry(arr, 0), rb_ary_entry(arr, 1));
+ if (n.curl_rc == CURLE_OK) {
+ 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)));
}
- 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* self)
-{
- VALUE body = rb_iv_get((VALUE)self, "@body");
- rb_str_cat(body, buffer, size * count);
+ native_curly_free(&n);
- return size * count;
+ return resp;
}
/* given a set of key/value pairs, build a url encoded string of the form
@@ -268,17 +116,7 @@ static size_t data_callback(void* buffer, size_t size, size_t count, void* self)
* our own compatible implementation */
static VALUE build_query_string(VALUE params)
{
- VALUE paramize;
- VALUE has_to_query = rb_funcall(params, rb_intern("respond_to?"), 1,
- syms.to_query);
-
- if (has_to_query == Qtrue) {
- /* use active support */
- return rb_funcall(params, rb_intern("to_query"), 0);
- } else {
- paramize = rb_const_get(rb_const_get(rb_cObject, rb_intern("Curly")),
- rb_intern("Parameterize"));
- /* use our own version */
- return rb_funcall(paramize, rb_intern("query_string"), 1, params);
- }
+ VALUE paramize = rb_const_get(rb_const_get(rb_cObject, rb_intern("Curly")),
+ rb_intern("Parameterize"));
+ return rb_funcall(paramize, rb_intern("query_string"), 1, params);
}
View
2  ext/curly/request.h
@@ -1,7 +1,9 @@
#ifndef _CURLY_REQUEST_H_
#define _CURLY_REQUEST_H_
#include <ruby.h>
+#include "native.h"
void Init_curly_request(VALUE curly_mod);
+void request_prepare(VALUE self, native_curly* n);
#endif
View
44 ext/curly/response.c
@@ -1,30 +1,6 @@
#include "response.h"
#include <curl/curl.h>
-
-/* :nodoc: */
-static VALUE response_init(VALUE self)
-{
- rb_iv_set(self, "@body", rb_str_new2(""));
- rb_iv_set(self, "@headers", rb_hash_new());
- rb_iv_set(self, "@status", INT2NUM(0));
-
- return Qnil;
-}
-
-/*
- * call-seq:
- * success? -> true or false
- *
- * returns +true+ if the HTTP response code returned from the server falls
- * in the +2xx+ range, +false+ otherwise.
- */
-static VALUE response_success(VALUE self)
-{
- int status = NUM2INT(rb_iv_get(self, "@status"));
- return (status >= 200 && status < 300 ? Qtrue : Qfalse);
-}
-
/*
* call-seq:
* timed_out? -> true or false
@@ -52,26 +28,6 @@ void Init_curly_response(VALUE curly_mod)
{
VALUE response = rb_define_class_under(curly_mod, "Response", rb_cObject);
- rb_define_method(response, "initialize", response_init, 0);
- rb_define_method(response, "success?", response_success, 0);
rb_define_method(response, "timed_out?", response_timed_out, 0);
- /*
- * the body (if any) returned by the server in response to the issued request.
- */
- rb_define_attr(response, "body", 1, 0);
- /* Hash of header name/value pairs returned from the server (if any). */
- rb_define_attr(response, "headers", 1, 0);
- /* the HTTP status code returned by the server. */
- rb_define_attr(response, "status", 1, 0);
- /* The error code returned by curl (if any). */
- rb_define_attr(response, "curl_code", 1, 0);
- /* If +curl_code+ is set, contains a string representation the error. */
- rb_define_attr(response, "curl_error", 1, 0);
-
- /* provide some aliases to match Typhoeus */
- rb_alias(response, rb_intern("code"), rb_intern("status"));
- rb_alias(response, rb_intern("curl_return_code"), rb_intern("curl_code"));
- rb_alias(response, rb_intern("curl_error_message"), rb_intern("curl_error"));
-
}
View
3  lib/curly.rb
@@ -7,3 +7,6 @@
require "curly/version"
require "curly/parameterize"
require "curly/curly"
+require "curly/request"
+require "curly/response"
+require "curly/multi"
View
12 lib/curly/multi.rb
@@ -0,0 +1,12 @@
+class Curly::Multi
+
+ def initialize
+ @requests = []
+ end
+
+ def add(request)
+ @requests.push(request)
+ end
+
+ alias_method :enqueue, :add
+end
View
10 lib/curly/parameterize.rb
@@ -2,18 +2,22 @@
# This class converts a hash of http request parameters into a query string of
# the form key=value&key2=value2&key3=value3... in a format compatible to the
-# +Object.to_query+ method provided by ActiveSupport. This class will only be
-# used if Curly detects that ActiveSupport's +to_query+ is not present.
+# +Object.to_query+ method provided by ActiveSupport.
+# If ActiveSupport +to_query+ is found that will be used instead.
module Curly::Parameterize
class << self
# Convert the given +params+ hash into a URLencoded query string.
def query_string(params)
- params.collect { |key, value| to_param(key, value) }.sort * '&'
+ params.respond_to?(:to_query) ? params.to_query : build_query_string(params)
end
private
+ def build_query_string(params)
+ params.collect { |key, value| to_param(key, value) }.sort * '&'
+ end
+
def to_param(key, value)
if value.instance_of?(Array)
value.collect{ |v| to_param("#{key}[]", v) }.join('&')
View
50 lib/curly/request.rb
@@ -0,0 +1,50 @@
+require 'uri'
+
+class Curly::Request
+ class << self
+
+ def get(url, opts = {})
+ opts[:method] = :get
+ Curly::Request.new(url, opts).run
+ end
+
+ def post(url, opts = {})
+ opts[:method] = :post
+ Curly::Request.new(url, opts).run
+ end
+
+ def put(url, opts = {})
+ opts[:method] = :put
+ Curly::Request.new(url, opts).run
+ end
+
+ def delete(url, opts = {})
+ opts[:method] = :delete
+ Curly::Request.new(url, opts).run
+ end
+
+ end
+
+ attr_accessor :url
+ attr_accessor :body, :headers, :method, :params, :timeout
+
+ attr_reader :response
+
+ def initialize(url, opts = {})
+ @url = url
+ @body = opts[:body]
+ @headers = opts[:headers]
+ @method = opts[:method]
+ @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
View
28 lib/curly/response.rb
@@ -0,0 +1,28 @@
+class Curly::Response
+ attr_reader :body, :head, :status, :curl_code, :curl_error
+
+ alias_method :code, :status
+
+ alias_method :curl_return_code, :curl_code
+ alias_method :curl_error_message, :curl_error
+
+ def initialize
+ @body = ''
+ @head = ''
+ @status = 0
+ end
+
+ # returns +true+ if the http status code returned by the server falls within
+ # the 2xx range, +false+ otherwise.
+ def success?
+ @status >= 200 && @status < 300
+ end
+
+ def headers
+ @headers ||= @head.split(/\r\n/).each_with_object({}) do |line, h|
+ key, value = line.split(': ', 2)
+ h[key] = value if value
+ end
+ end
+
+end
View
23 spec/easy_spec.rb
@@ -5,14 +5,6 @@
describe "Curly::Request" do
- before(:all) do
- SpecServer.start
- end
-
- after(:all) do
- SpecServer.stop
- end
-
let(:params) do
{
'x' => 'a',
@@ -70,6 +62,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 => {
@@ -133,4 +134,10 @@
resp.status.should eq(200)
end
+ it "reads headers set by the server" do
+ resp = Curly::Request.get("#{TEST_URL}/headers-test")
+ resp.headers['X-Sample-Header'].should eq('123')
+ resp.headers['X-Another-Header'].should eq('456')
+ end
+
end
View
26 spec/multi_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+require 'spec_server'
+require 'curly'
+require 'pry'
+
+describe "Curly::Multi" do
+
+ it "works with multiple requests" do
+ r1 = Curly::Request.new("#{TEST_URL}/status-test")
+ r2 = Curly::Request.new("#{TEST_URL}/body-test")
+
+ m = Curly::Multi.new
+
+ m.add(r1)
+ m.add(r2)
+
+ m.run
+
+ r1.response.code.should eq(418)
+
+ JSON.parse(r2.response.body).should eq(
+ 'value' => 1234
+ )
+
+ end
+end
View
4 spec/spec_helper.rb
@@ -15,3 +15,7 @@
# --seed 1234
config.order = 'random'
end
+
+require 'spec_server'
+SpecServer.start
+sleep 2
View
12 spec/spec_server.rb
@@ -5,18 +5,10 @@
class SpecServer < Sinatra::Base
def self.start
- @pid = fork {
- $stdout.reopen("/dev/null")
- $stderr.reopen("/dev/null")
- SpecServer.run!
- }
+ Thread.new { SpecServer.run! }
sleep 2
end
- def self.stop
- Process.kill("SIGTERM", @pid)
- end
-
get '/status-test' do
status 418
end
@@ -34,6 +26,8 @@ def self.stop
get '/headers-test' do
r = request.env.select{|k, v| k =~ /^HTTP_/ }
+ response.headers['X-Sample-Header'] = '123'
+ response.headers['X-Another-Header'] = '456'
JSON.dump(r)
end
Something went wrong with that request. Please try again.