Giter VIP home page Giter VIP logo

ethon's Introduction

Typhoeus CI Experimental Code Climate Gem Version

Like a modern code version of the mythical beast with 100 serpent heads, Typhoeus runs HTTP requests in parallel while cleanly encapsulating handling logic.

Example

A single request:

Typhoeus.get("www.example.com", followlocation: true)

Parallel requests:

hydra = Typhoeus::Hydra.new
10.times.map{ hydra.queue(Typhoeus::Request.new("www.example.com", followlocation: true)) }
hydra.run

Installation

Run:

bundle add typhoeus

Or install it yourself as:

gem install typhoeus

Project Tracking

Usage

Introduction

The primary interface for Typhoeus is comprised of three classes: Request, Response, and Hydra. Request represents an HTTP request object, response represents an HTTP response, and Hydra manages making parallel HTTP connections.

request = Typhoeus::Request.new(
  "www.example.com",
  method: :post,
  body: "this is a request body",
  params: { field1: "a field" },
  headers: { Accept: "text/html" }
)

We can see from this that the first argument is the url. The second is a set of options. The options are all optional. The default for :method is :get.

When you want to send URL parameters, you can use :params hash to do so. Please note that in case of you should send a request via x-www-form-urlencoded parameters, you need to use :body hash instead. params are for URL parameters and :body is for the request body.

Sending requests through the proxy

Add a proxy url to the list of options:

options = {proxy: 'http://myproxy.org'}
req = Typhoeus::Request.new(url, options)

If your proxy requires authentication, add it with proxyuserpwd option key:

options = {proxy: 'http://proxyurl.com', proxyuserpwd: 'user:password'}
req = Typhoeus::Request.new(url, options)

Note that proxyuserpwd is a colon-separated username and password, in the vein of basic auth userpwd option.

You can run the query either on its own or through the hydra:

request.run
#=> <Typhoeus::Response ... >
hydra = Typhoeus::Hydra.hydra
hydra.queue(request)
hydra.run

The response object will be set after the request is run.

response = request.response
response.code
response.total_time
response.headers
response.body

Making Quick Requests

Typhoeus has some convenience methods for performing single HTTP requests. The arguments are the same as those you pass into the request constructor.

Typhoeus.get("www.example.com")
Typhoeus.head("www.example.com")
Typhoeus.put("www.example.com/posts/1", body: "whoo, a body")
Typhoeus.patch("www.example.com/posts/1", body: "a new body")
Typhoeus.post("www.example.com/posts", body: { title: "test post", content: "this is my test"})
Typhoeus.delete("www.example.com/posts/1")
Typhoeus.options("www.example.com")

Sending params in the body with PUT

When using POST the content-type is set automatically to 'application/x-www-form-urlencoded'. That's not the case for any other method like PUT, PATCH, HEAD and so on, irrespective of whether you are using body or not. To get the same result as POST, i.e. a hash in the body coming through as params in the receiver, you need to set the content-type as shown below:

Typhoeus.put("www.example.com/posts/1",
        headers: {'Content-Type'=> "application/x-www-form-urlencoded"},
        body: {title:"test post updated title", content: "this is my updated content"}
    )

Handling HTTP errors

You can query the response object to figure out if you had a successful request or not. Here’s some example code that you might use to handle errors. The callbacks are executed right after the request is finished, make sure to define them before running the request.

request = Typhoeus::Request.new("www.example.com", followlocation: true)

request.on_complete do |response|
  if response.success?
    # hell yeah
  elsif response.timed_out?
    # aw hell no
    log("got a time out")
  elsif response.code == 0
    # Could not get an http response, something's wrong.
    log(response.return_message)
  else
    # Received a non-successful http response.
    log("HTTP request failed: " + response.code.to_s)
  end
end

request.run

This also works with serial (blocking) requests in the same fashion. Both serial and parallel requests return a Response object.

Handling file uploads

A File object can be passed as a param for a POST request to handle uploading files to the server. Typhoeus will upload the file as the original file name and use Mime::Types to set the content type.

Typhoeus.post(
  "http://localhost:3000/posts",
  body: {
    title: "test post",
    content: "this is my test",
    file: File.open("thesis.txt","r")
  }
)

Streaming the response body

Typhoeus can stream responses. When you're expecting a large response, set the on_body callback on a request. Typhoeus will yield to the callback with chunks of the response, as they're read. When you set an on_body callback, Typhoeus will not store the complete response.

downloaded_file = File.open 'huge.iso', 'wb'
request = Typhoeus::Request.new("www.example.com/huge.iso")
request.on_headers do |response|
  if response.code != 200
    raise "Request failed"
  end
end
request.on_body do |chunk|
  downloaded_file.write(chunk)
end
request.on_complete do |response|
  downloaded_file.close
  # Note that response.body is ""
end
request.run

If you need to interrupt the stream halfway, you can return the :abort symbol from the on_body block, example:

request.on_body do |chunk|
  buffer << chunk
  :abort if buffer.size > 1024 * 1024
end

This will properly stop the stream internally and avoid any memory leak which may happen if you interrupt with something like a return, throw or raise.

Making Parallel Requests

Generally, you should be running requests through hydra. Here is how that looks:

hydra = Typhoeus::Hydra.hydra

first_request = Typhoeus::Request.new("http://example.com/posts/1")
first_request.on_complete do |response|
  third_url = response.body
  third_request = Typhoeus::Request.new(third_url)
  hydra.queue third_request
end
second_request = Typhoeus::Request.new("http://example.com/posts/2")

hydra.queue first_request
hydra.queue second_request
hydra.run # this is a blocking call that returns once all requests are complete

The execution of that code goes something like this. The first and second requests are built and queued. When hydra is run the first and second requests run in parallel. When the first request completes, the third request is then built and queued, in this example based on the result of the first request. The moment it is queued Hydra starts executing it. Meanwhile the second request would continue to run (or it could have completed before the first). Once the third request is done, hydra.run returns.

How to get an array of response bodies back after executing a queue:

hydra = Typhoeus::Hydra.new
requests = 10.times.map {
  request = Typhoeus::Request.new("www.example.com", followlocation: true)
  hydra.queue(request)
  request
}
hydra.run

responses = requests.map { |request|
  request.response.body
}

