em-http-request-samesite 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +0 -0
  5. data/.travis.yml +7 -0
  6. data/Changelog.md +68 -0
  7. data/Gemfile +14 -0
  8. data/README.md +63 -0
  9. data/Rakefile +10 -0
  10. data/benchmarks/clients.rb +170 -0
  11. data/benchmarks/em-excon.rb +87 -0
  12. data/benchmarks/em-profile.gif +0 -0
  13. data/benchmarks/em-profile.txt +65 -0
  14. data/benchmarks/server.rb +48 -0
  15. data/em-http-request.gemspec +32 -0
  16. data/examples/.gitignore +1 -0
  17. data/examples/digest_auth/client.rb +25 -0
  18. data/examples/digest_auth/server.rb +28 -0
  19. data/examples/fetch.rb +30 -0
  20. data/examples/fibered-http.rb +51 -0
  21. data/examples/multi.rb +25 -0
  22. data/examples/oauth-tweet.rb +35 -0
  23. data/examples/socks5.rb +23 -0
  24. data/lib/em-http-request.rb +1 -0
  25. data/lib/em-http.rb +20 -0
  26. data/lib/em-http/client.rb +341 -0
  27. data/lib/em-http/core_ext/bytesize.rb +6 -0
  28. data/lib/em-http/decoders.rb +252 -0
  29. data/lib/em-http/http_client_options.rb +49 -0
  30. data/lib/em-http/http_connection.rb +321 -0
  31. data/lib/em-http/http_connection_options.rb +70 -0
  32. data/lib/em-http/http_encoding.rb +149 -0
  33. data/lib/em-http/http_header.rb +83 -0
  34. data/lib/em-http/http_status_codes.rb +57 -0
  35. data/lib/em-http/middleware/digest_auth.rb +112 -0
  36. data/lib/em-http/middleware/json_response.rb +15 -0
  37. data/lib/em-http/middleware/oauth.rb +40 -0
  38. data/lib/em-http/middleware/oauth2.rb +28 -0
  39. data/lib/em-http/multi.rb +57 -0
  40. data/lib/em-http/request.rb +23 -0
  41. data/lib/em-http/version.rb +5 -0
  42. data/lib/em/io_streamer.rb +49 -0
  43. data/spec/client_fiber_spec.rb +23 -0
  44. data/spec/client_spec.rb +1000 -0
  45. data/spec/digest_auth_spec.rb +48 -0
  46. data/spec/dns_spec.rb +41 -0
  47. data/spec/encoding_spec.rb +49 -0
  48. data/spec/external_spec.rb +150 -0
  49. data/spec/fixtures/google.ca +16 -0
  50. data/spec/fixtures/gzip-sample.gz +0 -0
  51. data/spec/gzip_spec.rb +91 -0
  52. data/spec/helper.rb +31 -0
  53. data/spec/http_proxy_spec.rb +268 -0
  54. data/spec/middleware/oauth2_spec.rb +15 -0
  55. data/spec/middleware_spec.rb +143 -0
  56. data/spec/multi_spec.rb +104 -0
  57. data/spec/pipelining_spec.rb +66 -0
  58. data/spec/redirect_spec.rb +430 -0
  59. data/spec/socksify_proxy_spec.rb +60 -0
  60. data/spec/spec_helper.rb +25 -0
  61. data/spec/ssl_spec.rb +71 -0
  62. data/spec/stallion.rb +334 -0
  63. data/spec/stub_server.rb +45 -0
  64. metadata +265 -0
