abhay-typhoeus 0.0.22

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.gitignore +1 -0
  2. data/README.textile +212 -0
  3. data/Rakefile +45 -0
  4. data/VERSION +1 -0
  5. data/benchmarks/profile.rb +25 -0
  6. data/benchmarks/vs_nethttp.rb +35 -0
  7. data/examples/twitter.rb +21 -0
  8. data/ext/typhoeus/.gitignore +6 -0
  9. data/ext/typhoeus/extconf.rb +23 -0
  10. data/ext/typhoeus/native.c +11 -0
  11. data/ext/typhoeus/native.h +20 -0
  12. data/ext/typhoeus/typhoeus_easy.c +206 -0
  13. data/ext/typhoeus/typhoeus_easy.h +19 -0
  14. data/ext/typhoeus/typhoeus_multi.c +213 -0
  15. data/ext/typhoeus/typhoeus_multi.h +16 -0
  16. data/lib/typhoeus.rb +51 -0
  17. data/lib/typhoeus/.gitignore +1 -0
  18. data/lib/typhoeus/easy.rb +210 -0
  19. data/lib/typhoeus/filter.rb +28 -0
  20. data/lib/typhoeus/multi.rb +34 -0
  21. data/lib/typhoeus/remote.rb +306 -0
  22. data/lib/typhoeus/remote_method.rb +108 -0
  23. data/lib/typhoeus/remote_proxy_object.rb +48 -0
  24. data/lib/typhoeus/response.rb +17 -0
  25. data/lib/typhoeus/service.rb +20 -0
  26. data/profilers/valgrind.rb +24 -0
  27. data/spec/fixtures/result_set.xml +60 -0
  28. data/spec/servers/app.rb +24 -0
  29. data/spec/servers/delay_fixture_server.rb +66 -0
  30. data/spec/servers/method_server.rb +51 -0
  31. data/spec/spec.opts +2 -0
  32. data/spec/spec_helper.rb +29 -0
  33. data/spec/typhoeus/easy_spec.rb +148 -0
  34. data/spec/typhoeus/filter_spec.rb +35 -0
  35. data/spec/typhoeus/multi_spec.rb +82 -0
  36. data/spec/typhoeus/remote_method_spec.rb +141 -0
  37. data/spec/typhoeus/remote_proxy_object_spec.rb +73 -0
  38. data/spec/typhoeus/remote_spec.rb +699 -0
  39. data/spec/typhoeus/response_spec.rb +31 -0
  40. data/typhoeus.gemspec +88 -0
  41. metadata +104 -0