hydra.run is a blocking request. You can also use the on_complete callback to handle each request as it completes:

hydra = Typhoeus::Hydra.new
10.times do
  request = Typhoeus::Request.new("www.example.com", followlocation: true)
  request.on_complete do |response|
    #do_something_with response
  end
  hydra.queue(request)
end
hydra.run

Making Parallel Requests with Faraday + Typhoeus

require 'faraday'

conn = Faraday.new(:url => 'http://httppage.com') do |builder|
  builder.request  :url_encoded
  builder.response :logger
  builder.adapter  :typhoeus
end

conn.in_parallel do
  response1 = conn.get('/first')
  response2 = conn.get('/second')

  # these will return nil here since the
  # requests have not been completed
  response1.body
  response2.body
end

# after it has been completed the response information is fully available
# response1.status, etc
response1.body
response2.body

Specifying Max Concurrency

Hydra will also handle how many requests you can make in parallel. Things will get flakey if you try to make too many requests at the same time. The built in limit is 200. When more requests than that are queued up, hydra will save them for later and start the requests as others are finished. You can raise or lower the concurrency limit through the Hydra constructor.

Typhoeus::Hydra.new(max_concurrency: 20)

Memoization

Hydra memoizes requests within a single run call. You have to enable memoization. This will result in a single request being issued. However, the on_complete handlers of both will be called.

Typhoeus::Config.memoize = true

hydra = Typhoeus::Hydra.new(max_concurrency: 1)
2.times do
  hydra.queue Typhoeus::Request.new("www.example.com")
end
hydra.run

This will result in two requests.

Typhoeus::Config.memoize = false

hydra = Typhoeus::Hydra.new(max_concurrency: 1)
2.times do
  hydra.queue Typhoeus::Request.new("www.example.com")
end
hydra.run

Caching

Typhoeus includes built in support for caching. In the following example, if there is a cache hit, the cached object is passed to the on_complete handler of the request object.

class Cache
  def initialize
    @memory = {}
  end

  def get(request)
    @memory[request]
  end

  def set(request, response)
    @memory[request] = response
  end
end

Typhoeus::Config.cache = Cache.new

Typhoeus.get("www.example.com").cached?
#=> false
Typhoeus.get("www.example.com").cached?
#=> true

For use with Dalli:

require "typhoeus/cache/dalli"

dalli = Dalli::Client.new(...)
Typhoeus::Config.cache = Typhoeus::Cache::Dalli.new(dalli)

For use with Rails:

require "typhoeus/cache/rails"

Typhoeus::Config.cache = Typhoeus::Cache::Rails.new

For use with Redis:

require "typhoeus/cache/redis"

redis = Redis.new(...)
Typhoeus::Config.cache = Typhoeus::Cache::Redis.new(redis)

All three of these adapters take an optional keyword argument default_ttl, which sets a default TTL on cached responses (in seconds), for requests which do not have a cache TTL set.

You may also selectively choose not to cache by setting cache to false on a request or to use a different adapter.

cache = Cache.new
Typhoeus.get("www.example.com", cache: cache)

Direct Stubbing

Hydra allows you to stub out specific urls and patterns to avoid hitting remote servers while testing.

response = Typhoeus::Response.new(code: 200, body: "{'name' : 'paul'}")
Typhoeus.stub('www.example.com').and_return(response)

Typhoeus.get("www.example.com") == response
#=> true

The queued request will hit the stub. You can also specify a regex to match urls.

response = Typhoeus::Response.new(code: 200, body: "{'name' : 'paul'}")
Typhoeus.stub(/example/).and_return(response)

Typhoeus.get("www.example.com") == response
#=> true

You may also specify an array for the stub to return sequentially.

Typhoeus.stub('www.example.com').and_return([response1, response2])

Typhoeus.get('www.example.com') == response1 #=> true
Typhoeus.get('www.example.com') == response2 #=> true

When testing make sure to clear your expectations or the stubs will persist between tests. The following can be included in your spec_helper.rb file to do this automatically.

RSpec.configure do |config|
  config.before :each do
    Typhoeus::Expectation.clear
  end
end

Timeouts

No exceptions are raised on HTTP timeouts. You can check whether a request timed out with the following method:

Typhoeus.get("www.example.com", timeout: 1).timed_out?

Timed out responses also have their success? method return false.

There are two different timeouts available: timeout and connecttimeout. timeout is the time limit for the entire request in seconds. connecttimeout is the time limit for just the connection phase, again in seconds.

There are two additional more fine grained options timeout_ms and connecttimeout_ms. These options offer millisecond precision but are not always available (for instance on linux if nosignal is not set to true).

When you pass a floating point timeout (or connecttimeout) Typhoeus will set timeout_ms for you if it has not been defined. The actual timeout values passed to curl will always be rounded up.

DNS timeouts of less than one second are not supported unless curl is compiled with an asynchronous resolver.

The default timeout is 0 (zero) which means curl never times out during transfer. The default connecttimeout is 300 seconds. A connecttimeout of 0 will also result in the default connecttimeout of 300 seconds.

Following Redirections

Use followlocation: true, eg:

Typhoeus.get("www.example.com", followlocation: true)

Basic Authentication

Typhoeus::Request.get("www.example.com", userpwd: "user:password")

Compression

Typhoeus.get("www.example.com", accept_encoding: "gzip")

The above has a different behavior than setting the header directly in the header hash, eg:

Typhoeus.get("www.example.com", headers: {"Accept-Encoding" => "gzip"})

Setting the header hash directly will not include the --compressed flag in the libcurl command and therefore libcurl will not decompress the response. If you want the --compressed flag to be added automatically, set :accept_encoding Typhoeus option.

Cookies

Typhoeus::Request.get("www.example.com", cookiefile: "/path/to/file", cookiejar: "/path/to/file")

Here, cookiefile is a file to read cookies from, and cookiejar is a file to write received cookies to. If you just want cookies enabled, you need to pass the same filename for both options.

Other CURL options

Are available and documented here

SSL

SSL comes built in to libcurl so it’s in Typhoeus as well. If you pass in a url with "https" it should just work assuming that you have your cert bundle in order and the server is verifiable. You must also have libcurl built with SSL support enabled. You can check that by doing this:

curl --version

