This repository has been archived by the owner on Jan 2, 2018. It is now read-only.
forked from rubymotion-community/BubbleWrap
/
http.rb
213 lines (186 loc) · 8.5 KB
/
http.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
module BubbleWrap
SETTINGS = {}
# The HTTP module provides a simple interface to make HTTP requests.
#
# TODO: preflight support, easier/better cookie support, better error handling
module HTTP
# Make a GET request and process the response asynchronously via a block.
#
# @examples
# # Simple GET request printing the body
# BubbleWrap::HTTP.get("https://api.github.com/users/mattetti") do |response|
# p response.body.to_str
# end
#
# # GET request with basic auth credentials
# BubbleWrap::HTTP.get("https://api.github.com/users/mattetti", {credentials: {username: 'matt', password: 'aimonetti'}}) do |response|
# p response.body.to_str # prints the response's body
# end
#
def self.get(url, options={}, &block)
delegator = block_given? ? block : options.delete(:action)
HTTP::Query.new( url, :get, options.merge({:action => delegator}) )
end
# Make a POST request
def self.post(url, options={}, &block)
delegator = block_given? ? block : options.delete(:action)
HTTP::Query.new( url, :post, options.merge({:action => delegator}) )
end
# Make a PUT request
def self.put(url, options={}, &block)
delegator = block_given? ? block : options.delete(:action)
HTTP::Query.new( url, :put, options.merge({:action => delegator}) )
end
# Make a DELETE request
def self.delete(url, options={}, &block)
delegator = block_given? ? block : options.delete(:action)
HTTP::Query.new( url, :delete, options.merge({:action => delegator}) )
end
# Make a HEAD request
def self.head(url, options={}, &block)
delegator = block_given? ? block : options.delete(:action)
HTTP::Query.new( url, :head, options.merge({:action => delegator}) )
end
# Make a PATCH request
def self.patch(url, options={}, &block)
delegator = block_given? ? block : options.delete(:action)
HTTP::Query.new( url, :patch, options.merge({:action => delegator}) )
end
# Response class wrapping the results of a Query's response
class Response
attr_reader :body
attr_reader :headers
attr_accessor :status_code, :error_message
attr_reader :url
def initialize(values={})
self.update(values)
end
def update(values)
values.each do |k,v|
self.instance_variable_set("@#{k}", v)
end
end
def ok?
status_code == 200
end
end
# Class wrapping NSConnection and often used indirectly by the BubbleWrap::HTTP module methods.
class Query
attr_accessor :request
attr_accessor :connection
attr_accessor :credentials # username & password has a hash
attr_accessor :proxy_credential # credential supplied to proxy servers
attr_accessor :post_data
attr_reader :method
attr_reader :response
attr_reader :status_code
attr_reader :response_headers
attr_reader :response_size
attr_reader :options
# ==== Parameters
# url<String>:: url of the resource to download
# http_method<Symbol>:: Value representing the HTTP method to use
# options<Hash>:: optional options used for the query
#
# ==== Options
# :payload<String> - data to pass to a POST, PUT, DELETE query.
# :delegator - Proc, class or object to call when the file is downloaded.
# a proc will receive a Response object while the passed object
# will receive the handle_query_response method
# :headers<Hash> - headers send with the request
# Anything else will be available via the options attribute reader.
#
def initialize(url, http_method = :get, options={})
@method = http_method.upcase.to_s
@delegator = options.delete(:action) || self
@payload = options.delete(:payload)
@credentials = options.delete(:credentials) || {}
@credentials = {:username => '', :password => ''}.merge(@credentials)
headers = options.delete(:headers)
if headers
@headers = {}
headers.each{|k,v| @headers[k] = v.gsub("\n", '\\n') } # escaping LFs
end
@options = options
@response = HTTP::Response.new
initiate_request(url)
connection.start
connection
end
def initiate_request(url_string)
# http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/nsrunloop_Class/Reference/Reference.html#//apple_ref/doc/constant_group/Run_Loop_Modes
# NSConnectionReplyMode
p "HTTP building a NSRequest for #{url_string}"# if SETTINGS[:debug]
@url = NSURL.URLWithString(url_string)
@request = NSMutableURLRequest.requestWithURL(@url,
cachePolicy:NSURLRequestUseProtocolCachePolicy,
timeoutInterval:30.0)
@request.setHTTPMethod @method
@request.setAllHTTPHeaderFields(@headers) if @headers
# @payload needs to be converted to data
unless method == :get || @payload.nil?
@payload = @payload.map{|k,v| "#{k}=#{v}"}.join('&') if @payload.is_a?(Hash)
@payload = @payload.to_s.dataUsingEncoding(NSUTF8StringEncoding)
@request.setHTTPBody @payload
end
# NSHTTPCookieStorage.sharedHTTPCookieStorage
@connection = NSURLConnection.connectionWithRequest(request, delegate:self)
@request.instance_variable_set("@done_loading", false)
def @request.done_loading; @done_loading; end
def @request.done_loading!; @done_loading = true; end
end
def connection(connection, didReceiveResponse:response)
@status_code = response.statusCode
@response_headers = response.allHeaderFields
@response_size = response.expectedContentLength.to_f
# p "HTTP status code: #{@status_code}, content length: #{@response_size}, headers: #{@response_headers}" if SETTINGS[:debug]
end
# This delegate method get called every time a chunk of data is being received
def connection(connection, didReceiveData:received_data)
@received_data ||= NSMutableData.new
@received_data.appendData(received_data)
end
def connection(connection, willSendRequest:request, redirectResponse:redirect_response)
puts "HTTP redirected #{request.description}" #if SETTINGS[:debug]
new_request = request.mutableCopy
@request.setAllHTTPHeaderFields(@headers) if @headers
# p @request.allHTTPHeaderFields.description
@connection.cancel
@connection = NSURLConnection.connectionWithRequest(new_request, delegate:self)
new_request
end
def connection(connection, didFailWithError: error)
p "HTTP Connection failed #{error.localizedDescription}"
response.error_message = error.localizedDescription
end
# The transfer is done and everything went well
def connectionDidFinishLoading(connection)
@request.done_loading!
# copy the data in a local var that we will attach to the response object
response_body = NSData.dataWithData(@received_data) if @received_data
@response.update(status_code: status_code, body: response_body, headers: response_headers, url: @url)
# Don't reset the received data since this method can be called multiple times if the headers can report the wrong length.
# @received_data = nil
if @delegator.respond_to?(:call)
@delegator.call( @response, self )
end
end
def connection(connection, didReceiveAuthenticationChallenge:challenge)
# p "HTTP auth required" if SETTINGS[:debug]
if (challenge.previousFailureCount == 0)
# by default we are keeping the credential for the entire session
# Eventually, it would be good to let the user pick one of the 3 possible credential persistence options:
# NSURLCredentialPersistenceNone,
# NSURLCredentialPersistenceForSession,
# NSURLCredentialPersistencePermanent
p "auth challenged, answered with credentials: #{credentials.inspect}"
new_credential = NSURLCredential.credentialWithUser(credentials[:username], password:credentials[:password], persistence:NSURLCredentialPersistenceForSession)
challenge.sender.useCredential(new_credential, forAuthenticationChallenge:challenge)
else
challenge.sender.cancelAuthenticationChallenge(challenge)
p 'Auth Failed :('
end
end
end
end
end