Binary file
@@ -0,0 +1,65 @@
1
+ Total: 143 samples
2
+ 67 46.9% 46.9% 109 76.2% Kernel#gem_original_require
3
+ 17 11.9% 58.7% 17 11.9% garbage_collector
4
+ 12 8.4% 67.1% 12 8.4% EventMachine.connect_server
5
+ 11 7.7% 74.8% 11 7.7% Dir.[]
6
+ 11 7.7% 82.5% 11 7.7% File.file?
7
+ 4 2.8% 85.3% 4 2.8% Kernel#eval
8
+ 3 2.1% 87.4% 16 11.2% Enumerable#find
9
+ 3 2.1% 89.5% 3 2.1% Regexp#initialize
10
+ 2 1.4% 90.9% 16 11.2% EventMachine.run_machine
11
+ 2 1.4% 92.3% 2 1.4% Regexp#match
12
+ 1 0.7% 93.0% 1 0.7% FFI::DynamicLibrary.open
13
+ 1 0.7% 93.7% 1 0.7% Hash#values
14
+ 1 0.7% 94.4% 7 4.9% Kernel#load
15
+ 1 0.7% 95.1% 3 2.1% MIME::Type#initialize
16
+ 1 0.7% 95.8% 1 0.7% MatchData#captures
17
+ 1 0.7% 96.5% 13 9.1% Module#bind_connect
18
+ 1 0.7% 97.2% 17 11.9% Module#searcher
19
+ 1 0.7% 97.9% 1 0.7% String#downcase
20
+ 1 0.7% 98.6% 1 0.7% String#scan
21
+ 1 0.7% 99.3% 1 0.7% String#strip
22
+ 1 0.7% 100.0% 1 0.7% TCPSocket#initialize
23
+ 0 0.0% 100.0% 1 0.7% Array#map
24
+ 0 0.0% 100.0% 14 9.8% Array#reverse_each
25
+ 0 0.0% 100.0% 14 9.8% Class#from_gems_in
26
+ 0 0.0% 100.0% 14 9.8% Class#from_installed_gems
27
+ 0 0.0% 100.0% 14 9.8% Class#load
28
+ 0 0.0% 100.0% 1 0.7% Class#parse
29
+ 0 0.0% 100.0% 1 0.7% Class#simplified
30
+ 0 0.0% 100.0% 6 4.2% Enumerable#each_with_index
31
+ 0 0.0% 100.0% 1 0.7% Excon::Connection#connect
32
+ 0 0.0% 100.0% 1 0.7% Excon::Connection#request
33
+ 0 0.0% 100.0% 1 0.7% Excon::Connection#socket
34
+ 0 0.0% 100.0% 1 0.7% FFI::Library#ffi_lib
35
+ 0 0.0% 100.0% 10 7.0% Gem::GemPathSearcher#find
36
+ 0 0.0% 100.0% 1 0.7% Gem::GemPathSearcher#find_active
37
+ 0 0.0% 100.0% 16 11.2% Gem::GemPathSearcher#init_gemspecs
38
+ 0 0.0% 100.0% 16 11.2% Gem::GemPathSearcher#initialize
39
+ 0 0.0% 100.0% 11 7.7% Gem::GemPathSearcher#matching_file?
40
+ 0 0.0% 100.0% 11 7.7% Gem::GemPathSearcher#matching_files
41
+ 0 0.0% 100.0% 14 9.8% Gem::SourceIndex#load_gems_in
42
+ 0 0.0% 100.0% 14 9.8% Gem::SourceIndex#refresh!
43
+ 0 0.0% 100.0% 1 0.7% Gem::SourceIndex#search
44
+ 0 0.0% 100.0% 1 0.7% HttpOptions#initialize
45
+ 0 0.0% 100.0% 1 0.7% HttpOptions#set_uri
46
+ 0 0.0% 100.0% 14 9.8% Integer#times
47
+ 0 0.0% 100.0% 1 0.7% Kernel#loop
48
+ 0 0.0% 100.0% 109 76.2% Kernel#require
49
+ 0 0.0% 100.0% 1 0.7% Module#activate
50
+ 0 0.0% 100.0% 13 9.1% Module#connect
51
+ 0 0.0% 100.0% 1 0.7% Module#get
52
+ 0 0.0% 100.0% 2 1.4% Module#load_full_rubygems_library
53
+ 0 0.0% 100.0% 5 3.5% Module#loaded_path?
54
+ 0 0.0% 100.0% 16 11.2% Module#meter
55
+ 0 0.0% 100.0% 16 11.2% Module#run
56
+ 0 0.0% 100.0% 16 11.2% Module#source_index
57
+ 0 0.0% 100.0% 30 21.0% Module#try_activate
58
+ 0 0.0% 100.0% 17 11.9% Object#with_server
59
+ 0 0.0% 100.0% 2 1.4% Regexp.union
60
+ 0 0.0% 100.0% 1 0.7% TCPSocket.open
61
+ 0 0.0% 100.0% 16 11.2% Tach::Meter#initialize
62
+ 0 0.0% 100.0% 16 11.2% Tach::Meter#run_tach
63
+ 0 0.0% 100.0% 1 0.7% URI::Parser#initialize
64
+ 0 0.0% 100.0% 1 0.7% URI::Parser#initialize_regexp
65
+ 0 0.0% 100.0% 7 4.9% YAML::EngineManager#yamler=
@@ -0,0 +1,48 @@
1
+ require 'excon'
2
+ require 'httparty'
3
+ require 'net/http'
4
+ require 'open-uri'
5
+ require 'rest_client'
6
+ require 'tach'
7
+ require 'typhoeus'
8
+ require 'sinatra/base'
9
+ require 'streamly_ffi'
10
+ require 'curb'
11
+
12
+ require File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib', 'em-http')
13
+
14
+ module Benchmark
15
+ class Server < Sinatra::Base
16
+
17
+ def self.run
18
+ Rack::Handler::WEBrick.run(
19
+ Benchmark::Server.new,
20
+ :Port => 9292,
21
+ :AccessLog => [],
22
+ :Logger => WEBrick::Log.new(nil, WEBrick::Log::ERROR)
23
+ )
24
+ end
25
+
26
+ get '/data/:amount' do |amount|
27
+ 'x' * amount.to_i
28
+ end
29
+
30
+ end
31
+ end
32
+
33
+ def with_server(&block)
34
+ pid = Process.fork do
35
+ Benchmark::Server.run
36
+ end
37
+ loop do
38
+ sleep(1)
39
+ begin
40
+ #Excon.get('http://localhost:9292/api/foo')
41
+ break
42
+ rescue
43
+ end
44
+ end
45
+ yield
46
+ ensure
47
+ Process.kill(9, pid)
48
+ end
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'em-http/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'em-http-request-samesite'
7
+ s.version = EventMachine::HttpRequest::VERSION
8
+
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Ilya Grigorik"]
11
+ s.summary = 'EventMachine based, async HTTP Request client'
12
+ s.description = s.summary
13
+ s.license = 'MIT'
14
+ s.rubyforge_project = 'em-http-request'
15
+
16
+ s.add_dependency 'addressable', '>= 2.3.4'
17
+ s.add_dependency 'cookiejar-future'
18
+ s.add_dependency 'em-socksify', '>= 0.3'
19
+ s.add_dependency 'eventmachine', '>= 1.0.3'
20
+ s.add_dependency 'http_parser.rb', '>= 0.6.0'
21
+
22
+ s.add_development_dependency 'mongrel', '~> 1.2.0.pre2'
23
+ s.add_development_dependency 'multi_json'
24
+ s.add_development_dependency 'rack', '< 2.0'
25
+ s.add_development_dependency 'rake'
26
+ s.add_development_dependency 'rspec'
27
+
28
+ s.files = `git ls-files`.split("\n")
29
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
30
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
31
+ s.require_paths = ['lib']
32
+ end
@@ -0,0 +1 @@
1
+ twitter_auth.rb
@@ -0,0 +1,25 @@
1
+ $: << 'lib' << '../../lib'
2
+
3
+ require 'em-http'
4
+ require 'em-http/middleware/digest_auth'
5
+
6
+ digest_config = {
7
+ :username => 'digest_username',
8
+ :password => 'digest_password'
9
+ }
10
+
11
+ EM.run do
12
+
13
+ conn_handshake = EM::HttpRequest.new('http://localhost:3000')
14
+ http_handshake = conn_handshake.get
15
+
16
+ http_handshake.callback do
17
+ conn = EM::HttpRequest.new('http://localhost:3000')
18
+ conn.use EM::Middleware::DigestAuth, http_handshake.response_header['WWW_AUTHENTICATE'], digest_config
19
+ http = conn.get
20
+ http.callback do
21
+ puts http.response
22
+ EM.stop
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ require 'webrick'
2
+
3
+ include WEBrick
4
+
5
+ config = { :Realm => 'DigestAuth_REALM' }
6
+
7
+ htdigest = WEBrick::HTTPAuth::Htdigest.new 'my_password_file'
8
+ htdigest.set_passwd config[:Realm], 'digest_username', 'digest_password'
9
+ htdigest.flush
10
+
11
+ config[:UserDB] = htdigest
12
+
13
+ digest_auth = WEBrick::HTTPAuth::DigestAuth.new config
14
+
15
+ class TestServlet < HTTPServlet::AbstractServlet
16
+ def do_GET(req, res)
17
+ @options[0][:authenticator].authenticate req, res
18
+ res.body = "You are authenticated to see the super secret data\n"
19
+ end
20
+ end
21
+
22
+ s = HTTPServer.new(:Port => 3000)
23
+ s.mount('/', TestServlet, {:authenticator => digest_auth})
24
+ trap("INT") do
25
+ File.delete('my_password_file')
26
+ s.shutdown
27
+ end
28
+ s.start
data/examples/fetch.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require '../lib/em-http'
4
+
5
+ urls = ARGV
6
+ if urls.size < 1
7
+ puts "Usage: #{$0} <url> <url> <...>"
8
+ exit
9
+ end
10
+
11
+ pending = urls.size
12
+
13
+ EM.run do
14
+ urls.each do |url|
15
+ http = EM::HttpRequest.new(url).get
16
+ http.callback {
17
+ puts "#{url}\n#{http.response_header.status} - #{http.response.length} bytes\n"
18
+ puts http.response
19
+
20
+ pending -= 1
21
+ EM.stop if pending < 1
22
+ }
23
+ http.errback {
24
+ puts "#{url}\n" + http.error
25
+
26
+ pending -= 1
27
+ EM.stop if pending < 1
28
+ }
29
+ end
30
+ end
@@ -0,0 +1,51 @@
1
+ $: << 'lib' << '../lib'
2
+
3
+ require 'eventmachine'
4
+ require 'em-http'
5
+ require 'fiber'
6
+
7
+ # Using Fibers in Ruby 1.9 to simulate blocking IO / IO scheduling
8
+ # while using the async EventMachine API's
9
+
10
+ def async_fetch(url)
11
+ f = Fiber.current
12
+ http = EventMachine::HttpRequest.new(url, :connect_timeout => 10, :inactivity_timeout => 20).get
13
+
14
+ http.callback { f.resume(http) }
15
+ http.errback { f.resume(http) }
16
+
17
+ Fiber.yield
18
+
19
+ if http.error
20
+ p [:HTTP_ERROR, http.error]
21
+ end
22
+
23
+ http
24
+ end
25
+
26
+ EventMachine.run do
27
+ Fiber.new{
28
+
29
+ puts "Setting up HTTP request #1"
30
+ data = async_fetch('http://0.0.0.0/')
31
+ puts "Fetched page #1: #{data.response_header.status}"
32
+
33
+ puts "Setting up HTTP request #2"
34
+ data = async_fetch('http://www.yahoo.com/')
35
+ puts "Fetched page #2: #{data.response_header.status}"
36
+
37
+ puts "Setting up HTTP request #3"
38
+ data = async_fetch('http://non-existing.domain/')
39
+ puts "Fetched page #3: #{data.response_header.status}"
40
+
41
+ EventMachine.stop
42
+ }.resume
43
+ end
44
+
45
+ puts "Done"
46
+
47
+ # Setting up HTTP request #1
48
+ # Fetched page #1: 302
49
+ # Setting up HTTP request #2
50
+ # Fetched page #2: 200
51
+ # Done
data/examples/multi.rb ADDED
@@ -0,0 +1,25 @@
1
+ $: << '../lib' << 'lib'
2
+
3
+ require 'eventmachine'
4
+ require 'em-http'
5
+
6
+ EventMachine.run {
7
+ multi = EventMachine::MultiRequest.new
8
+
9
+ reqs = [
10
+ 'http://google.com/',
11
+ 'http://google.ca:81/'
12
+ ]
13
+
14
+ reqs.each_with_index do |url, idx|
15
+ http = EventMachine::HttpRequest.new(url, :connect_timeout => 1)
16
+ req = http.get
17
+ multi.add idx, req
18
+ end
19
+
20
+ multi.callback do
21
+ p multi.responses[:callback].size
22
+ p multi.responses[:errback].size
23
+ EventMachine.stop
24
+ end
25
+ }
@@ -0,0 +1,35 @@
1
+ $: << 'lib' << '../lib'
2
+
3
+ require 'em-http'
4
+ require 'em-http/middleware/oauth'
5
+ require 'em-http/middleware/json_response'
6
+
7
+ require 'pp'
8
+
9
+ OAuthConfig = {
10
+ :consumer_key => '',
11
+ :consumer_secret => '',
12
+ :access_token => '',
13
+ :access_token_secret => ''
14
+ }
15
+
16
+ EM.run do
17
+ # automatically parse the JSON response into a Ruby object
18
+ EventMachine::HttpRequest.use EventMachine::Middleware::JSONResponse
19
+
20
+ # sign the request with OAuth credentials
21
+ conn = EventMachine::HttpRequest.new('http://api.twitter.com/1/statuses/home_timeline.json')
22
+ conn.use EventMachine::Middleware::OAuth, OAuthConfig
23
+
24
+ http = conn.get
25
+ http.callback do
26
+ pp http.response
27
+ EM.stop
28
+ end
29
+
30
+ http.errback do
31
+ puts "Failed retrieving user stream."
32
+ pp http.response
33
+ EM.stop
34
+ end
35
+ end
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require '../lib/em-http'
4
+
5
+ EM.run do
6
+ # Establish a SOCKS5 tunnel via SSH
7
+ # ssh -D 8000 some_remote_machine
8
+
9
+ connection_options = {:proxy => {:host => '127.0.0.1', :port => 8000, :type => :socks5}}
10
+ http = EM::HttpRequest.new('http://igvita.com/', connection_options).get :redirects => 2
11
+
12
+ http.callback {
13
+ puts "#{http.response_header.status} - #{http.response.length} bytes\n"
14
+ puts http.response
15
+ EM.stop
16
+ }
17
+
18
+ http.errback {
19
+ puts "Error: " + http.error
20
+ puts http.inspect
21
+ EM.stop
22
+ }
23
+ end
@@ -0,0 +1 @@
1
+ require 'em-http'
data/lib/em-http.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'eventmachine'
2
+ require 'em-socksify'
3
+ require 'addressable/uri'
4
+ require 'http/parser'
5
+
6
+ require 'base64'
7
+ require 'socket'
8
+ require 'openssl'
9
+
10
+ require 'em-http/core_ext/bytesize'
11
+ require 'em-http/http_connection'
12
+ require 'em-http/http_header'
13
+ require 'em-http/http_encoding'
14
+ require 'em-http/http_status_codes'
15
+ require 'em-http/http_client_options'
16
+ require 'em-http/http_connection_options'
17
+ require 'em-http/client'
18
+ require 'em-http/multi'
19
+ require 'em-http/request'
20
+ require 'em-http/decoders'
@@ -0,0 +1,341 @@
1
+ require 'cookiejar'
2
+
3
+ module EventMachine
4
+
5
+
6
+ class HttpClient
7
+ include Deferrable
8
+ include HttpEncoding
9
+ include HttpStatus
10
+
11
+ TRANSFER_ENCODING="TRANSFER_ENCODING"
12
+ CONTENT_ENCODING="CONTENT_ENCODING"
13
+ CONTENT_LENGTH="CONTENT_LENGTH"
14
+ CONTENT_TYPE="CONTENT_TYPE"
15
+ LAST_MODIFIED="LAST_MODIFIED"
16
+ KEEP_ALIVE="CONNECTION"
17
+ SET_COOKIE="SET_COOKIE"
18
+ LOCATION="LOCATION"
19
+ HOST="HOST"
20
+ ETAG="ETAG"
21
+
22
+ CRLF="\r\n"
23
+
24
+ attr_accessor :state, :response, :conn
25
+ attr_reader :response_header, :error, :content_charset, :req, :cookies
26
+
27
+ def initialize(conn, options)
28
+ @conn = conn
29
+ @req = options
30
+
31
+ @stream = nil
32
+ @headers = nil
33
+ @cookies = []
34
+ @cookiejar = CookieJar.new
35
+
36
+ reset!
37
+ end
38
+
39
+ def reset!
40
+ @response_header = HttpResponseHeader.new
41
+ @state = :response_header
42
+
43
+ @response = ''
44
+ @error = nil
45
+ @content_decoder = nil
46
+ @content_charset = nil
47
+ end
48
+
49
+ def last_effective_url; @req.uri; end
50
+ def redirects; @req.followed; end
51
+ def peer; @conn.peer; end
52
+
53
+ def connection_completed
54
+ @state = :response_header
55
+
56
+ head, body = build_request, @req.body
57
+ @conn.middleware.each do |m|
58
+ head, body = m.request(self, head, body) if m.respond_to?(:request)
59
+ end
60
+
61
+ send_request(head, body)
62
+ end
63
+
64
+ def on_request_complete
65
+ begin
66
+ @content_decoder.finalize! if @content_decoder
67
+ rescue HttpDecoders::DecoderError
68
+ on_error "Content-decoder error"
69
+ end
70
+
71
+ unbind
72
+ end
73
+
74
+ def continue?
75
+ @response_header.status == 100 && (@req.method == 'POST' || @req.method == 'PUT')
76
+ end
77
+
78
+ def finished?
79
+ @state == :finished || (@state == :body && @response_header.content_length.nil?)
80
+ end
81
+
82
+ def redirect?
83
+ @response_header.redirection? && @req.follow_redirect?
84
+ end
85
+
86
+ def unbind(reason = nil)
87
+ if finished?
88
+ if redirect?
89
+
90
+ begin
91
+ @conn.middleware.each do |m|
92
+ m.response(self) if m.respond_to?(:response)
93
+ end
94
+
95
+ # one of the injected middlewares could have changed
96
+ # our redirect settings, check if we still want to
97
+ # follow the location header
98
+ if redirect?
99
+ @req.followed += 1
100
+
101
+ @cookies.clear
102
+ @cookies = @cookiejar.get(@response_header.location).map(&:to_s) if @req.pass_cookies
103
+
104
+ @conn.redirect(self, @response_header.location)
105
+ else
106
+ succeed(self)
107
+ end
108
+
109
+ rescue => e
110
+ on_error(e.message)
111
+ end
112
+ else
113
+ succeed(self)
114
+ end
115
+
116
+ else
117
+ on_error(reason || 'connection closed by server')
118
+ end
119
+ end
120
+
121
+ def on_error(msg = nil)
122
+ @error = msg
123
+ fail(self)
124
+ end
125
+ alias :close :on_error
126
+
127
+ def stream(&blk); @stream = blk; end
128
+ def headers(&blk); @headers = blk; end
129
+
130
+ def normalize_body(body)
131
+ body.is_a?(Hash) ? form_encode_body(body) : body
132
+ end
133
+
134
+ def build_request
135
+ head = @req.headers ? munge_header_keys(@req.headers) : {}
136
+
137
+ if @conn.connopts.http_proxy?
138
+ proxy = @conn.connopts.proxy
139
+ head['proxy-authorization'] = proxy[:authorization] if proxy[:authorization]
140
+ end
141
+
142
+ # Set the cookie header if provided
143
+ if cookie = head['cookie']
144
+ @cookies << encode_cookie(cookie) if cookie
145
+ end
146
+ head['cookie'] = @cookies.compact.uniq.join("; ").squeeze(";") unless @cookies.empty?
147
+
148
+ # Set connection close unless keepalive
149
+ if !@req.keepalive
150
+ head['connection'] = 'close'
151
+ end
152
+
153
+ # Set the Host header if it hasn't been specified already
154
+ head['host'] ||= encode_host
155
+
156
+ # Set the User-Agent if it hasn't been specified
157
+ if !head.key?('user-agent')
158
+ head['user-agent'] = 'EventMachine HttpClient'
159
+ elsif head['user-agent'].nil?
160
+ head.delete('user-agent')
161
+ end
162
+
163
+ # Set the Accept-Encoding header if none is provided
164
+ if !head.key?('accept-encoding') && req.compressed
165
+ head['accept-encoding'] = 'gzip, compressed'
166
+ end
167
+
168
+ # Set the auth from the URI if given
169
+ head['Authorization'] = @req.uri.userinfo.split(/:/, 2) if @req.uri.userinfo
170
+
171
+ head
172
+ end
173
+
174
+ def send_request(head, body)
175
+ body = normalize_body(body)
176
+ file = @req.file
177
+ query = @req.query
178
+
179
+ # Set the Content-Length if file is given
180
+ head['content-length'] = File.size(file) if file
181
+
182
+ # Set the Content-Length if body is given,
183
+ # or we're doing an empty post or put
184
+ if body
185
+ head['content-length'] ||= body.respond_to?(:bytesize) ? body.bytesize : body.size
186
+ elsif @req.method == 'POST' or @req.method == 'PUT'
187
+ # wont happen if body is set and we already set content-length above
188
+ head['content-length'] ||= 0
189
+ end
190
+
191
+ # Set content-type header if missing and body is a Ruby hash
192
+ if !head['content-type'] and @req.body.is_a? Hash
193
+ head['content-type'] = 'application/x-www-form-urlencoded'
194
+ end
195
+
196
+ request_header ||= encode_request(@req.method, @req.uri, query, @conn.connopts)
197
+ request_header << encode_headers(head)
198
+ request_header << CRLF
199
+ @conn.send_data request_header
200
+
201
+ @req_body = body || (@req.file && Pathname.new(@req.file))
202
+ send_request_body unless @req.headers['expect'] == '100-continue'
203
+ end
204
+
205
+ def on_body_data(data)
206
+ if @content_decoder
207
+ begin
208
+ @content_decoder << data
209
+ rescue HttpDecoders::DecoderError
210
+ on_error "Content-decoder error"
211
+ end
212
+ else
213
+ on_decoded_body_data(data)
214
+ end
215
+ end
216
+
217
+ def on_decoded_body_data(data)
218
+ data.force_encoding @content_charset if @content_charset
219
+ if @stream
220
+ @stream.call(data)
221
+ else
222
+ @response << data
223
+ end
224
+ end
225
+
226
+ def request_body_pending?
227
+ !!@req_body
228
+ end
229
+
230
+ def send_request_body
231
+ return if @req_body.nil?
232
+
233
+ if @req_body.is_a?(String)
234
+ @conn.send_data @req_body
235
+
236
+ elsif @req_body.is_a?(Pathname)
237
+ @conn.stream_file_data @req_body.to_path, http_chunks: false
238
+
239
+ elsif @req_body.respond_to?(:read) && @req_body.respond_to?(:eof?) # IO or IO-like object
240
+ @conn.stream_data @req_body
241
+
242
+ else
243
+ raise "Don't know how to send request body: #{@req_body.inspect}"
244
+ end
245
+ @req_body = nil
246
+ end
247
+
248
+ def parse_response_header(header, version, status)
249
+ @response_header.raw = header
250
+ header.each do |key, val|
251
+ @response_header[key.upcase.gsub('-','_')] = val
252
+ end
253
+
254
+ @response_header.http_version = version.join('.')
255
+ @response_header.http_status = status
256
+ @response_header.http_reason = CODE[status] || 'unknown'
257
+
258
+ # invoke headers callback after full parse
259
+ # if one is specified by the user
260
+ @headers.call(@response_header) if @headers
261
+
262
+ unless @response_header.http_status and @response_header.http_reason
263
+ @state = :invalid
264
+ on_error "no HTTP response"
265
+ return
266
+ end
267
+
268
+ # add set-cookie's to cookie list
269
+ if @response_header.cookie && @req.pass_cookies
270
+ [@response_header.cookie].flatten.each {|cookie| @cookiejar.set(cookie, @req.uri)}
271
+ end
272
+
273
+ # correct location header - some servers will incorrectly give a relative URI
274
+ if @response_header.location
275
+ begin
276
+ location = Addressable::URI.parse(@response_header.location)
277
+ location.path = "/" if location.path.empty?
278
+
279
+ if location.relative?
280
+ location = @req.uri.join(location)
281
+ else
282
+ # if redirect is to an absolute url, check for correct URI structure
283
+ raise if location.host.nil?
284
+ end
285
+
286
+ @response_header[LOCATION] = location.to_s
287
+
288
+ rescue
289
+ on_error "Location header format error"
290
+ return
291
+ end
292
+ end
293
+
294
+ # Fire callbacks immediately after recieving header requests
295
+ # if the request method is HEAD. In case of a redirect, terminate
296
+ # current connection and reinitialize the process.
297
+ if @req.method == "HEAD"
298
+ @state = :finished
299
+ return
300
+ end
301
+
302
+ if @response_header.chunked_encoding?
303
+ @state = :chunk_header
304
+ elsif @response_header.content_length
305
+ @state = :body
306
+ else
307
+ @state = :body
308
+ end
309
+
310
+ if @req.decoding && decoder_class = HttpDecoders.decoder_for_encoding(response_header[CONTENT_ENCODING])
311
+ begin
312
+ @content_decoder = decoder_class.new do |s| on_decoded_body_data(s) end
313
+ rescue HttpDecoders::DecoderError
314
+ on_error "Content-decoder error"
315
+ end
316
+ end
317
+
318
+ # handle malformed header - Content-Type repetitions.
319
+ content_type = [response_header[CONTENT_TYPE]].flatten.first
320
+
321
+ if String.method_defined?(:force_encoding) && /;\s*charset=\s*(.+?)\s*(;|$)/.match(content_type)
322
+ @content_charset = Encoding.find($1.gsub(/^\"|\"$/, '')) rescue Encoding.default_external
323
+ end
324
+ end
325
+
326
+ class CookieJar
327
+ def initialize
328
+ @jar = ::CookieJar::Jar.new
329
+ end
330
+
331
+ def set string, uri
332
+ @jar.set_cookie(uri, string) rescue nil # drop invalid cookies
333
+ end
334
+
335
+ def get uri
336
+ uri = URI.parse(uri) rescue nil
337
+ uri ? @jar.get_cookies(uri) : []
338
+ end
339
+ end # CookieJar
340
+ end
341
+ end