@@ -0,0 +1 @@
1
+ .*.sw?
@@ -0,0 +1,212 @@
1
+ h1. Typhoeus
2
+
3
+ "http://github.com/pauldix/typhoeus/tree/master":http://github.com/pauldix/typhoeus/tree/master
4
+
5
+ "the mailing list":http://groups.google.com/group/typhoeus
6
+
7
+ Thanks to my employer "kgbweb":http://kgbweb.com for allowing me to release this as open source. Btw, we're hiring and we work on cool stuff like this every day. Get a hold of me if you rock at rails/js/html/css or if you have experience in search, information retrieval, and machine learning.
8
+
9
+ I also wanted to thank Todd A. Fisher. I ripped a good chunk of the c libcurl-multi code from his update to Curb. Awesome stuff Todd!
10
+
11
+ h2. Summary
12
+
13
+ Like a modern code version of the mythical beast with 100 serpent heads, Typhoeus runs HTTP requests in parallel while cleanly encapsulating handling logic. To be a little more specific, it's a library for accessing web services in Ruby. It's specifically designed for building RESTful service oriented architectures in Ruby that need to be fast enough to process calls to multiple services within the client's HTTP request/response life cycle.
14
+
15
+ Some of the awesome features are parallel request execution, memoization of request responses (so you don't make the same request multiple times in a single group), built in support for caching responses to memcached (or whatever), a nifty DSL for creating classes that make http calls and process responses, and mocking capability baked in. It uses libcurl and libcurl-multi to work this speedy magic. I wrote the c bindings myself so it's yet another Ruby libcurl library, but with some extra awesomeness added in.
16
+
17
+ h2. Installation
18
+
19
+ For now Typhoeus exists only on github. It requires you to have a current version of libcurl installed. I've tested this with 7.19.4.
20
+ <pre>
21
+ gem sources -a http://gems.github.com # if you haven't already
22
+ gem install pauldix-typhoeus
23
+ </pre>
24
+ If you're on Debian or Ubuntu and getting errors while trying to install, it could be because you don't have the latest version of libcurl installed. Do this to fix:
25
+ <pre>
26
+ sudo apt-get install libcurl4-gnutls-dev
27
+ </pre>
28
+
29
+ Another problem could be if you are running Mac Ports and you have libcurl installed through there. You need to uninstall it for Typhoeus to work! The version in Mac Ports is old and doesn't play nice. You should "download curl":http://curl.haxx.se/download.html and build from source. Then you'll have to install the gem again.
30
+
31
+ If you're still having issues, please let me know on "the mailing list":http://groups.google.com/group/typhoeus.
32
+
33
+ There's one other thing you should know. The Easy object (which is just a libcurl thing) allows you to set timeout values in milliseconds. However, for this to work you need to build libcurl with c-ares support built in. Unfortunately, I still haven't been able to get this to work on either my Mac or Linux. I'll have to figure that out.
34
+
35
+ h2. Usage
36
+
37
+
38
+ <pre>
39
+ require 'rubygems'
40
+ require 'typhoeus'
41
+ require 'json'
42
+
43
+ # here's an example for twitter search
44
+ # Including Typhoeus adds http methods like get, put, post, and delete.
45
+ # What's more interesting though is the stuff to build up what I call
46
+ # remote_methods.
47
+ class Twitter
48
+ include Typhoeus
49
+ remote_defaults :on_success => lambda {|response| JSON.parse(response.body)},
50
+ :on_failure => lambda {|response| puts "error code: #{response.code}"},
51
+ :base_uri => "http://search.twitter.com"
52
+
53
+ define_remote_method :search, :path => '/search.json'
54
+ define_remote_method :trends, :path => '/trends/:time_frame.json'
55
+ end
56
+
57
+ tweets = Twitter.search(:params => {:q => "railsconf"})
58
+
59
+ # if you look at the path argument for the :trends method, it has :time_frame.
60
+ # this tells it to add in a parameter called :time_frame that gets interpolated
61
+ # and inserted.
62
+ trends = Twitter.trends(:time_frame => :current)
63
+
64
+ # and then the calls don't actually happen until the first time you
65
+ # call a method on one of the objects returned from the remote_method
66
+ puts tweets.keys # it's a hash from parsed JSON
67
+
68
+ # you can also do things like override any of the default parameters
69
+ Twitter.search(:params => {:q => "hi"}, :on_success => lambda {|response| puts response.body})
70
+
71
+ # on_success and on_failure lambdas take a response object.
72
+ # It has four accesssors: code, body, headers, and time
73
+
74
+ # here's and example of memoization
75
+ twitter_searches = []
76
+ 10.times do
77
+ twitter_searches << Twitter.search(:params => {:q => "railsconf"})
78
+ end
79
+
80
+ # this next part will actually make the call. However, it only makes one
81
+ # http request and parses the response once. The rest are memoized.
82
+ twitter_searches.each {|s| puts s.keys}
83
+
84
+ # you can also have it cache responses and do gets automatically
85
+ # here we define a remote method that caches the responses for 60 seconds
86
+ klass = Class.new do
87
+ include Typhoeus
88
+
89
+ define_remote_method :foo, :base_uri => "http://localhost:3001", :cache_responses => 60
90
+ end
91
+
92
+ klass.cache = some_memcached_instance_or_whatever
93
+ response = klass.foo
94
+ puts response.body # makes the request
95
+
96
+ second_response = klass.foo
97
+ puts response.body # pulls from the cache without making a request
98
+
99
+ # you can also pass timeouts on the define_remote_method or as a parameter
100
+ # Note that timeouts are in milliseconds.
101
+ Twitter.trends(:time_frame => :current, :timeout => 2000)
102
+
103
+ # you also get the normal get, put, post, and delete methods
104
+ class Remote
105
+ include Typhoeus
106
+ end
107
+
108
+ Remote.get("http://www.pauldix.net")
109
+ Remote.put("http://", :body => "this is a request body")
110
+ Remote.post("http://localhost:3001/posts.xml",
111
+ {:params => {:post => {:author => "paul", :title => "a title", :body => "a body"}}})
112
+ Remote.delete("http://localhost:3001/posts/1")
113
+
114
+ # you also have the ability to set request headers. So you can set your user agent and manually
115
+ Remote.get("http://www.pauldix.net", :headers => {"User-Agent" => "typhoeus", "If-None-Match" => "some etag"})
116
+
117
+ # and do things like basic HTTP authentication
118
+ require 'base64'
119
+ Remote.get("http://twitter.com/statuses/followers.json",
120
+ :headers => {"Authorization" => "Basic #{Base64.b64encode("login:password")}"})
121
+
122
+ # body and headers arguments also get passed through on defined remote methods.
123
+ class TwitterRestAPI
124
+ include Typhoeus
125
+ remote_defaults :on_success => lambda {|response| JSON.parse(response.body)},
126
+ :on_failure => lambda {|response| puts "error code: #{response.code}"},
127
+ :base_uri => "http://twitter.com"
128
+
129
+ define_remote_method :followers,
130
+ :path => '/statuses/followers.json',
131
+ :headers => {"Authorization" => "Basic #{Base64.b64encode("twitter_id:password")}"}
132
+ end
133
+
134
+ # The response object returned by get, put, post, and delete is passed to the on_success
135
+ # or on_failure lambda block if declared.
136
+ # The return value of the lambda block is then what is returned by the remote method invocation.
137
+ # The response object can do the following:
138
+ response.code # the http return code
139
+ response.body # the body of the response
140
+ response.headers # the response headers
141
+ response.time # the response time in seconds
142
+
143
+ # Typhoeus also has a nifty mocking framework built in
144
+ # mock all calls to get
145
+ Remote.mock(:get, :code => 200, :body => "whatever")
146
+
147
+ # here we mock calls to get for the url
148
+ Remote.mock(:get, :url => "http://pauldix.net", :code => 200, :body => "hi", :headers => "there", :time => 2)
149
+
150
+ # note that url, code, body, headers, and time are all optional parameters to mock.
151
+ # the first parameter can be either :get, :put, :post, or :delete
152
+
153
+ # you can also provide headers and body that are expected on the call. An exception will be raised if they don't match
154
+ Remote.mock(:get, :url => "http://pauldix.net", :expected_headers => {"If-None-Match" => "sldfkj234"})
155
+ Remote.mock(:put, :url => "http://pauldix.net", :expected_body => "this is a body!")
156
+
157
+ # using that mocking you could mock out the Twitter client like so:
158
+ Twitter.mock(:get, :body => '{"hi": "there"}')
159
+ # now any calls to trends, or search will get the mock and call the on_success handler. the response object will have that body.
160
+ # we could also mock out a failure like so
161
+ Twitter.mock(:get, :body => '{"fail": "oh noes!"}', :code => 500)
162
+ # now calls to a remote method will result in the on_failure handler being called
163
+ </pre>
164
+
165
+ The best place to see the functionality of what including Typhoeus in a class gives you is to look at the "remote_spec.rb"
166
+
167
+ h2. Benchmarks
168
+
169
+ I set up a benchmark to test how the parallel performance works vs Ruby's built in NET::HTTP. The setup was a local evented HTTP server that would take a request, sleep for 500 milliseconds and then issued a blank response. I set up the client to call this 20 times. Here are the results:
170
+
171
+ <pre>
172
+ net::http 0.030000 0.010000 0.040000 ( 10.054327)
173
+ typhoeus 0.020000 0.070000 0.090000 ( 0.508817)
174
+ </pre>
175
+
176
+ We can see from this that NET::HTTP performs as expected, taking 10 seconds to run 20 500ms requests. Typhoeus only takes 500ms (the time of the response that took the longest.) One other thing to note is that Typhoeus keeps a pool of libcurl Easy handles to use. For this benchmark I warmed the pool first. So if you test this out it may be a bit slower until the Easy handle pool has enough in it to run all the simultaneous requests. For some reason the easy handles can take quite some time to allocate.
177
+
178
+ h2. Next Steps
179
+
180
+ * Write up some more examples.
181
+ * Create a SimpleDB client library using Typhoeus.
182
+ * Create or get someone to create a CouchDB client library using Typhoeus.
183
+ * Add support for automatic retry, exponential back-off, and queuing for later.
184
+ * Add in the support for custom get and set methods on the cache.
185
+ * Add in support for integrated HTTP caching with Memcached.
186
+
187
+ h2. LICENSE
188
+
189
+ (The MIT License)
190
+
191
+ Copyright (c) 2009:
192
+
193
+ "Paul Dix":http://pauldix.net
194
+
195
+ Permission is hereby granted, free of charge, to any person obtaining
196
+ a copy of this software and associated documentation files (the
197
+ 'Software'), to deal in the Software without restriction, including
198
+ without limitation the rights to use, copy, modify, merge, publish,
199
+ distribute, sublicense, and/or sell copies of the Software, and to
200
+ permit persons to whom the Software is furnished to do so, subject to
201
+ the following conditions:
202
+
203
+ The above copyright notice and this permission notice shall be
204
+ included in all copies or substantial portions of the Software.
205
+
206
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
207
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
208
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
209
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
210
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
211
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
212
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,45 @@
1
+ begin
2
+ require './lib/typhoeus.rb'
3
+ rescue LoadError => err
4
+ puts "Typhoeus has not been built correctly: #{err.message}"
5
+ end
6
+
7
+ begin
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |gemspec|
10
+ gemspec.name = "typhoeus"
11
+ gemspec.summary = "A library for interacting with web services (and building SOAs) at blinding speed."
12
+ gemspec.email = "paul@pauldix.net"
13
+ gemspec.homepage = "http://github.com/pauldix/typhoeus"
14
+ gemspec.authors = ["Paul Dix"]
15
+
16
+ gemspec.require_path = ['lib', 'ext']
17
+ gemspec.extra_rdoc_files = `git ls-files *.rdoc`.split("\n")
18
+ gemspec.files = `git ls-files`.split("\n")
19
+ gemspec.extensions = ["ext/typhoeus/extconf.rb"]
20
+ end
21
+
22
+ task :install do
23
+ # rm_rf "*.gem"
24
+ # puts `gem build typhoeus.gemspec`
25
+ # puts `sudo gem install typhoeus-#{Typhoeus::VERSION}.gem`
26
+ end
27
+
28
+ rescue LoadError
29
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
30
+ end
31
+
32
+ begin
33
+ require "spec"
34
+ require "spec/rake/spectask"
35
+
36
+ Spec::Rake::SpecTask.new do |t|
37
+ t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
38
+ t.spec_files = FileList['spec/**/*_spec.rb']
39
+ end
40
+
41
+ desc "Run all the tests"
42
+ task :default => :spec
43
+ rescue LoadError => err
44
+ puts "RSpec not available"
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.22
@@ -0,0 +1,25 @@
1
+ require File.dirname(__FILE__) + '/../lib/typhoeus.rb'
2
+ require 'rubygems'
3
+ require 'ruby-prof'
4
+
5
+ calls = 20
6
+ @klass = Class.new do
7
+ include Typhoeus
8
+ end
9
+
10
+ Typhoeus.init_easy_objects
11
+
12
+ RubyProf.start
13
+
14
+ responses = []
15
+ calls.times do |i|
16
+ responses << @klass.get("http://127.0.0.1:3000/#{i}")
17
+ end
18
+
19
+ responses.each {|r| }#raise unless r.response_body == "whatever"}
20
+
21
+ result = RubyProf.stop
22
+
23
+ # Print a flat profile to text
24
+ printer = RubyProf::FlatPrinter.new(result)
25
+ printer.print(STDOUT, 0)
@@ -0,0 +1,35 @@
1
+ require 'rubygems'
2
+ require File.dirname(__FILE__) + '/../lib/typhoeus.rb'
3
+ require 'open-uri'
4
+ require 'benchmark'
5
+ include Benchmark
6
+
7
+
8
+ calls = 20
9
+ @klass = Class.new do
10
+ include Typhoeus
11
+ end
12
+
13
+ Typhoeus.init_easy_object_pool
14
+
15
+ benchmark do |t|
16
+ t.report("net::http") do
17
+ responses = []
18
+
19
+ calls.times do |i|
20
+ responses << open("http://127.0.0.1:3000/#{i}").read
21
+ end
22
+
23
+ responses.each {|r| raise unless r == "whatever"}
24
+ end
25
+
26
+ t.report("typhoeus") do
27
+ responses = []
28
+
29
+ calls.times do |i|
30
+ responses << @klass.get("http://127.0.0.1:3000/#{i}")
31
+ end
32
+
33
+ responses.each {|r| raise unless r.body == "whatever"}
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + '/../lib/typhoeus.rb'
2
+ require 'rubygems'
3
+ require 'json'
4
+
5
+ class Twitter
6
+ include Typhoeus
7
+ remote_defaults :on_success => lambda {|response| JSON.parse(response.body)},
8
+ :on_failure => lambda {|response| puts "error code: #{response.code}"},
9
+ :base_uri => "http://search.twitter.com"
10
+
11
+ define_remote_method :search, :path => '/search.json'
12
+ define_remote_method :trends, :path => '/trends/:time_frame.json'
13
+ end
14
+
15
+ tweets = Twitter.search(:params => {:q => "railsconf"})
16
+ trends = Twitter.trends(:time_frame => :current)
17
+
18
+ # and then the calls don't actually happen until the first time you
19
+ # call a method on one of the objects returned from the remote_method
20
+
21
+ puts tweets.keys # it's a hash from parsed JSON
@@ -0,0 +1,6 @@
1
+ conftest.dSYM/*
2
+ conftest.dSYM
3
+ mkmf.log
4
+ *.o
5
+ native.bundle
6
+ Makefile
@@ -0,0 +1,23 @@
1
+ ENV["ARCHFLAGS"] = "-arch #{`uname -p` =~ /powerpc/ ? 'ppc' : 'i386'}"
2
+
3
+ require 'mkmf'
4
+
5
+ dir_config('curl')
6
+
7
+ if find_executable('curl-config')
8
+ $CFLAGS << " #{`curl-config --cflags`.strip}"
9
+ $LIBS << " #{`curl-config --libs`.strip}"
10
+ elsif !have_library('curl') or !have_header('curl/curl.h')
11
+ fail <<-EOM
12
+ Can't find libcurl or curl/curl.h
13
+
14
+ Try passing --with-curl-dir or --with-curl-lib and --with-curl-include
15
+ options to extconf.
16
+ EOM
17
+ end
18
+
19
+ if try_compile('int main() { return 0; }','-Wall')
20
+ $CFLAGS << ' -O3 -Wall -Wcast-qual -Wwrite-strings -Wconversion -Wmissing-noreturn -Winline'
21
+ end
22
+
23
+ create_makefile("typhoeus/native")
@@ -0,0 +1,11 @@
1
+ #include <native.h>
2
+
3
+ VALUE mTyphoeus;
4
+
5
+ void Init_native()
6
+ {
7
+ mTyphoeus = rb_const_get(rb_cObject, rb_intern("Typhoeus"));
8
+
9
+ init_typhoeus_easy();
10
+ init_typhoeus_multi();
11
+ }
@@ -0,0 +1,20 @@
1
+ #ifndef TYPHOEUS_NATIVE
2
+ #define TYPHOEUS_NATIVE
3
+
4
+ #include <ruby.h>
5
+
6
+ #ifndef RSTRING_PTR
7
+ #define RSTRING_PTR(s) (RSTRING(s)->ptr)
8
+ #endif
9
+
10
+ #ifndef RSTRING_LEN
11
+ #define RSTRING_LEN(s) (RSTRING(s)->len)
12
+ #endif
13
+
14
+ #include <curl/curl.h>
15
+ #include <curl/easy.h>
16
+ #include <curl/multi.h>
17
+
18
+ extern VALUE mTyphoeus;
19
+
20
+ #endif
@@ -0,0 +1,206 @@
1
+ #include <typhoeus_easy.h>
2
+
3
+ static VALUE idAppend;
4
+ VALUE cTyphoeusEasy;
5
+
6
+ static void dealloc(CurlEasy *curl_easy) {
7
+ if (curl_easy->request_chunk != NULL) {
8
+ free(curl_easy->request_chunk);
9
+ }
10
+
11
+ if (curl_easy->headers != NULL) {
12
+ curl_slist_free_all(curl_easy->headers);
13
+ }
14
+
15
+ curl_easy_cleanup(curl_easy->curl);
16
+
17
+ free(curl_easy);
18
+ }
19
+
20
+ static VALUE easy_setopt_string(VALUE self, VALUE opt_name, VALUE parameter) {
21
+ CurlEasy *curl_easy;
22
+ Data_Get_Struct(self, CurlEasy, curl_easy);
23
+
24
+ long opt = NUM2LONG(opt_name);
25
+ curl_easy_setopt(curl_easy->curl, opt, StringValuePtr(parameter));
26
+ return opt_name;
27
+ }
28
+
29
+ static VALUE easy_setopt_long(VALUE self, VALUE opt_name, VALUE parameter) {
30
+ CurlEasy *curl_easy;
31
+ Data_Get_Struct(self, CurlEasy, curl_easy);
32
+
33
+ long opt = NUM2LONG(opt_name);
34
+ curl_easy_setopt(curl_easy->curl, opt, NUM2LONG(parameter));
35
+ return opt_name;
36
+ }
37
+
38
+ static VALUE easy_getinfo_string(VALUE self, VALUE info) {
39
+ char *info_string;
40
+ CurlEasy *curl_easy;
41
+ Data_Get_Struct(self, CurlEasy, curl_easy);
42
+
43
+ long opt = NUM2LONG(info);
44
+ curl_easy_getinfo(curl_easy->curl, opt, &info_string);
45
+
46
+ return rb_str_new2(info_string);
47
+ }
48
+
49
+ static VALUE easy_getinfo_long(VALUE self, VALUE info) {
50
+ long info_long;
51
+ CurlEasy *curl_easy;
52
+ Data_Get_Struct(self, CurlEasy, curl_easy);
53
+
54
+ long opt = NUM2LONG(info);
55
+ curl_easy_getinfo(curl_easy->curl, opt, &info_long);
56
+
57
+ return LONG2NUM(info_long);
58
+ }
59
+
60
+ static VALUE easy_getinfo_double(VALUE self, VALUE info) {
61
+ double info_double = 0;
62
+ CurlEasy *curl_easy;
63
+ Data_Get_Struct(self, CurlEasy, curl_easy);
64
+
65
+ long opt = NUM2LONG(info);
66
+ curl_easy_getinfo(curl_easy->curl, opt, &info_double);
67
+
68
+ return rb_float_new(info_double);
69
+ }
70
+
71
+ static VALUE easy_perform(VALUE self) {
72
+ CurlEasy *curl_easy;
73
+ Data_Get_Struct(self, CurlEasy, curl_easy);
74
+ curl_easy_perform(curl_easy->curl);
75
+
76
+ return Qnil;
77
+ }
78
+
79
+ static size_t write_data_handler(char *stream, size_t size, size_t nmemb, VALUE val) {
80
+ rb_funcall(val, idAppend, 1, rb_str_new(stream, size * nmemb));
81
+ return size * nmemb;
82
+ }
83
+
84
+ static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *data) {
85
+ int realsize = size * nmemb;
86
+ RequestChunk *mem = (RequestChunk *)data;
87
+
88
+ if (realsize > mem->size - mem->read) {
89
+ realsize = mem->size - mem->read;
90
+ }
91
+
92
+ if (realsize != 0) {
93
+ memcpy(ptr, &(mem->memory[mem->read]), realsize);
94
+ mem->read += realsize;
95
+ }
96
+
97
+ return realsize;
98
+ }
99
+
100
+ static void set_response_handlers(VALUE easy, CURL *curl) {
101
+ rb_iv_set(easy, "@response_body", rb_str_new2(""));
102
+ rb_iv_set(easy, "@response_header", rb_str_new2(""));
103
+
104
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&write_data_handler);
105
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, rb_iv_get(easy, "@response_body"));
106
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, (curl_write_callback)&write_data_handler);
107
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, rb_iv_get(easy, "@response_header"));
108
+ }
109
+
110
+ static VALUE easy_reset(VALUE self) {
111
+ CurlEasy *curl_easy;
112
+ Data_Get_Struct(self, CurlEasy, curl_easy);
113
+
114
+ if (curl_easy->request_chunk != NULL) {
115
+ free(curl_easy->request_chunk);
116
+ curl_easy->request_chunk = NULL;
117
+ }
118
+
119
+ if (curl_easy->headers != NULL) {
120
+ curl_slist_free_all(curl_easy->headers);
121
+ curl_easy->headers = NULL;
122
+ }
123
+
124
+ curl_easy_reset(curl_easy->curl);
125
+
126
+ set_response_handlers(self, curl_easy->curl);
127
+
128
+ return Qnil;
129
+ }
130
+
131
+ static VALUE easy_add_header(VALUE self, VALUE header) {
132
+ CurlEasy *curl_easy;
133
+ Data_Get_Struct(self, CurlEasy, curl_easy);
134
+
135
+ curl_easy->headers = curl_slist_append(curl_easy->headers, RSTRING_PTR(header));
136
+ return header;
137
+ }
138
+
139
+ static VALUE easy_set_headers(VALUE self) {
140
+ CurlEasy *curl_easy;
141
+ Data_Get_Struct(self, CurlEasy, curl_easy);
142
+
143
+ curl_easy_setopt(curl_easy->curl, CURLOPT_HTTPHEADER, curl_easy->headers);
144
+
145
+ return Qnil;
146
+ }
147
+
148
+ static VALUE easy_set_request_body(VALUE self, VALUE data, VALUE content_length_header) {
149
+ CurlEasy *curl_easy;
150
+ Data_Get_Struct(self, CurlEasy, curl_easy);
151
+
152
+ curl_easy->request_chunk = ALLOC(RequestChunk);
153
+ curl_easy->request_chunk->size = RSTRING_LEN(data);
154
+ curl_easy->request_chunk->memory = StringValuePtr(data);
155
+ curl_easy->request_chunk->read = 0;
156
+
157
+ curl_easy_setopt(curl_easy->curl, CURLOPT_READFUNCTION, (curl_read_callback)read_callback);
158
+ curl_easy_setopt(curl_easy->curl, CURLOPT_READDATA, curl_easy->request_chunk);
159
+ curl_easy_setopt(curl_easy->curl, CURLOPT_INFILESIZE, RSTRING_LEN(data));
160
+
161
+ return Qnil;
162
+ }
163
+
164
+ static VALUE easy_escape(VALUE self, VALUE data, VALUE length) {
165
+ CurlEasy *curl_easy;
166
+ Data_Get_Struct(self, CurlEasy, curl_easy);
167
+
168
+ return rb_str_new2(curl_easy_escape(curl_easy->curl, StringValuePtr(data), NUM2INT(length)));
169
+ }
170
+
171
+ static VALUE version(VALUE self) {
172
+ return rb_str_new2(curl_version());
173
+ }
174
+
175
+ static VALUE new(int argc, VALUE *argv, VALUE klass) {
176
+ CURL *curl = curl_easy_init();
177
+ CurlEasy *curl_easy = ALLOC(CurlEasy);
178
+ curl_easy->curl = curl;
179
+ curl_easy->headers = NULL;
180
+ curl_easy->request_chunk = NULL;
181
+ VALUE easy = Data_Wrap_Struct(cTyphoeusEasy, 0, dealloc, curl_easy);
182
+
183
+ set_response_handlers(easy, curl);
184
+
185
+ rb_obj_call_init(easy, argc, argv);
186
+
187
+ return easy;
188
+ }
189
+
190
+ void init_typhoeus_easy() {
191
+ VALUE klass = cTyphoeusEasy = rb_define_class_under(mTyphoeus, "Easy", rb_cObject);
192
+ idAppend = rb_intern("<<");
193
+ rb_define_singleton_method(klass, "new", new, -1);
194
+ rb_define_private_method(klass, "easy_setopt_string", easy_setopt_string, 2);
195
+ rb_define_private_method(klass, "easy_setopt_long", easy_setopt_long, 2);
196
+ rb_define_private_method(klass, "easy_getinfo_string", easy_getinfo_string, 1);
197
+ rb_define_private_method(klass, "easy_getinfo_long", easy_getinfo_long, 1);
198
+ rb_define_private_method(klass, "easy_getinfo_double", easy_getinfo_double, 1);
199
+ rb_define_private_method(klass, "easy_perform", easy_perform, 0);
200
+ rb_define_private_method(klass, "easy_reset", easy_reset, 0);
201
+ rb_define_private_method(klass, "easy_set_request_body", easy_set_request_body, 1);
202
+ rb_define_private_method(klass, "easy_set_headers", easy_set_headers, 0);
203
+ rb_define_private_method(klass, "easy_add_header", easy_add_header, 1);
204
+ rb_define_private_method(klass, "easy_escape", easy_escape, 2);
205
+ rb_define_private_method(klass, "version", version, 0);
206
+ }