Now, even if you have libcurl built with OpenSSL you may still have a messed up cert bundle or if you’re hitting a non-verifiable SSL server then you’ll have to disable peer verification to make SSL work. Like this:

Typhoeus.get("https://www.example.com", ssl_verifypeer: false)

If you are getting "SSL: certificate subject name does not match target host name" from curl (ex:- you are trying to access to b.c.host.com when the certificate subject is *.host.com). You can disable host verification. Like this:

# host checking enabled
Typhoeus.get("https://www.example.com", ssl_verifyhost: 2)
# host checking disabled
Typhoeus.get("https://www.example.com", ssl_verifyhost: 0)

Verbose debug output

It’s sometimes useful to see verbose output from curl. You can enable it on a per-request basis:

Typhoeus.get("http://example.com", verbose: true)

or globally:

Typhoeus::Config.verbose = true

Just remember that libcurl prints it’s debug output to the console (to STDERR), so you’ll need to run your scripts from the console to see it.

Default User Agent Header

In many cases, all HTTP requests made by an application require the same User-Agent header set. Instead of supplying it on a per-request basis by supplying a custom header, it is possible to override it for all requests using:

Typhoeus::Config.user_agent = "custom user agent"

Running the specs

Running the specs should be as easy as:

bundle install
bundle exec rake

Semantic Versioning

This project conforms to semver.

LICENSE

(The MIT License)

Copyright © 2009-2010 Paul Dix

Copyright © 2011-2012 David Balatero

Copyright © 2012-2016 Hans Hasselberg

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

ethon's People

Contributors

atsaloli avatar cedric-m avatar craiglittle avatar ejhayes avatar etipton avatar gnagno avatar hanshasselberg avatar happyhax0r avatar ifesdjeen avatar jarthod avatar jonaldomo avatar jwagner avatar kjarrigan avatar koppenheim avatar linrock avatar natejgreene avatar ojab avatar orien avatar periclestheo avatar pschuegr avatar richievos avatar skalee avatar spraints avatar theojulienne avatar travisp avatar v-kolesnikov avatar vjt avatar voxik avatar yuki24 avatar zapotek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ethon's Issues

Ethon's using curl_easy_escape but not free'ing the string

This is another cause of the ethon memory leaks. In queryable:

  def to_s
    query_pairs.map{ |pair|
      return pair if pair.is_a?(String)

      pair.map{ |e|
        escape && @easy ? @easy.escape(e.to_s) : e
      }.join("=")
    }.join('&')
  end

Easy escape:

def escape(value)
  Curl.easy_escape(handle, value, 0)
end

That returns a string, but the underlying string isn't ever escape. From http://curl.haxx.se/libcurl/c/curl_easy_escape.html

This function converts the given input string to an URL encoded string and returns that as a new allocated string.....
...
You must curl_free(3) the returned string when you're done with it.

I'm not 100% sure what the value of using curl's escape function is versus using ruby's CGI. But to stay with the curl one, I believe the FFI signature will need to be changed to return a pointer, which gets manually converted to a ruby string, and then have the original string freed (or something along those lines).

#copypostfields= should not escape NUL bytes

Currently, #set_form copies the body using #copypostfields= if body is given as a string.

However, for example from Faraday which builds all the body string including multipart requests, the body can contain NUL bytes and it is escaped as "\0". The problem goes even worse because we call #postfieldsize= with the original string.

We could fix https://github.com/typhoeus/ethon/blob/master/lib/ethon/easy/options.rb#L174 like this:

def copypostfields=(value)
  Curl.set_option(:copypostfields, value, handle) # no escaping here :)
end

Thanks!

Core dump

I'm getting a core dump with a really simple request using ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux]. I'm sure this is not a typhoeus (edit: sorry, I meant Ethon) problem, but you may have some ideas? It started as soon as I added the httppost.

$: << './lib'
require 'ethon'

e = Ethon::Easy.new
e.http_request(
  'http://<bucket-name>.s3.amazonaws.com',
  :post,
  { 
    :body => 'hi there',
    :httppost => 'multipart/form-data',
    :params => {

    } 
  } 
)
e.perform
puts e.response_body

setopt for off_t potentially broken on 32-bit systems

The off_t options (:max_send_speed_large and :max_recv_speed_large) consistently get set as :int (using value_for), this then gets passed on to set_option, which calls (I assume) easy_setopt_fixnum (which takes a long). I don't have a 32-bit system to test this on, but I think this is wrong, because an off_t is larger than a long on those systems.

Also, if you try to specify an off_t of 1<<62 or bigger (still a valid long!), set_option will try to call easy_setopt_bignum...

Memory leak?

Hey there.

