em-http-request-samesite 1.1.7

Sign up to get free protection for your applications and to get access to all the features.
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