bubble-wrap-http 1.6.0.rc1
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.
- checksums.yaml +7 -0
- data/README.md +129 -0
- data/lib/bubble-wrap-http.rb +10 -0
- data/motion/http.rb +101 -0
- data/motion/http/query.rb +412 -0
- data/motion/http/response.rb +32 -0
- metadata +75 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: aefaa21254b706e9bfc9fae4fb19435f0ef526bd
|
4
|
+
data.tar.gz: 662e834e0a96c3b04e919beed10f108898a7a49f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d3d5ae993ee8cd50cbcd539eb501ca53dd82ea0d135500488380ba1f101ee40091de9c3855633c386fb0772e551be237ba8ee6f4e755bddcdf9f031ce8061293
|
7
|
+
data.tar.gz: e133ca52ac112ef4c12ea92d732b663c73066c9910999b798f8cbabab6bfb823d13417bc1fc985a064d183a175dfbcdb336c76b16b5f5ddfd9fcd84955fc3567
|
data/README.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
# BubbleWrap::HTTP
|
2
|
+
|
3
|
+
[](https://travis-ci.org/rubymotion/BubbleWrap-HTTP)
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```
|
10
|
+
gem 'bubble-wrap-http'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
```
|
22
|
+
gem install bubble-wrap-http
|
23
|
+
```
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
`BubbleWrap::HTTP` wraps `NSURLRequest`, `NSURLConnection` and friends to provide Ruby developers with a more familiar and easier to use API.
|
28
|
+
The API uses async calls and blocks to stay as simple as possible.
|
29
|
+
|
30
|
+
To enable it add the following require line to your `Rakefile`:
|
31
|
+
```ruby
|
32
|
+
require 'bubble-wrap-http'
|
33
|
+
```
|
34
|
+
|
35
|
+
Usage example:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
BubbleWrap::HTTP.get("https://api.github.com/users/mattetti") do |response|
|
39
|
+
p response.body.to_str
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
BubbleWrap::HTTP.get("https://api.github.com/users/mattetti", {credentials: {username: 'matt', password: 'aimonetti'}}) do |response|
|
45
|
+
p response.body.to_str # prints the response's body
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
data = {first_name: 'Matt', last_name: 'Aimonetti'}
|
51
|
+
BubbleWrap::HTTP.post("http://foo.bar.com/", {payload: data}) do |response|
|
52
|
+
if response.ok?
|
53
|
+
json = BW::JSON.parse(response.body.to_str)
|
54
|
+
p json['id']
|
55
|
+
elsif response.status_code.to_s =~ /40\d/
|
56
|
+
App.alert("Login failed")
|
57
|
+
else
|
58
|
+
App.alert(response.error_message)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
To upload files to a server, provide a `files:` hash:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
data = {token: "some-api-token"}
|
67
|
+
avatar_data = UIImagePNGRepresentation(UIImage.imageNamed("some-image"))
|
68
|
+
avatar = { data: avatar_data, filename: "some-image.png", content_type: "image/png" }
|
69
|
+
|
70
|
+
BubbleWrap::HTTP.post("http://foo.bar.com/", {payload: data}, files: { avatar: avatar }) do |response|
|
71
|
+
if response.ok?
|
72
|
+
# files are uploaded
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
A `:download_progress` option can also be passed. The expected object
|
78
|
+
would be a Proc that takes two arguments: a float representing the
|
79
|
+
amount of data currently received and another float representing the
|
80
|
+
total amount of data expected.
|
81
|
+
|
82
|
+
Connections can also be cancelled. Just keep a refrence,
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
@conn = BubbleWrap::HTTP.get("https://api.github.com/users/mattetti") do |response|
|
86
|
+
p response.body.to_str
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
and send the `cancel` method to it asynchronously as desired. The block will not be executed.
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
@conn.cancel
|
94
|
+
```
|
95
|
+
|
96
|
+
### Gotchas
|
97
|
+
|
98
|
+
Because of how RubyMotion currently works, you sometimes need to assign objects as `@instance_variables` in order to retain their callbacks.
|
99
|
+
|
100
|
+
For example:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
class HttpClient
|
104
|
+
def get_user(user_id, &callback)
|
105
|
+
BubbleWrap::HTTP.get(user_url(user_id)) do |response|
|
106
|
+
# ..
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
This class should be invoked in your code as:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
@http_client = HttpClient.new
|
116
|
+
@http_client.get_user(user_id) do |user|
|
117
|
+
# ..
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
(instead of doing an instance-variable-less `HttpClient.new.get_user`)
|
122
|
+
|
123
|
+
## Contributing
|
124
|
+
|
125
|
+
1. Fork it
|
126
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
127
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
128
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
129
|
+
5. Create new Pull Request
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
unless defined?(Motion::Project::Config)
|
4
|
+
raise "This file must be required within a RubyMotion project Rakefile."
|
5
|
+
end
|
6
|
+
|
7
|
+
lib_dir_path = File.expand_path(File.join(File.dirname(File.expand_path(__FILE__)), '..'))
|
8
|
+
Motion::Project::App.setup do |app|
|
9
|
+
app.files.unshift(Dir.glob(File.join(lib_dir_path, "motion/**/*.rb")))
|
10
|
+
end
|
data/motion/http.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
module BubbleWrap
|
2
|
+
|
3
|
+
# The HTTP module provides a simple interface to make HTTP requests.
|
4
|
+
#
|
5
|
+
# TODO: preflight support, easier/better cookie support, better error handling
|
6
|
+
module HTTP
|
7
|
+
|
8
|
+
# Make a GET request and process the response asynchronously via a block.
|
9
|
+
#
|
10
|
+
# @examples
|
11
|
+
# # Simple GET request printing the body
|
12
|
+
# BubbleWrap::HTTP.get("https://api.github.com/users/mattetti") do |response|
|
13
|
+
# p response.body.to_str
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # GET request with basic auth credentials
|
17
|
+
# BubbleWrap::HTTP.get("https://api.github.com/users/mattetti", {credentials: {username: 'matt', password: 'aimonetti'}}) do |response|
|
18
|
+
# p response.body.to_str # prints the response's body
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
|
22
|
+
[:get, :post, :put, :delete, :head, :options, :patch].each do |http_verb|
|
23
|
+
|
24
|
+
define_singleton_method(http_verb) do |url, options = {}, &block|
|
25
|
+
options[:action] = block if block
|
26
|
+
HTTP::Query.new(url, http_verb, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
module Patch
|
32
|
+
module_function
|
33
|
+
def use_weak_callbacks?
|
34
|
+
if BubbleWrap.respond_to?("use_weak_callbacks?")
|
35
|
+
return BubbleWrap.use_weak_callbacks?
|
36
|
+
end
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def ios?
|
41
|
+
Kernel.const_defined?(:UIApplication)
|
42
|
+
end
|
43
|
+
|
44
|
+
def osx?
|
45
|
+
Kernel.const_defined?(:NSApplication)
|
46
|
+
end
|
47
|
+
|
48
|
+
def debug?
|
49
|
+
if BubbleWrap.respond_to?("debug?")
|
50
|
+
return BubbleWrap.debug?
|
51
|
+
end
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_uuid
|
56
|
+
uuid = CFUUIDCreate(nil)
|
57
|
+
CFUUIDCreateString(nil, uuid)
|
58
|
+
end
|
59
|
+
|
60
|
+
module JSON
|
61
|
+
module_function
|
62
|
+
def generate(obj)
|
63
|
+
NSJSONSerialization.dataWithJSONObject(obj, options:0, error:nil).to_str
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse(str_data, &block)
|
67
|
+
return nil unless str_data
|
68
|
+
data = str_data.respond_to?(:to_data) ? str_data.to_data : str_data
|
69
|
+
opts = NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves | NSJSONReadingAllowFragments
|
70
|
+
error = Pointer.new(:id)
|
71
|
+
obj = NSJSONSerialization.JSONObjectWithData(data, options:opts, error:error)
|
72
|
+
raise ParserError, error[0].description if error[0]
|
73
|
+
if block_given?
|
74
|
+
yield obj
|
75
|
+
else
|
76
|
+
obj
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
module NetworkIndicator
|
82
|
+
module_function
|
83
|
+
def show
|
84
|
+
if BubbleWrap.const_defined?("NetworkIndicator")
|
85
|
+
BubbleWrap::NetworkIndicator.show
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def hide
|
90
|
+
if BubbleWrap.const_defined?("NetworkIndicator")
|
91
|
+
BubbleWrap::NetworkIndicator.hide
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class InvalidURLError < StandardError; end
|
101
|
+
class InvalidFileError < StandardError; end
|
@@ -0,0 +1,412 @@
|
|
1
|
+
# Class wrapping NSConnection and often used indirectly by the BubbleWrap::HTTP module methods.
|
2
|
+
module BubbleWrap; module HTTP; class Query
|
3
|
+
attr_accessor :request
|
4
|
+
attr_accessor :connection
|
5
|
+
attr_accessor :credentials # username & password has a hash
|
6
|
+
attr_accessor :proxy_credential # credential supplied to proxy servers
|
7
|
+
attr_accessor :post_data
|
8
|
+
attr_reader :method
|
9
|
+
|
10
|
+
attr_reader :response
|
11
|
+
attr_reader :status_code
|
12
|
+
attr_reader :response_headers
|
13
|
+
attr_reader :response_size
|
14
|
+
attr_reader :options
|
15
|
+
CLRF = "\r\n"
|
16
|
+
# ==== Parameters
|
17
|
+
# url<String>:: url of the resource to download
|
18
|
+
# http_method<Symbol>:: Value representing the HTTP method to use
|
19
|
+
# options<Hash>:: optional options used for the query
|
20
|
+
#
|
21
|
+
# ==== Options
|
22
|
+
# :payload<String> - data to pass to a POST, PUT, DELETE query.
|
23
|
+
# :action - Proc, class or object to call when the file is downloaded.
|
24
|
+
# a proc will receive a Response object while the passed object
|
25
|
+
# will receive the handle_query_response method
|
26
|
+
# :headers<Hash> - headers send with the request
|
27
|
+
# :cookies<Boolean> - Set whether cookies should be sent with request or not (Default: true)
|
28
|
+
# Anything else will be available via the options attribute reader.
|
29
|
+
#
|
30
|
+
def initialize(url_string, http_method = :get, options={})
|
31
|
+
@method = http_method.upcase.to_s
|
32
|
+
@delegator = options.delete(:action) || self
|
33
|
+
if @delegator.respond_to?("weak!")
|
34
|
+
@delegator.weak! if BubbleWrap::HTTP::Patch.use_weak_callbacks?
|
35
|
+
end
|
36
|
+
|
37
|
+
@payload = options.delete(:payload)
|
38
|
+
@encoding = options.delete(:encoding) || NSUTF8StringEncoding
|
39
|
+
@files = options.delete(:files)
|
40
|
+
@boundary = options.delete(:boundary) || BubbleWrap::HTTP::Patch.create_uuid
|
41
|
+
@credentials = options.delete(:credentials) || {}
|
42
|
+
@credentials = {:username => nil, :password => nil}.merge(@credentials)
|
43
|
+
@timeout = options.delete(:timeout) || 30.0
|
44
|
+
@headers = escape_line_feeds(options.delete :headers)
|
45
|
+
@format = options.delete(:format)
|
46
|
+
@cache_policy = options.delete(:cache_policy) || NSURLRequestUseProtocolCachePolicy
|
47
|
+
@credential_persistence = options.delete(:credential_persistence) || NSURLCredentialPersistenceForSession
|
48
|
+
@cookies = options.key?(:cookies) ? options.delete(:cookies) : true
|
49
|
+
@options = options
|
50
|
+
@response = BubbleWrap::HTTP::Response.new
|
51
|
+
@follow_urls = options[:follow_urls] || true
|
52
|
+
@present_credentials = options[:present_credentials] == nil ? true : options.delete(:present_credentials)
|
53
|
+
|
54
|
+
@url = create_url(url_string)
|
55
|
+
@body = create_request_body
|
56
|
+
@request = create_request
|
57
|
+
@original_url = @url.copy
|
58
|
+
|
59
|
+
@connection = create_connection(request, self)
|
60
|
+
@connection.scheduleInRunLoop(NSRunLoop.currentRunLoop, forMode:NSRunLoopCommonModes)
|
61
|
+
@connection.start
|
62
|
+
|
63
|
+
show_status_indicator true
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
"#<#{self.class}:#{self.object_id} - Method: #{@method}, url: #{@url.description}, body: #{@body.description}, Payload: #{@payload}, Headers: #{@headers} Credentials: #{@credentials}, Timeout: #{@timeout}, \
|
68
|
+
Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
69
|
+
end
|
70
|
+
alias description to_s
|
71
|
+
|
72
|
+
def done?
|
73
|
+
@did_fail_error || @did_finish_loading || @canceled
|
74
|
+
end
|
75
|
+
|
76
|
+
def connection(connection, didReceiveResponse:response)
|
77
|
+
# On OSX, if using an FTP connection, this method will fire *immediately* after creating an
|
78
|
+
# NSURLConnection, even if the connection has not yet started. The `response`
|
79
|
+
# object will be a NSURLResponse, *not* an `NSHTTPURLResponse`, and so will start to crash.
|
80
|
+
if BubbleWrap::HTTP::Patch.osx? && !response.is_a?(NSHTTPURLResponse)
|
81
|
+
return
|
82
|
+
end
|
83
|
+
did_receive_response(response)
|
84
|
+
end
|
85
|
+
|
86
|
+
# This delegate method get called every time a chunk of data is being received
|
87
|
+
def connection(connection, didReceiveData:received_data)
|
88
|
+
@received_data ||= NSMutableData.new
|
89
|
+
@received_data.appendData(received_data)
|
90
|
+
|
91
|
+
if download_progress = options[:download_progress]
|
92
|
+
download_progress.call(@received_data.length.to_f, response_size)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def connection(connection, willSendRequest:request, redirectResponse:redirect_response)
|
97
|
+
# abort early if the user has explicitly disabled redirects
|
98
|
+
if @options[:no_redirect] and redirect_response then
|
99
|
+
return nil
|
100
|
+
end
|
101
|
+
@redirect_count ||= 0
|
102
|
+
@redirect_count += 1
|
103
|
+
log "##{@redirect_count} HTTP redirect_count: #{request.inspect} - #{self.description}"
|
104
|
+
|
105
|
+
if @redirect_count >= 30
|
106
|
+
error = NSError.errorWithDomain('BubbleWrap::HTTP', code:NSURLErrorHTTPTooManyRedirects,
|
107
|
+
userInfo:NSDictionary.dictionaryWithObject("Too many redirections",
|
108
|
+
forKey: NSLocalizedDescriptionKey))
|
109
|
+
self.connection(connection, didFailWithError: error)
|
110
|
+
nil
|
111
|
+
else
|
112
|
+
@url = request.URL if @follow_urls
|
113
|
+
request
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def connection(connection, didFailWithError: error)
|
118
|
+
return if done?
|
119
|
+
|
120
|
+
@did_fail_error = error
|
121
|
+
log "HTTP Connection to #{@url.absoluteString} failed #{error.localizedDescription}"
|
122
|
+
show_status_indicator false
|
123
|
+
@request.done_loading!
|
124
|
+
@response.error = error
|
125
|
+
@response.error_message = error.localizedDescription
|
126
|
+
call_delegator_with_response
|
127
|
+
end
|
128
|
+
|
129
|
+
def connection(connection, didSendBodyData:sending, totalBytesWritten:written, totalBytesExpectedToWrite:expected)
|
130
|
+
if upload_progress = options[:upload_progress]
|
131
|
+
upload_progress.call(sending, written, expected)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def connectionDidFinishLoading(connection)
|
136
|
+
return if done?
|
137
|
+
@did_finish_loading = true
|
138
|
+
|
139
|
+
show_status_indicator false
|
140
|
+
@request.done_loading!
|
141
|
+
response_body = NSData.dataWithData(@received_data) if @received_data
|
142
|
+
@response.update(status_code: status_code, body: response_body, headers: response_headers, url: @url, original_url: @original_url)
|
143
|
+
|
144
|
+
call_delegator_with_response
|
145
|
+
end
|
146
|
+
|
147
|
+
def connection(connection, didReceiveAuthenticationChallenge:challenge)
|
148
|
+
if (challenge.previousFailureCount == 0)
|
149
|
+
if credentials[:username].to_s.empty? && credentials[:password].to_s.empty?
|
150
|
+
challenge.sender.continueWithoutCredentialForAuthenticationChallenge(challenge)
|
151
|
+
log 'Continue without credentials to get 401 status in response'
|
152
|
+
else
|
153
|
+
new_credential = NSURLCredential.credentialWithUser(credentials[:username], password:credentials[:password], persistence:@credential_persistence)
|
154
|
+
challenge.sender.useCredential(new_credential, forAuthenticationChallenge:challenge)
|
155
|
+
log "auth challenged, answered with credentials: #{credentials.inspect}"
|
156
|
+
end
|
157
|
+
else
|
158
|
+
did_receive_response(challenge.failureResponse)
|
159
|
+
@response.update(status_code: status_code, headers: response_headers, url: @url, original_url: @original_url)
|
160
|
+
challenge.sender.cancelAuthenticationChallenge(challenge)
|
161
|
+
log 'Auth Failed :('
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def cancel
|
166
|
+
return if done?
|
167
|
+
@canceled = true
|
168
|
+
|
169
|
+
@connection.cancel
|
170
|
+
show_status_indicator false
|
171
|
+
@request.done_loading!
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
def did_receive_response(response)
|
177
|
+
@status_code = response.statusCode
|
178
|
+
@response_headers = response.allHeaderFields
|
179
|
+
@response_size = response.expectedContentLength.to_f
|
180
|
+
end
|
181
|
+
|
182
|
+
def show_status_indicator(show)
|
183
|
+
if BubbleWrap::HTTP::Patch.ios? && (@status.nil? || @status != !!show)
|
184
|
+
@status = !!show
|
185
|
+
if show
|
186
|
+
BubbleWrap::HTTP::Patch::NetworkIndicator.show
|
187
|
+
else
|
188
|
+
BubbleWrap::HTTP::Patch::NetworkIndicator.hide
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def create_request
|
194
|
+
log "BubbleWrap::HTTP building a NSRequest for #{@url.description}"
|
195
|
+
|
196
|
+
request = NSMutableURLRequest.requestWithURL(@url,
|
197
|
+
cachePolicy:@cache_policy,
|
198
|
+
timeoutInterval:@timeout)
|
199
|
+
request.setHTTPMethod(@method)
|
200
|
+
set_content_type
|
201
|
+
append_auth_header
|
202
|
+
request.setAllHTTPHeaderFields(@headers)
|
203
|
+
request.setHTTPBody(@body)
|
204
|
+
request.setHTTPShouldHandleCookies(@cookies)
|
205
|
+
patch_nsurl_request(request)
|
206
|
+
|
207
|
+
request
|
208
|
+
end
|
209
|
+
|
210
|
+
def set_content_type
|
211
|
+
return if headers_provided?
|
212
|
+
return if (@method == "GET" || @method == "HEAD" || @method == "OPTIONS")
|
213
|
+
@headers ||= {}
|
214
|
+
@headers["Content-Type"] = case @format
|
215
|
+
when :json
|
216
|
+
"application/json"
|
217
|
+
when :xml
|
218
|
+
"application/xml"
|
219
|
+
when :text
|
220
|
+
"text/plain"
|
221
|
+
else
|
222
|
+
if @format == :form_data || @payload_or_files_were_appended
|
223
|
+
"multipart/form-data; boundary=#{@boundary}"
|
224
|
+
else
|
225
|
+
"application/x-www-form-urlencoded"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def headers_provided?
|
231
|
+
@headers && @headers.keys.find {|k| k.downcase == 'content-type'}
|
232
|
+
end
|
233
|
+
|
234
|
+
def credentials_provided?
|
235
|
+
@credentials[:username] && @credentials[:password]
|
236
|
+
end
|
237
|
+
|
238
|
+
def create_request_body
|
239
|
+
return nil if (@method == "GET" || @method == "HEAD" || @method == "OPTIONS")
|
240
|
+
return nil unless (@payload || @files)
|
241
|
+
|
242
|
+
body = NSMutableData.data
|
243
|
+
|
244
|
+
append_payload(body) if @payload
|
245
|
+
append_files(body) if @files
|
246
|
+
append_body_boundary(body) if @payload_or_files_were_appended
|
247
|
+
|
248
|
+
log "Built HTTP body: \n #{body.to_str}"
|
249
|
+
body
|
250
|
+
end
|
251
|
+
|
252
|
+
def append_payload(body)
|
253
|
+
if @payload.is_a?(NSData)
|
254
|
+
body.appendData(@payload)
|
255
|
+
elsif @payload.is_a?(String)
|
256
|
+
body.appendData encode_to_data(@payload, @encoding)
|
257
|
+
elsif @format == :json
|
258
|
+
json_string = BubbleWrap::HTTP::Patch::JSON.generate(@payload)
|
259
|
+
body.appendData encode_to_data(json_string, @encoding)
|
260
|
+
else
|
261
|
+
append_form_params(body)
|
262
|
+
end
|
263
|
+
body
|
264
|
+
end
|
265
|
+
|
266
|
+
def append_form_params(body)
|
267
|
+
list = process_payload_hash(@payload)
|
268
|
+
list.each do |key, value|
|
269
|
+
s = "--#{@boundary}\r\n"
|
270
|
+
s += "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n"
|
271
|
+
s += value.to_s
|
272
|
+
s += "\r\n"
|
273
|
+
body.appendData encode_to_data(s, @encoding)
|
274
|
+
end
|
275
|
+
@payload_or_files_were_appended = true
|
276
|
+
body
|
277
|
+
end
|
278
|
+
|
279
|
+
def append_auth_header
|
280
|
+
return if @headers && @headers["Authorization"]
|
281
|
+
|
282
|
+
if credentials_provided? && @present_credentials
|
283
|
+
mock_request = CFHTTPMessageCreateRequest(nil, nil, nil, nil)
|
284
|
+
CFHTTPMessageAddAuthentication(mock_request, nil, @credentials[:username], @credentials[:password], KCFHTTPAuthenticationSchemeBasic, false)
|
285
|
+
|
286
|
+
@headers ||= {}
|
287
|
+
@headers["Authorization"] = CFHTTPMessageCopyHeaderFieldValue(mock_request, "Authorization")
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def parse_file(key, value)
|
292
|
+
value = {data: value} unless value.is_a?(Hash)
|
293
|
+
raise(InvalidFileError, "You need to supply a `:data` entry in #{value} for file '#{key}' in your HTTP `:files`") if value[:data].nil?
|
294
|
+
{
|
295
|
+
data: value[:data],
|
296
|
+
filename: value.fetch(:filename, key),
|
297
|
+
content_type: value.fetch(:content_type, "application/octet-stream")
|
298
|
+
}
|
299
|
+
end
|
300
|
+
|
301
|
+
def append_files(body)
|
302
|
+
@files.each do |key, value|
|
303
|
+
file = parse_file(key, value)
|
304
|
+
s = "--#{@boundary}\r\n"
|
305
|
+
s += "Content-Disposition: attachment; name=\"#{key}\"; filename=\"#{file[:filename]}\"\r\n"
|
306
|
+
s += "Content-Type: #{file[:content_type]}\r\n\r\n"
|
307
|
+
file_data = NSMutableData.new
|
308
|
+
file_data.appendData encode_to_data(s, @encoding)
|
309
|
+
file_data.appendData(file[:data])
|
310
|
+
file_data.appendData encode_to_data("\r\n", @encoding)
|
311
|
+
body.appendData(file_data)
|
312
|
+
end
|
313
|
+
@payload_or_files_were_appended = true
|
314
|
+
body
|
315
|
+
end
|
316
|
+
|
317
|
+
def append_body_boundary(body)
|
318
|
+
body.appendData encode_to_data("--#{@boundary}--\r\n", @encoding)
|
319
|
+
end
|
320
|
+
|
321
|
+
def create_url(url_string)
|
322
|
+
url_string = url_string.stringByAddingPercentEscapesUsingEncoding NSUTF8StringEncoding
|
323
|
+
if (@method == "GET" || @method == "HEAD" || @method == "OPTIONS") && @payload
|
324
|
+
unless @payload.empty?
|
325
|
+
convert_payload_to_url if @payload.is_a?(Hash)
|
326
|
+
url_string += "?#{@payload}"
|
327
|
+
end
|
328
|
+
end
|
329
|
+
url = NSURL.URLWithString(url_string)
|
330
|
+
|
331
|
+
validate_url(url)
|
332
|
+
url
|
333
|
+
end
|
334
|
+
|
335
|
+
def validate_url(url)
|
336
|
+
if !NSURLConnection.canHandleRequest(NSURLRequest.requestWithURL(url))
|
337
|
+
raise InvalidURLError, "Invalid URL provided (Make sure you include a valid URL scheme, e.g. http:// or similar)."
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def escape(string)
|
342
|
+
string_to_escape = string.to_s
|
343
|
+
if string_to_escape
|
344
|
+
CFURLCreateStringByAddingPercentEscapes nil, string_to_escape, nil, "!*'();:@&=+$,/?%#[]", KCFStringEncodingUTF8
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def convert_payload_to_url
|
349
|
+
params_array = process_payload_hash(@payload)
|
350
|
+
params_array.map! { |key, value| "#{escape key}=#{escape value}" }
|
351
|
+
@payload = params_array.join("&")
|
352
|
+
end
|
353
|
+
|
354
|
+
def process_payload_hash(payload, prefix=nil)
|
355
|
+
list = []
|
356
|
+
payload.each do |k,v|
|
357
|
+
if v.is_a?(Hash)
|
358
|
+
new_prefix = prefix ? "#{prefix}[#{k.to_s}]" : k.to_s
|
359
|
+
param = process_payload_hash(v, new_prefix)
|
360
|
+
list += param
|
361
|
+
elsif v.is_a?(Array)
|
362
|
+
v.each do |val|
|
363
|
+
param = prefix ? "#{prefix}[#{k.to_s}][]" : "#{k.to_s}[]"
|
364
|
+
if val.is_a?(Hash)
|
365
|
+
list += process_payload_hash(val, param)
|
366
|
+
else
|
367
|
+
list << [param, val]
|
368
|
+
end
|
369
|
+
end
|
370
|
+
else
|
371
|
+
param = prefix ? "#{prefix}[#{k.to_s}]" : k.to_s
|
372
|
+
list << [param, v]
|
373
|
+
end
|
374
|
+
end
|
375
|
+
list
|
376
|
+
end
|
377
|
+
|
378
|
+
def log(message)
|
379
|
+
NSLog message if BubbleWrap::HTTP::Patch.debug?
|
380
|
+
end
|
381
|
+
|
382
|
+
def escape_line_feeds(hash)
|
383
|
+
return nil if hash.nil?
|
384
|
+
escaped_hash = {}
|
385
|
+
|
386
|
+
hash.each{|k,v| escaped_hash[k] = v.gsub("\n", CLRF) if v }
|
387
|
+
escaped_hash
|
388
|
+
end
|
389
|
+
|
390
|
+
def patch_nsurl_request(request)
|
391
|
+
request.instance_variable_set("@done_loading", false)
|
392
|
+
|
393
|
+
def request.done_loading?; @done_loading; end
|
394
|
+
def request.done_loading!; @done_loading = true; end
|
395
|
+
end
|
396
|
+
|
397
|
+
def call_delegator_with_response
|
398
|
+
if @delegator.respond_to?(:call)
|
399
|
+
@delegator.call( @response, self )
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
# This is a temporary method used for mocking.
|
404
|
+
def create_connection(request, delegate)
|
405
|
+
NSURLConnection.alloc.initWithRequest(request, delegate:delegate, startImmediately:false)
|
406
|
+
end
|
407
|
+
|
408
|
+
def encode_to_data(string, encoding)
|
409
|
+
string.dataUsingEncoding encoding
|
410
|
+
end
|
411
|
+
|
412
|
+
end; end; end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Response class wrapping the results of a Query's response
|
2
|
+
module BubbleWrap; module HTTP; class Response
|
3
|
+
attr_reader :body
|
4
|
+
attr_reader :headers
|
5
|
+
attr_accessor :status_code, :status_description, :error_message, :error
|
6
|
+
attr_reader :url
|
7
|
+
attr_reader :original_url
|
8
|
+
|
9
|
+
def initialize(values={})
|
10
|
+
self.update(values)
|
11
|
+
end
|
12
|
+
|
13
|
+
def update(values)
|
14
|
+
values.each do |k,v|
|
15
|
+
self.instance_variable_set("@#{k}", v)
|
16
|
+
end
|
17
|
+
update_status_description
|
18
|
+
end
|
19
|
+
|
20
|
+
def ok?
|
21
|
+
status_code.to_s =~ /2\d\d/ ? true : false
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"#<#{self.class}:#{self.object_id} - url: #{self.url}, body: #{self.body}, headers: #{self.headers}, status code: #{self.status_code}, error message: #{self.error_message} >"
|
26
|
+
end
|
27
|
+
alias description to_s
|
28
|
+
|
29
|
+
def update_status_description
|
30
|
+
@status_description = status_code.nil? ? nil : NSHTTPURLResponse.localizedStringForStatusCode(status_code)
|
31
|
+
end
|
32
|
+
end; end; end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bubble-wrap-http
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.6.0.rc1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Aimonetti
|
8
|
+
- Francis Chong
|
9
|
+
- James Harton
|
10
|
+
- Clay Allsopp
|
11
|
+
- Dylan Markow
|
12
|
+
- Jan Weinkauff
|
13
|
+
- Marin Usalj
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
date: 2014-04-16 00:00:00.000000000 Z
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: rake
|
21
|
+
requirement: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - '>='
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
type: :development
|
27
|
+
prerelease: false
|
28
|
+
version_requirements: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
description: BubbleWrap's deprecated HTTP library
|
34
|
+
email:
|
35
|
+
- mattaimonetti@gmail.com
|
36
|
+
- francis@ignition.hk
|
37
|
+
- james@sociable.co.nz
|
38
|
+
- clay.allsopp@gmail.com
|
39
|
+
- dylan@dylanmarkow.com
|
40
|
+
- jan@dreimannzelt.de
|
41
|
+
- mneorr@gmail.com
|
42
|
+
executables: []
|
43
|
+
extensions: []
|
44
|
+
extra_rdoc_files: []
|
45
|
+
files:
|
46
|
+
- README.md
|
47
|
+
- lib/bubble-wrap-http.rb
|
48
|
+
- motion/http/query.rb
|
49
|
+
- motion/http/response.rb
|
50
|
+
- motion/http.rb
|
51
|
+
homepage: https://github.com/rubymotion/BubbleWrap-HTTP
|
52
|
+
licenses:
|
53
|
+
- MIT
|
54
|
+
metadata: {}
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>'
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.3.1
|
69
|
+
requirements: []
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 2.0.3
|
72
|
+
signing_key:
|
73
|
+
specification_version: 4
|
74
|
+
summary: BubbleWrap's deprecated HTTP library
|
75
|
+
test_files: []
|