I noticed some of my Rails processes were constantly growing, so I set out to try to figure out why. Long story short, I got bleak_house (https://github.com/evan/bleak_house) running my app and profiled several code paths. The only path which seemed to grow objects constantly was that using typhoeus/ethon.

Here is a bleak snippet of the object_count/line/object_type after 4000 requests:

4002 ~/.rvm/gems/ruby-1.8.7-p371/gems/ethon-0.5.7/lib/ethon/easy/http/actionable.rb:62:Hash
4002 ~/.rvm/gems/ruby-1.8.7-p371/gems/ethon-0.5.7/lib/ethon/easy/http/actionable.rb:62:Ethon::Easy::Form
4002 ~/.rvm/gems/ruby-1.8.7-p371/gems/ethon-0.5.7/lib/ethon/easy/form.rb:37:Proc
4002 ~/.rvm/gems/ruby-1.8.7-p371/gems/ethon-0.5.7/lib/ethon/easy/form.rb:27:__unknown__
4002 ~/.rvm/gems/ruby-1.8.7-p371/gems/ethon-0.5.7/lib/ethon/easy/form.rb:27:__scope__
4002 ~/.rvm/gems/ruby-1.8.7-p371/gems/ethon-0.5.7/lib/ethon/easy/form.rb:27:Array

I believe I successfully forced GC by doing about 10MB of string concatenation and running GC.start, and watched the total object count drop.

For reference the code path using typhoeus/ethon looks like:

response = Typhoeus::Request.get(url, :timeout_ms => 5000)
raise "Timeout!" if response.timed_out?
data = JSON.parse(response.body)

Any ideas?

Linux FDSet too big

FD_SETSIZE = ::Ethon::Libc.getdtablesize
layout :fds_bits, [:long, FD_SETSIZE / ::FFI::Type::LONG.size]

getdtablesize returns the maximum number of file descriptors. An fd_set has a bit for each of these. FFI::Type::LONG.size returns the number of bytes (not bits) for a long, so the resulting array is 8 times too big.

On a related note: according to man getdtablesize, it just does Process.getrlimit(Process::RLIMIT_NOFILE)[0] (so no need to import getdtablesize). Since rlimits can change, this function is not guaranteed to give you a struct definition that is big enough. You might be able to use the hard limit from getrlimit, but even that is no guarantee. See also http://etutorials.org/Programming/secure+programming/Chapter+3.+Input+Validation/3.13+Preventing+File+Descriptor+Overflows+When+Using+select/

implicit Easy#prepare

I want to suggest making Easy#prepare implicit, make it a private method and allow it to run right before the perform. If several runs required, it's possible to check if it was prepared already.

Different params encoding wit libcurl/7.19.7 & libcurl/7.24.0

I got a perculiar issue while using Ethon (via Typhoeus) and I was wondering if you can help me out on this one

First let me set the stage:

  • Using typhoeus (0.6.5)
  • Using vcr (2.5.0)
  • libcurl/7.24.0 (on dev machine)
  • libcurl/7.19.7 (on CI server)

We run rspecs that make extensive use of https://github.com/vcr/vcr to record various Typhoeus HTTP requests and replay them.

We record the cassettes on our local developer machines and replay them on our CI server.

Now we have a HTTP request that look like this: http://example.com/path?locale=en&auth_token=123

What happens when we record a cassette is the request/response gets saved under that URI.

However, when we run the tests on the CI servers VCR tries to find them with the following URI: http://example.com/path?locale=en&auth%5Ftoken=123

So there is something fishy going on. I think I've confined the problem to the different libcurl versions. Here's an example with Ethon.easy:

dev machine

>> require 'typhoeus'
false
>> easy = Ethon::Easy.new
#<Ethon::Easy:0x007fa09bb21770 @body_write_callback=#<Proc:0x007fa09bb21680@/Users/sascha/.rvm/gems/ruby-1.9.3-p392-turbo/gems/ethon-0.6.1/lib/ethon/easy/callbacks.rb:37>, @handle=#<FFI::AutoPointer address=0x007fa09e038600>, @header_write_callback=#<Proc:0x007fa09bb211a8@/Users/sascha/.rvm/gems/ruby-1.9.3-p392-turbo/gems/ethon-0.6.1/lib/ethon/easy/callbacks.rb:50>, @debug_callback=#<Proc:0x007fa09bb20ed8@/Users/sascha/.rvm/gems/ruby-1.9.3-p392-turbo/gems/ethon-0.6.1/lib/ethon/easy/callbacks.rb:64>, @response_body="", @response_headers="", @debug_info=#<Ethon::Easy::DebugInfo:0x007fa09bb20b40 @messages=[]>>
>> easy.http_request("www.example.com/path", :get, { params: {locale: 'en', auth_token: 123}, verbose: true })
nil
>> easy.perform
About to connect() to www.example.com port 80 (#0)
  Trying 93.184.216.119...
connected
Connected to www.example.com (93.184.216.119) port 80 (#0)
GET /path?locale=en&auth_token=123 HTTP/1.1
Host: www.example.com
Accept: */*

HTTP/1.1 404 Not Found
Accept-Ranges: bytes
Cache-Control: max-age=604800
Content-Type: text/html
Date: Thu, 12 Sep 2013 08:26:33 GMT
Etag: "3012602696"
Expires: Thu, 19 Sep 2013 08:26:33 GMT
Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT
Server: ECS (lax/286D)
X-Cache: HIT
x-ec-custom-error: 1
Content-Length: 1270

Connection #0 to host www.example.com left intact
:ok

CI server

>> require 'typhoeus'
 => true 
>> easy = Ethon::Easy.new
 => #<Ethon::Easy:0x0000000259ede8 @body_write_callback=#<Proc:0x0000000259e690@/usr/local/rvm/gems/ruby-1.9.3-p374@location-app-master/gems/ethon-0.6.1/lib/ethon/easy/callbacks.rb:37>, @handle=#<FFI::AutoPointer address=0x00000002ed42c0>, @header_write_callback=#<Proc:0x000000025a7060@/usr/local/rvm/gems/ruby-1.9.3-p374@location-app-master/gems/ethon-0.6.1/lib/ethon/easy/callbacks.rb:50>, @debug_callback=#<Proc:0x000000025a6778@/usr/local/rvm/gems/ruby-1.9.3-p374@location-app-master/gems/ethon-0.6.1/lib/ethon/easy/callbacks.rb:64>, @response_body="", @response_headers="", @debug_info=#<Ethon::Easy::DebugInfo:0x000000025a5c38 @messages=[]>> 
>> easy.http_request("www.example.com/path", :get, { params: {locale: 'en', auth_token: 123}, verbose: true })
 => nil 
>> easy.perform
About to connect() to www.example.com port 80 (#0)
  Trying 93.184.216.119... connected
Connected to www.example.com (93.184.216.119) port 80 (#0)
GET /path?locale=en&auth%5Ftoken=123 HTTP/1.1
Host: www.example.com
Accept: */*

HTTP/1.1 404 Not Found
Accept-Ranges: bytes
Cache-Control: max-age=604800
Content-Type: text/html
Date: Thu, 12 Sep 2013 08:28:41 GMT
Etag: "3012602696"
Expires: Thu, 19 Sep 2013 08:28:41 GMT
Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT
Server: ECS (lax/286D)
X-Cache: HIT
x-ec-custom-error: 1
Content-Length: 1270

Connection #0 to host www.example.com left intact
 => :ok 

You see the difference clearly here:

GET /path?locale=en&auth_token=123 HTTP/1.1
vs:
GET /path?locale=en&auth%5Ftoken=123 HTTP/1.1

I'm sure I'm doing something wrong. Can anyone help me out on this one? Thanks!

0.5.12: undefined method `each' for nil:NilClass - response_callbacks.rb:43

A NoMethodError occurred in search#index:

undefined method `each' for nil:NilClass
ethon (0.5.12) lib/ethon/easy/response_callbacks.rb:43:in `complete'

-------------------------------
Request:
-------------------------------

  * URL       : https://level.travel/search/Moscow-RU-to-Any-EG-departure-11.05.2013-for-13-nights-2-adults-0-kids-1..5-stars
  * IP address: 82.97.205.46
  * Parameters: {"controller"=>"search", "action"=>"index", "query"=>"Moscow-RU-to-Any-EG-departure-11.05.2013-for-13-nights-2-adults-0-kids-1..5-stars"}
  * Rails root: /home/deployer/apps/leveltravel/current
  * Timestamp : 2013-04-23 16:24:41 +0400

-------------------------------
Backtrace:
-------------------------------

  ethon (0.5.12) lib/ethon/easy/response_callbacks.rb:43:in `complete'
  ethon (0.5.12) lib/ethon/easy/operations.rb:24:in `perform'
  typhoeus (0.5.4) lib/typhoeus/request/operations.rb:26:in `run'
  typhoeus (0.5.4) lib/typhoeus/request/block_connection.rb:31:in `run'
  typhoeus (0.5.4) lib/typhoeus/request/stubbable.rb:23:in `run'
  typhoeus (0.5.4) lib/typhoeus/request/before.rb:26:in `run'
  typhoeus (0.5.4) lib/typhoeus/request/actions.rb:22:in `get'
  app/apis/weather_bug.rb:27:in `live_request'
  app/apis/weather_bug.rb:19:in `live'
  app/controllers/search_controller.rb:45:in `index'
  actionpack (3.2.11) lib/action_controller/metal/implicit_render.rb:4:in `send_action'
  actionpack (3.2.11) lib/abstract_controller/base.rb:167:in `process_action'
  actionpack (3.2.11) lib/action_controller/metal/rendering.rb:10:in `process_action'
  actionpack (3.2.11) lib/abstract_controller/callbacks.rb:18:in `block in process_action'
  activesupport (3.2.11) lib/active_support/callbacks.rb:513:in `_run__906001855511687055__process_action__4226426739626338382__callbacks'
  activesupport (3.2.11) lib/active_support/callbacks.rb:405:in `__run_callback'
  activesupport (3.2.11) lib/active_support/callbacks.rb:385:in `_run_process_action_callbacks'
  activesupport (3.2.11) lib/active_support/callbacks.rb:81:in `run_callbacks'
  actionpack (3.2.11) lib/abstract_controller/callbacks.rb:17:in `process_action'
  actionpack (3.2.11) lib/action_controller/metal/rescue.rb:29:in `process_action'
  actionpack (3.2.11) lib/action_controller/metal/instrumentation.rb:30:in `block in process_action'
  activesupport (3.2.11) lib/active_support/notifications.rb:123:in `block in instrument'
  activesupport (3.2.11) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
  activesupport (3.2.11) lib/active_support/notifications.rb:123:in `instrument'
  actionpack (3.2.11) lib/action_controller/metal/instrumentation.rb:29:in `process_action'
  actionpack (3.2.11) lib/action_controller/metal/params_wrapper.rb:207:in `process_action'
  activerecord (3.2.11) lib/active_record/railties/controller_runtime.rb:18:in `process_action'
  actionpack (3.2.11) lib/abstract_controller/base.rb:121:in `process'
  actionpack (3.2.11) lib/abstract_controller/rendering.rb:45:in `process'
  actionpack (3.2.11) lib/action_controller/metal.rb:203:in `dispatch'
  actionpack (3.2.11) lib/action_controller/metal/rack_delegation.rb:14:in `dispatch'
  actionpack (3.2.11) lib/action_controller/metal.rb:246:in `block in action'
  actionpack (3.2.11) lib/action_dispatch/routing/route_set.rb:73:in `call'
  actionpack (3.2.11) lib/action_dispatch/routing/route_set.rb:73:in `dispatch'
  actionpack (3.2.11) lib/action_dispatch/routing/route_set.rb:36:in `call'
  journey (1.0.4) lib/journey/router.rb:68:in `block in call'
  journey (1.0.4) lib/journey/router.rb:56:in `each'
  journey (1.0.4) lib/journey/router.rb:56:in `call'
  actionpack (3.2.11) lib/action_dispatch/routing/route_set.rb:601:in `call'
  exception_notification (3.0.1) lib/exception_notifier.rb:41:in `call'
  warden (1.2.1) lib/warden/manager.rb:35:in `block in call'
  warden (1.2.1) lib/warden/manager.rb:34:in `catch'
  warden (1.2.1) lib/warden/manager.rb:34:in `call'
  actionpack (3.2.11) lib/action_dispatch/middleware/best_standards_support.rb:17:in `call'
  rack (1.4.5) lib/rack/etag.rb:23:in `call'
  rack (1.4.5) lib/rack/conditionalget.rb:25:in `call'
  actionpack (3.2.11) lib/action_dispatch/middleware/head.rb:14:in `call'
  actionpack (3.2.11) lib/action_dispatch/middleware/params_parser.rb:21:in `call'
  actionpack (3.2.11) lib/action_dispatch/middleware/flash.rb:242:in `call'
  rack (1.4.5) lib/rack/session/abstract/id.rb:210:in `context'
  rack (1.4.5) lib/rack/session/abstract/id.rb:205:in `call'
  actionpack (3.2.11) lib/action_dispatch/middleware/cookies.rb:341:in `call'
  activerecord (3.2.11) lib/active_record/query_cache.rb:64:in `call'
  activerecord (3.2.11) lib/active_record/connection_adapters/abstract/connection_pool.rb:479:in `call'
  actionpack (3.2.11) lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
  activesupport (3.2.11) lib/active_support/callbacks.rb:405:in `_run__4493510109563642895__call__1195701628514063054__callbacks'
  activesupport (3.2.11) lib/active_support/callbacks.rb:405:in `__run_callback'
  activesupport (3.2.11) lib/active_support/callbacks.rb:385:in `_run_call_callbacks'
  activesupport (3.2.11) lib/active_support/callbacks.rb:81:in `run_callbacks'
  actionpack (3.2.11) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
  rack (1.4.5) lib/rack/sendfile.rb:102:in `call'
  actionpack (3.2.11) lib/action_dispatch/middleware/remote_ip.rb:31:in `call'
  actionpack (3.2.11) lib/action_dispatch/middleware/debug_exceptions.rb:16:in `call'
  actionpack (3.2.11) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'
  railties (3.2.11) lib/rails/rack/logger.rb:32:in `call_app'
  railties (3.2.11) lib/rails/rack/logger.rb:18:in `call'
  actionpack (3.2.11) lib/action_dispatch/middleware/request_id.rb:22:in `call'
  rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'
  rack (1.4.5) lib/rack/runtime.rb:17:in `call'
  rack (1.4.5) lib/rack/lock.rb:15:in `call'
  rack-cache (1.2) lib/rack/cache/context.rb:136:in `forward'
  rack-cache (1.2) lib/rack/cache/context.rb:245:in `fetch'
  rack-cache (1.2) lib/rack/cache/context.rb:185:in `lookup'
  rack-cache (1.2) lib/rack/cache/context.rb:66:in `call!'
  rack-cache (1.2) lib/rack/cache/context.rb:51:in `call'
  railties (3.2.11) lib/rails/engine.rb:479:in `call'
  railties (3.2.11) lib/rails/application.rb:223:in `call'
  railties (3.2.11) lib/rails/railtie/configurable.rb:30:in `method_missing'
  unicorn (4.6.2) lib/unicorn/http_server.rb:552:in `process_client'
  unicorn (4.6.2) lib/unicorn/http_server.rb:632:in `worker_loop'
  unicorn (4.6.2) lib/unicorn/http_server.rb:500:in `spawn_missing_workers'
  unicorn (4.6.2) lib/unicorn/http_server.rb:142:in `start'
  unicorn (4.6.2) bin/unicorn:126:in `'
  /home/deployer/apps/leveltravel/shared/bundle/ruby/1.9.1/bin/unicorn:23:in `load'
  /home/deployer/apps/leveltravel/shared/bundle/ruby/1.9.1/bin/unicorn:23:in `'

Support HTTP PURGE verb (and maybe other custom ones)

I'm not sure if this belongs in Typhoeus or Ethon, but Typhoeus used to support HTTP PURGE which is used by HTTP caches like Varnish to signal that entries should be removed.

The error I get now is: uninitialized constant Ethon::Easy::Http::Purge.

If I add this, the request works (it just copies Ethon::Easy::Http::Get):

module Ethon
  class Easy
    module Http

      # This class knows everything about making GET requests.
      class Purge
        include Ethon::Easy::Http::Actionable
        include Ethon::Easy::Http::Postable

        # Setup easy to make a GET request.
        #
        # @example Setup.
        #   get.set_params(easy)
        #
        # @param [ Easy ] easy The easy to setup.
        def setup(easy)
          super
          easy.customrequest = "GET" unless form.empty?
        end
      end
    end
  end
end

But maybe there's a way to allow for custom HTTP verbs?

Bump FFI version in gemspec

ffi is at version 1.1.5 but the current ethon depends on ffi ~> 1.0.11. This is causing issues for me when updating typhoeus to 0.5.1.

Can you bump the ffi dependency version?

Can't start applications

Ethon 0.4.4 prevents application from starting up. 0.4.2 works fine.

Error message:

/Users/myusr/.rvm/gems/ruby-1.9.2-p290/gems/typhoeus-    0.5.0.alpha/lib/typhoeus/responses/informations.rb:9:in `<module:Informations>': uninitialized     constant Ethon::Easies (NameError)

Full trace: https://gist.github.com/3722049

Frozen after "ETHON: Libcurl initialized" from Typheous

My server freezes right after that line is output. I'm doing a simple request of the form

Typhoeus.get(request_url,
    headers: headers.merge({'Content-Type' => 'application/json'})
  )

This happens every time. I'm using Ethon 0.5.3 and curl "7.30.0 (x86_64-apple-darwin13.0) libcurl/7.30.0 SecureTransport zlib/1.2.5".

Commenting out those lines solves my freezing issue. I even tried putting a timeout around those lines of code, but that did nothing to help.

Any help would be greatly appreciated. I know that 7.31 is out now, so I'm going to try updating to that.

include spec directory in gem itself

please include the spec file in the gem itself like most gems do. Currently we have to get it from github tag. It would be better to get it directly from the gem itself.

url encoding for arrays

Hi,

recently I had some problems with your encoding of arrays in the query params.
While adding an index in the brackets is a nice idea to make sure the array will be sorted correctly. But most it seems to break with the W3C suggestion (http://www.w3.org/TR/url/#collect-the-url-parameters) and implementation in some frameworks.

An option for setting the array encoding would be nice. So you could enable or disable this feature.

Best regards
Steffen

P.S.: Thanks for building Typhoeus.

Segmentation fault with rspec

I have the following output at the end of rspec with https://github.com/wpscanteam/wpscan :

/home/erwan/.rvm/gems/ruby-1.9.3-p362/gems/ethon-0.5.10/lib/ethon/multi.rb:26: [BUG] Segmentation fault
ruby 1.9.3p362 (2012-12-25 revision 38607) [x86_64-linux]

-- Control frame information -----------------------------------------------
c:0005 p:---- s:0012 b:0012 l:000011 d:000011 CFUNC  :multi_cleanup
c:0004 p:0024 s:0008 b:0008 l:0001c0 d:000007 BLOCK  /home/erwan/.rvm/gems/ruby-1.9.3-p362/gems/ethon-0.5.10/lib/ethon/multi.rb:26
c:0003 p:---- s:0006 b:0006 l:000005 d:000005 FINISH
c:0002 p:---- s:0004 b:0004 l:000003 d:000003 CFUNC  :call
c:0001 p:0000 s:0002 b:0002 l:001698 d:001698 TOP   

-- Ruby level backtrace information ----------------------------------------
/home/erwan/.rvm/gems/ruby-1.9.3-p362/bin/rspec:0:in `call'
/home/erwan/.rvm/gems/ruby-1.9.3-p362/gems/ethon-0.5.10/lib/ethon/multi.rb:26:in `block in finalizer'
/home/erwan/.rvm/gems/ruby-1.9.3-p362/gems/ethon-0.5.10/lib/ethon/multi.rb:26:in `multi_cleanup'

-- C level backtrace information -------------------------------------------
Segmentation fault
$ curl --version
curl 7.29.0 (x86_64-unknown-linux-gnu) libcurl/7.29.0 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.15 libssh2/1.2.6
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smtp smtps telnet tftp 
Features: IDN IPv6 Largefile NTLM NTLM_WB SSL libz

License missing from gemspec

RubyGems.org doesn't report a license for your gem. This is because it is not specified in the gemspec of your last release.

via e.g.

spec.license = 'MIT'
# or
spec.licenses = ['MIT', 'GPL-2']

Including a license in your gemspec is an easy way for rubygems.org and other tools to check how your gem is licensed. As you can imagine, scanning your repository for a LICENSE file or parsing the README, and then attempting to identify the license or licenses is much more difficult and more error prone. So, even for projects that already specify a license, including a license in your gemspec is a good practice. See, for example, how rubygems.org uses the gemspec to display the rails gem license.

There is even a License Finder gem to help companies/individuals ensure all gems they use meet their licensing needs. This tool depends on license information being available in the gemspec. This is an important enough issue that even Bundler now generates gems with a default 'MIT' license.

I hope you'll consider specifying a license in your gemspec. If not, please just close the issue with a nice message. In either case, I'll follow up. Thanks for your time!

Appendix:

If you need help choosing a license (sorry, I haven't checked your readme or looked for a license file), GitHub has created a license picker tool. Code without a license specified defaults to 'All rights reserved'-- denying others all rights to use of the code.
Here's a list of the license names I've found and their frequencies

p.s. In case you're wondering how I found you and why I made this issue, it's because I'm collecting stats on gems (I was originally looking for download data) and decided to collect license metadata,too, and make issues for gemspecs not specifying a license as a public service :). See the previous link or my blog post about this project for more information.

Ethon's form freeing uses a method that doesn't exist

I'm currently working on fixing the memory leaks in Ethon, and I stumbled across what seems to be a bug in form.

If look look at the form finalizer: https://github.com/richievos/ethon/blob/master/lib/ethon/easy/form.rb#L37

it is using Curl.formfree:

proc { Curl.formfree(form.first) if form.multipart? }

But that method isn't defined on the Curl object. I'm pretty sure functions.rb needs to have:

base.attach_function :formfree,               :curl_formfree,            [:pointer],                     :void

Add Rubinius support

I have a proposal. I know it's a big goal but I think we as a community reached a point when we should now take other rubies as first class citizens in our ecosystem and start supporting them globally for us soon to live in a world where we could just switch rubies beneath and our applications will still work without a single change in our code.

I'm getting some FFI related errors trying to run Ethon's tests under current rbx-head. I'm not particularly good at this FFI and C extension stuff, I know that Rubinius has it's own implementation of FFI, but I'm not sure how it plays with ffi gem, the API is little bit different. I would really love to see if we could come closer to supporting Rubinius in Ethon (therefore in Typhoeus too), maybe just track down those bugs and inconsistencies and report them to more appropriate places (I can't tell if it is an Ethon's issue, FFI's or even on Rubinius side really). I think it would be nice if it is currently even possible.

Thanks for any update on this. I'm really curious.

Problem uploading a file

Hello,

I've been using ethon in order to upload an XML file to Amazon's MWS, and I haven't been able to achieve a success yet.

When I use Ethon::Easy's readdata option, I can't get it to work, neither with a string containing the file content, or with a file.

So, is it possible to successfully set the value for CURLOPT_INFILE/CURLOPT_READDATA with this gem?

Thanks!

FFI Problem

Hi Guys,

I get the following error when I add 'typhoeus' to my Gemfile.

/Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/ffi-1.9.2/lib/ffi.rb:14:in `require': dlopen(/Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/ffi-1.9.2/lib/ffi_c.bundle, 9): Library not loaded: /Users/headius/.rvm/rubies/ruby-head/lib/libruby.2.1.0.dylib (LoadError)
  Referenced from: /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/ffi-1.9.2/lib/ffi_c.bundle
  Reason: image not found - /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/ffi-1.9.2/lib/ffi_c.bundle
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/ffi-1.9.2/lib/ffi.rb:14:in `rescue in <top (required)>'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/ffi-1.9.2/lib/ffi.rb:3:in `<top (required)>'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/ethon-0.6.1/lib/ethon.rb:2:in `require'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/ethon-0.6.1/lib/ethon.rb:2:in `<top (required)>'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/typhoeus-0.6.5/lib/typhoeus.rb:2:in `require'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/typhoeus-0.6.5/lib/typhoeus.rb:2:in `<top (required)>'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/bundler-1.3.5/lib/bundler/runtime.rb:72:in `require'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/bundler-1.3.5/lib/bundler/runtime.rb:72:in `block (2 levels) in require'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/bundler-1.3.5/lib/bundler/runtime.rb:70:in `each'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/bundler-1.3.5/lib/bundler/runtime.rb:70:in `block in require'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/bundler-1.3.5/lib/bundler/runtime.rb:59:in `each'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/bundler-1.3.5/lib/bundler/runtime.rb:59:in `require'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/bundler-1.3.5/lib/bundler.rb:132:in `require'
    from /Users/govinda/workspace/test/mytest/config/application.rb:12:in `<top (required)>'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/railties-4.0.0/lib/rails/commands.rb:76:in `require'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/railties-4.0.0/lib/rails/commands.rb:76:in `block in <top (required)>'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/railties-4.0.0/lib/rails/commands.rb:73:in `tap'
    from /Users/govinda/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/railties-4.0.0/lib/rails/commands.rb:73:in `<top (required)>'
    from bin/rails:4:in `require'
    from bin/rails:4:in `<main>'

Without the gem everything works just fine. Is my setup missing anything vital?

I have the following environment with Ruby 2.0.0p247, Rubygems 2.0.3 ...

My Gemfile.lock looks like this:

GEM
  remote: https://rubygems.org/
  specs:
    actionmailer (4.0.0)
      actionpack (= 4.0.0)
      mail (~> 2.5.3)
    actionpack (4.0.0)
      activesupport (= 4.0.0)
      builder (~> 3.1.0)
      erubis (~> 2.7.0)
      rack (~> 1.5.2)
      rack-test (~> 0.6.2)
    activemodel (4.0.0)
      activesupport (= 4.0.0)
      builder (~> 3.1.0)
    activerecord (4.0.0)
      activemodel (= 4.0.0)
      activerecord-deprecated_finders (~> 1.0.2)
      activesupport (= 4.0.0)
      arel (~> 4.0.0)
    activerecord-deprecated_finders (1.0.3)
    activesupport (4.0.0)
      i18n (~> 0.6, >= 0.6.4)
      minitest (~> 4.2)
      multi_json (~> 1.3)
      thread_safe (~> 0.1)
      tzinfo (~> 0.3.37)
    arel (4.0.1)
    atomic (1.1.14)
    builder (3.1.4)
    coffee-rails (4.0.1)
      coffee-script (>= 2.2.0)
      railties (>= 4.0.0, < 5.0)
    coffee-script (2.2.0)
      coffee-script-source
      execjs
    coffee-script-source (1.6.3)
    erubis (2.7.0)
    ethon (0.6.1)
      ffi (>= 1.3.0)
      mime-types (~> 1.18)
    execjs (2.0.2)
    ffi (1.9.2)
    hike (1.2.3)
    i18n (0.6.5)
    jbuilder (1.5.2)
      activesupport (>= 3.0.0)
      multi_json (>= 1.2.0)
    jquery-rails (3.0.4)
      railties (>= 3.0, < 5.0)
      thor (>= 0.14, < 2.0)
    json (1.8.1)
    mail (2.5.4)
      mime-types (~> 1.16)
      treetop (~> 1.4.8)
    mime-types (1.25)
    minitest (4.7.5)
    multi_json (1.8.2)
    mysql2 (0.3.13)
    polyglot (0.3.3)
    rack (1.5.2)
    rack-test (0.6.2)
      rack (>= 1.0)
    rails (4.0.0)
      actionmailer (= 4.0.0)
      actionpack (= 4.0.0)
      activerecord (= 4.0.0)
      activesupport (= 4.0.0)
      bundler (>= 1.3.0, < 2.0)
      railties (= 4.0.0)
      sprockets-rails (~> 2.0.0)
    railties (4.0.0)
      actionpack (= 4.0.0)
      activesupport (= 4.0.0)
      rake (>= 0.8.7)
      thor (>= 0.18.1, < 2.0)
    rake (10.1.0)
    rdoc (3.12.2)
      json (~> 1.4)
    sass (3.2.12)
    sass-rails (4.0.1)
      railties (>= 4.0.0, < 5.0)
      sass (>= 3.1.10)
      sprockets-rails (~> 2.0.0)
    sdoc (0.3.20)
      json (>= 1.1.3)
      rdoc (~> 3.10)
    sprockets (2.10.0)
      hike (~> 1.2)
      multi_json (~> 1.0)
      rack (~> 1.0)
      tilt (~> 1.1, != 1.3.0)
    sprockets-rails (2.0.1)
      actionpack (>= 3.0)
      activesupport (>= 3.0)
      sprockets (~> 2.8)
    thor (0.18.1)
    thread_safe (0.1.3)
      atomic
    tilt (1.4.1)
    treetop (1.4.15)
      polyglot
      polyglot (>= 0.3.1)
    turbolinks (1.3.0)
      coffee-rails
    typhoeus (0.6.5)
      ethon (~> 0.6.1)
    tzinfo (0.3.38)
    uglifier (2.3.0)
      execjs (>= 0.3.0)
      json (>= 1.8.0)

PLATFORMS
  ruby

DEPENDENCIES
  coffee-rails (~> 4.0.0)
  jbuilder (~> 1.2)
  jquery-rails
  mysql2
  rails (= 4.0.0)
  sass-rails (~> 4.0.0)
  sdoc
  turbolinks
  typhoeus
  uglifier (>= 1.3.0)

getdtablesize not found Windows 7 x64

Summary

Just requiring typhoeus on windows causes it to break. The issue is due to: 477bee8

Perhaps only defining the constant for non-windows makes sense?

Details

Windows 7 x64

ruby 1.9.3p125 (2012-02-16) [i386-mingw32]

curl 7.21.1 (i686-pc-mingw32) libcurl/7.21.1 OpenSSL/0.9.8r zlib/1.2.3
Protocols: dict file ftp ftps http https imap imaps ldap ldaps pop3 pop3s rtsp smtp smtps telnet tftp
Features: Largefile NTLM SSL SSPI libz

Error

$ irb --noreadline
irb(main):001:0> require 'typhoeus'
FFI::NotFoundError: Function 'getdtablesize' not found in [msvcrt.dll]
        from c:/RailsInstaller/Ruby1.9.3/lib/ruby/gems/1.9.1/gems/ffi-1.0.11/lib/ffi/library.rb:249:in `attach_function'
        from c:/RailsInstaller/Ruby1.9.3/lib/ruby/gems/1.9.1/gems/ethon-0.5.3/lib/ethon/libc.rb:5:in `<module:Libc>'
        from c:/RailsInstaller/Ruby1.9.3/lib/ruby/gems/1.9.1/gems/ethon-0.5.3/lib/ethon/libc.rb:2:in `<module:Ethon>'
        from c:/RailsInstaller/Ruby1.9.3/lib/ruby/gems/1.9.1/gems/ethon-0.5.3/lib/ethon/libc.rb:1:in `<top (required)>'
        from c:/RailsInstaller/Ruby1.9.3/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require'
        from c:/RailsInstaller/Ruby1.9.3/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require'
        from c:/RailsInstaller/Ruby1.9.3/lib/ruby/gems/1.9.1/gems/ethon-0.5.3/lib/ethon.rb:7:in `<top (required)>'
        from c:/RailsInstaller/Ruby1.9.3/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require'
        from c:/RailsInstaller/Ruby1.9.3/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require'
        from c:/RailsInstaller/Ruby1.9.3/lib/ruby/gems/1.9.1/gems/typhoeus-0.5.1/lib/typhoeus.rb:2:in `<top (required)>'
        from c:/RailsInstaller/Ruby1.9.3/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:59:in `require'
        from c:/RailsInstaller/Ruby1.9.3/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:59:in `rescue in require'
        from c:/RailsInstaller/Ruby1.9.3/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:35:in `require'
        from (irb):1
        from c:/RailsInstaller/Ruby1.9.3/bin/irb:12:in `<main>'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.