motion-http 0.2.0 → 1.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a32a0271f1f711f5a11522c48894a06ea9e8924615d38bae2a235572f15d2135
4
- data.tar.gz: 590d6d30eb2889d97f47ed0e072313eb93dd38eb3e6485f0f894e526869f6a79
3
+ metadata.gz: 38301fffb680c2ff73c68cc0cafef260be772a4c09892c8f6706bb415014bdc6
4
+ data.tar.gz: 562ac8347a504aa3c061beeb6555ec54b73d0ec5c6d27266349150c1e4239c45
5
5
  SHA512:
6
- metadata.gz: a01adbafd00b594929e7b7f86de05e55ff60e1040db5e736247dcb5dca8f01d21804c54ae5ecca7f365a6181dfb030794acfb667a93694bf0a0da0cd4a17f0d4
7
- data.tar.gz: 684d0b718b0d84c7e4222cc16c97f39b4bf875e43b752c7d3f72d9e6540b9cc2e938a690a4542678eb5bff4d89f6409197750a4c608df0a48c9e7a327794901f
6
+ metadata.gz: d34d53372cd85ff50915ca586a36fdba710852a62e9f60c511139d0d72887084e974cc7a39faedc02a5737c96d858900e66ef661960572c22970aebccc613e32
7
+ data.tar.gz: 01d2e62e89ed670128c423c4f005383cc2ff46e351c3dc2163cf567a88ee7a5507257c3321cac4ba151f1bb91f2865af9ebd4ce1aae9f0c06b369d46ba63117a
data/README.md CHANGED
@@ -35,11 +35,11 @@ Using `motion-http` is quick and easy. You can use the simple approach for makin
35
35
 
36
36
  The basic syntax for a request looks like this:
37
37
  ```ruby
38
- HTTP.method(url, params, options) do |response|
39
- # this block will be called asynchronously
38
+ HTTP.method(url, options) do |response|
39
+ # this will be called asynchronously
40
40
  end
41
41
  ```
42
- Where `method` can be `get`, `post`, `put`, `patch`, or `delete`.
42
+ Where `method` can be either `get`, `post`, `put`, `patch`, `delete`, `head`, `options`, or `trace`.
43
43
 
44
44
  For example, to make a simple `GET` request:
45
45
  ```ruby
@@ -52,23 +52,27 @@ HTTP.get("http://www.example.com") do |response|
52
52
  end
53
53
  ```
54
54
 
55
- You can specify query params as the second argument:
55
+ If you need to specify query params:
56
56
  ```ruby
57
- HTTP.get("http://www.example.com/search", term: "my search term") do |response|
57
+ HTTP.get("http://www.example.com/search", params: { term: "my search term" }) do |response|
58
58
  # ...
59
59
  end
60
60
  ```
61
61
 
62
- The response object contains the status code, headers, and body from the response as well:
62
+ The response object contains the status code, headers, body, and shortcut methods for checking the response status:
63
63
  ```ruby
64
64
  HTTP.get("http://example.com") do |response|
65
- puts response.status_code
65
+ puts response.status_code.to_s
66
66
  puts response.headers.inspect
67
67
  puts response.body
68
+ response.success? # 2xx status
69
+ response.redirect? # 3xx status
70
+ response.client_error? # 4xx status
71
+ response.server_error? # 5xx status
68
72
  end
69
73
  ```
70
74
 
71
- JSON responses will automatically be parsed when requesting the `response.object`:
75
+ If the response body has a JSON content type it will automatically be parsed when requesting the `response.object`:
72
76
  ```ruby
73
77
  HTTP.get("http://api.example.com/people.json") do |response|
74
78
  if response.success?
@@ -81,17 +85,29 @@ HTTP.get("http://api.example.com/people.json") do |response|
81
85
  end
82
86
  ```
83
87
 
84
- The third argument is a hash of options. Currently the only option supported at this time is `follow_redirects` which defaults to true:
88
+ Use the `follow_redirects` option to specify whether or not to follow redirects. It defaults to true:
85
89
  ```ruby
86
- HTTP.get("http://example.com/redirect", nil, follow_redirects: false) do |response|
90
+ HTTP.get("http://example.com/redirect", follow_redirects: false) do |response|
87
91
  # ...
88
92
  end
89
93
  ```
90
94
 
91
- To make a simple `POST` request, the value passed as the second argument will be encoded as the request body:
95
+ When making a `POST` request, specify the `:form` option and it will automatically be encoded as `application/x-www-form-urlencoded` request body:
92
96
  ```ruby
93
- json = { widget: { name: "Foobar" } }
94
- HTTP.post("http://www.example.com/widgets", json) do |response|
97
+ HTTP.post("http://www.example.com/login", form: { user: 'andrew', pass: 'secret'}) do |response|
98
+ if response.success?
99
+ puts "Authenticated!"
100
+ elsif response.client_error?
101
+ puts "Bad username or password"
102
+ else
103
+ puts "Oops! Something went wrong."
104
+ end
105
+ end
106
+ ```
107
+
108
+ Likewise, to send a JSON encoded request body, use the `:json` option:
109
+ ```ruby
110
+ HTTP.post("http://www.example.com/widgets", json: { widget: { name: "Foobar" } }) do |response|
95
111
  if response.success?
96
112
  puts "Widget created!"
97
113
  elsif response.status_code == 422
@@ -102,11 +118,24 @@ HTTP.post("http://www.example.com/widgets", json) do |response|
102
118
  end
103
119
  ```
104
120
 
105
- `PUT`, `PATCH`, and `DELETE` requests work the same way:
121
+ Request specific headers can also be specified with the `:headers` option (overriding any previously set headers):
122
+ ```ruby
123
+ HTTP.post("http://www.example.com/widgets",
124
+ headers: { 'Content-Type' => 'application/vnd.api+json' },
125
+ json: { widget: { name: "Foobar" } }
126
+ ) do |response|
127
+ # ...
128
+ end
129
+ ```
130
+
131
+ All other HTTP method requests work the same way:
106
132
  ```ruby
107
133
  HTTP.put(url, params) { ... }
108
134
  HTTP.patch(url, params) { ... }
109
135
  HTTP.delete(url, params) { ... }
136
+ HTTP.head(url, params) { ... }
137
+ HTTP.options(url, params) { ... }
138
+ HTTP.trace(url, params) { ... }
110
139
  ```
111
140
 
112
141
  ### Advanced Usage
@@ -117,6 +146,7 @@ A common use case is to create a reusable HTTP client that uses a common base UR
117
146
  client = HTTP::Client.new("http://api.example.com")
118
147
  # Set or replace a single header:
119
148
  client.header "X-API-TOKEN", "abc123xyz"
149
+ client.header["X-API-TOKEN"] = "abc123xyz"
120
150
 
121
151
  # To set or replace multiple headers:
122
152
  client.headers "X-API-TOKEN" => "abc123xyz",
@@ -125,7 +155,7 @@ client.headers "X-API-TOKEN" => "abc123xyz",
125
155
  # Note that it is valid for some headers to appear multiple times (Accept, Vary, etc).
126
156
  # To append multiple headers of the same key:
127
157
  client.add_header "Accept", "application/json"
128
- client.add_header "Accept", "application/vnd.api+json"
158
+ client.headers.add "Accept", "application/json"
129
159
  ```
130
160
 
131
161
  Then you can make your requests relative to the base URL that you specified when creating your client.
@@ -135,6 +165,35 @@ client.get("/people") do |response|
135
165
  end
136
166
  ```
137
167
 
168
+ ### Basic Auth / Token Auth
169
+
170
+ To make Basic Auth requests, either set the credentials before the request, or set it on your client:
171
+
172
+ ```ruby
173
+ HTTP.basic_auth('username', 'password').get('https://example.com/protected')
174
+ # or
175
+ client.basic_auth('username', 'password')
176
+ client.get('/protected')
177
+ ```
178
+
179
+ The `auth` method is another shortcut for setting any value of the Authorization header:
180
+
181
+ ```ruby
182
+ HTTP.auth("Token token=#{my_token}")
183
+ # or
184
+ client.auth("Token token=#{my_token}")
185
+ # same as
186
+ client.headers['Authorization'] = "Token token=#{my_token}"
187
+ ```
188
+
189
+ ### Logging
190
+
191
+ By default, requests and responses will be logged. If you would like to disable this:
192
+
193
+ ```
194
+ HTTP.logger.disable!
195
+ ```
196
+
138
197
  ## Contributing
139
198
 
140
199
  1. Fork it
@@ -1,61 +1,13 @@
1
1
  class Motion
2
2
  class HTTP
3
3
  class Adapter
4
- JSONMediaType = Okhttp3::MediaType.parse("application/json; charset=utf-8")
5
-
6
- def self.client
7
- @client ||= Okhttp3::OkHttpClient.new
8
- end
9
-
10
4
  def self.perform(request, &callback)
11
- http_method = request.http_method
12
- url = request.url
13
- headers = request.headers
14
- params = request.params
15
-
16
- request = OkHttp3::Request::Builder.new
17
- request.url(url) # TODO: encode GET params and append to URL prior to calling this method
18
- headers.each do |key, value|
19
- if value.is_a? Array
20
- value.each {|val| request.addHeader(key, val) }
21
- else
22
- request.header(key, value)
23
- end
24
- end
25
- if http_method != :get
26
- puts "would have set body for #{http_method.to_s.upcase} #{url}"
27
- # body = OkHttp3::RequestBody.create(JSONMediaType, params) # TODO: allow other content types
28
- # request.method(http_method.to_s, body)
29
- end
30
- client.newCall(request.build).enqueue(OkhttpCallback.new(request, callback))
5
+ volley_request = VolleyRequest.create(request, callback)
6
+ queue.add(volley_request)
31
7
  end
32
8
 
33
- class OkhttpCallback
34
- def initialize(request, callback)
35
- @request = request
36
- @callback = callback
37
- end
38
-
39
- def onFailure(call, e)
40
- puts "Error: #{e.getMessage}"
41
- @callback.call(Response.new(@request, nil, Headers.new, e.getMessage))
42
- end
43
-
44
- def onResponse(call, response)
45
- @callback.call(parse_response(response))
46
- end
47
-
48
- def parse_response(response)
49
- headers = Headers.new
50
- i = 0
51
- while i < response.headers.size
52
- key = response.headers.name(i)
53
- value = response.headers.value(i)
54
- headers.add(key, value)
55
- i += 1
56
- end
57
- Response.new(@request, response.code, headers, response.body.string)
58
- end
9
+ def self.queue
10
+ @queue ||= Com::Android::Volley::Toolbox::Volley.newRequestQueue(Motion::HTTP.application_context)
59
11
  end
60
12
  end
61
13
  end
@@ -0,0 +1,13 @@
1
+ # Copied from https://github.com/HipByte/Flow/blob/44283b31a63bc826d2c068557b6357dc1195680b/flow/base64/android/base64.rb
2
+ class Base64
3
+ def self.encode(string)
4
+ bytes = Java::Lang::String.new(string).getBytes("UTF-8")
5
+ Android::Util::Base64.encodeToString(bytes, Android::Util::Base64::NO_WRAP)
6
+ end
7
+
8
+ def self.decode(string)
9
+ java_string = Java::Lang::String.new(string)
10
+ bytes = Android::Util::Base64.decode(java_string, Android::Util::Base64::NO_WRAP)
11
+ Java::Lang::String.new(bytes, "UTF-8")
12
+ end
13
+ end
data/lib/android/json.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # NOTE: Copied from https://github.com/HipByte/Flow/blob/master/flow/json/android/json.rb
2
2
  class JSON
3
- def self.load(str)
3
+ def self.parse(str)
4
4
  tok = Org::JSON::JSONTokener.new(str)
5
5
  obj = tok.nextValue
6
6
  if obj == nil
@@ -0,0 +1,5 @@
1
+ class ParamsEncoder
2
+ def self.encode(arg)
3
+ Java::Net::URLEncoder.encode(arg, 'UTF-8')
4
+ end
5
+ end
@@ -0,0 +1,52 @@
1
+ class VolleyRequest < Com::Android::Volley::Request
2
+ attr_accessor :original_request, :callback
3
+
4
+ METHOD_CODES = {
5
+ get: 0,
6
+ post: 1,
7
+ put: 2,
8
+ delete: 3,
9
+ head: 4,
10
+ options: 5,
11
+ trace: 6,
12
+ patch: 7,
13
+ }
14
+
15
+ def self.create(request, callback)
16
+ volley_request = new(METHOD_CODES[request.http_method], request.url, nil)
17
+ volley_request.original_request = request
18
+ volley_request.headers = request.headers.to_hash
19
+ volley_request.body = request.body
20
+ volley_request.callback = callback
21
+ volley_request
22
+ end
23
+
24
+ def parseNetworkResponse(networkResponse)
25
+ response = build_response(networkResponse)
26
+ Com::Android::Volley::Response.success(response, Com::Android::Volley::Toolbox::HttpHeaderParser.parseCacheHeaders(networkResponse))
27
+ end
28
+
29
+ def deliverResponse(response)
30
+ Motion::HTTP.logger.log_response(response)
31
+ callback.call(response) if callback
32
+ end
33
+
34
+ def deliverError(error)
35
+ if error.networkResponse
36
+ response = build_response(error.networkResponse)
37
+ deliverResponse(response)
38
+ else
39
+ Motion::HTTP.logger.error("Error while requesting #{original_request.url}: #{error.getMessage}")
40
+ error.getStackTrace.each do |line|
41
+ puts line.toString
42
+ end
43
+ response = Motion::HTTP::Response.new(original_request, nil, nil, error.getMessage)
44
+ callback.call(response) if callback
45
+ end
46
+ end
47
+
48
+ def build_response(networkResponse)
49
+ body = parse_body_from_response(networkResponse)
50
+ Motion::HTTP::Response.new(original_request, networkResponse.statusCode, Motion::HTTP::Headers.new(networkResponse.headers), body)
51
+ end
52
+ end
data/lib/cocoa/adapter.rb CHANGED
@@ -16,15 +16,14 @@ class Motion
16
16
  ns_url_request = build_ns_url_request
17
17
  task = @session.dataTaskWithRequest(ns_url_request, completionHandler: -> (data, response, error) {
18
18
  if error
19
- NSLog("Error: %@", error) # TODO: use configurable logging
20
- error_message = error.localizedDescription
21
- error_message += error.userInfo[NSLocalizedDescriptionKey] if error.userInfo[NSLocalizedDescriptionKey]
22
- response = Response.new(@request, response.statusCode, Headers.new(response.allHeaderFields), error_message)
19
+ error_message = "#{error.localizedDescription} #{error.userInfo['NSLocalizedDescriptionKey']}"
20
+ Motion::HTTP.logger.error("Error while requesting #{@request.url}: #{error_message}")
21
+ response = Response.new(@request, response&.statusCode, Headers.new(response&.allHeaderFields), error_message)
23
22
  else
24
23
  response = Response.new(@request, response.statusCode, Headers.new(response.allHeaderFields), data.to_s)
24
+ Motion::HTTP.logger.log_response(response)
25
25
  end
26
- Motion::HTTP.logger.log_response(response)
27
- callback.call(response)
26
+ callback.call(response) if callback
28
27
  })
29
28
  task.resume
30
29
  end
@@ -32,11 +31,18 @@ class Motion
32
31
  def build_ns_url_request
33
32
  ns_url_request = NSMutableURLRequest.alloc.initWithURL(NSURL.URLWithString(@request.url))
34
33
  ns_url_request.HTTPMethod = @request.http_method.to_s.upcase
35
- if @request.params
36
- # TODO: json serialization
37
- ns_url_request.setValue('application/x-www-form-urlencoded', forHTTPHeaderField: 'Content-Type')
38
- ns_url_request.HTTPBody = FormDataSerializer.serialize(@request.params).dataUsingEncoding(NSUTF8StringEncoding)
34
+ @request.headers.each do |key, value|
35
+ if value.is_a? Array
36
+ value.each {|v2| ns_url_request.addValue(v2, forHTTPHeaderField: key) }
37
+ else
38
+ ns_url_request.setValue(value, forHTTPHeaderField: key)
39
+ end
39
40
  end
41
+
42
+ if @request.body
43
+ ns_url_request.HTTPBody = NSString.alloc.initWithString(@request.body).dataUsingEncoding(NSUTF8StringEncoding)
44
+ end
45
+
40
46
  # TODO: add other headers
41
47
  ns_url_request
42
48
  end
@@ -0,0 +1,12 @@
1
+ # Copied from https://github.com/HipByte/Flow/blob/44283b31a63bc826d2c068557b6357dc1195680b/flow/base64/cocoa/base64.rb
2
+ class Base64
3
+ def self.encode(string)
4
+ data = string.dataUsingEncoding(NSUTF8StringEncoding)
5
+ data.base64EncodedStringWithOptions(0)
6
+ end
7
+
8
+ def self.decode(string)
9
+ data = NSData.alloc.initWithBase64EncodedString(string, options: 0)
10
+ NSString.alloc.initWithData(data, encoding: NSUTF8StringEncoding)
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ class ParamsEncoder
2
+ # TODO: check if iOS implements anything more performant/reliable that we should be using.
3
+ def self.encode(arg)
4
+ arg.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/) do |m|
5
+ '%' + m.unpack('H2' * m.bytesize).join('%').upcase
6
+ end.tr(' ', '+')
7
+ end
8
+ end
data/lib/common/http.rb CHANGED
@@ -1,39 +1,54 @@
1
1
  class Motion
2
2
  class HTTP
3
3
  class << self
4
+ attr_accessor :application_context # Android
5
+
4
6
  def logger
5
7
  @logger ||= Logger.new
6
8
  end
7
9
 
8
- def client
9
- @client ||= Client.new
10
+ def client(*args)
11
+ Client.new(*args)
12
+ end
13
+
14
+ def basic_auth(username, password)
15
+ client.basic_auth(username, password)
16
+ end
17
+
18
+ def auth(header_value)
19
+ client.auth(header_value)
10
20
  end
11
21
 
12
- # FIXME: doesn't work on Android
13
- # [:get, :post, :put, :patch, :delete].each do |method|
14
- # define_method "#{method}", do |url, params = nil, options = nil, &callback|
15
- # client.send(method, url, params, options, &callback)
16
- # end
17
- # end
22
+ def get(url, options = nil, &callback)
23
+ client.get(url, options, &callback)
24
+ end
25
+
26
+ def post(url, options = nil, &callback)
27
+ client.post(url, options, &callback)
28
+ end
29
+
30
+ def put(url, options = nil, &callback)
31
+ client.put(url, options, &callback)
32
+ end
18
33
 
19
- def get(url, params = nil, options = nil, &callback)
20
- client.get(url, params, options, &callback)
34
+ def patch(url, options = nil, &callback)
35
+ client.patch(url, options, &callback)
21
36
  end
22
37
 
23
- def post(url, params = nil, options = nil, &callback)
24
- client.post(url, params, options, &callback)
38
+ def delete(url, options = nil, &callback)
39
+ client.delete(url, options, &callback)
25
40
  end
26
41
 
27
- def put(url, params = nil, options = nil, &callback)
28
- client.put(url, params, options, &callback)
42
+ def head(url, options = nil, &callback)
43
+ client.head(url, options, &callback)
29
44
  end
30
45
 
31
- def patch(url, params = nil, options = nil, &callback)
32
- client.patch(url, params, options, &callback)
46
+ def options(url, options = nil, &callback)
47
+ client.options(url, options, &callback)
33
48
  end
34
49
 
35
- def delete(url, params = nil, options = nil, &callback)
36
- client.delete(url, params, options, &callback)
50
+ def trace(url, options = nil, &callback)
51
+ client.trace(url, options, &callback)
37
52
  end
38
53
  end
39
54
  end
@@ -3,9 +3,10 @@ class Motion
3
3
  class Client
4
4
  attr_reader :base_url
5
5
 
6
- def initialize(base_url = nil)
6
+ def initialize(base_url = nil, options = nil)
7
7
  @base_url = base_url || ''
8
- @headers = Headers.new
8
+ options ||= {}
9
+ @headers = Headers.new(options.delete(:headers))
9
10
  end
10
11
 
11
12
  def header(key, value)
@@ -25,31 +26,57 @@ class Motion
25
26
  @headers
26
27
  end
27
28
 
28
- # FIXME: doesn't work on Android for some reason
29
- # [:get, :post, :put, :patch, :delete].each do |method|
30
- # define_method "#{method}", do |path, params = nil, options = nil, &callback|
31
- # Request.new(method, base_url + path, headers, params, options).perform(&callback)
32
- # end
33
- # end
29
+ def basic_auth(username, password)
30
+ header_value = 'Basic ' + Base64.encode("#{username}:#{password}")
31
+ auth(header_value)
32
+ self
33
+ end
34
+
35
+ def auth(header_value)
36
+ @headers.set 'Authorization', header_value
37
+ self
38
+ end
39
+
40
+ def get(path, options = nil, &callback)
41
+ request(:get, path, options, &callback)
42
+ end
43
+
44
+ def post(path, options = nil, &callback)
45
+ request(:post, path, options, &callback)
46
+ end
47
+
48
+ def put(path, options = nil, &callback)
49
+ request(:put, path, options, &callback)
50
+ end
34
51
 
35
- def get(path, params = nil, options = nil, &callback)
36
- Request.new(:get, base_url + path, headers, params, options).perform(&callback)
52
+ def patch(path, options = nil, &callback)
53
+ request(:patch, path, options, &callback)
37
54
  end
38
55
 
39
- def post(path, params = nil, options = nil, &callback)
40
- Request.new(:post, base_url + path, headers, params, options).perform(&callback)
56
+ def delete(path, options = nil, &callback)
57
+ request(:delete, path, options, &callback)
41
58
  end
42
59
 
43
- def put(path, params = nil, options = nil, &callback)
44
- Request.new(:put, base_url + path, headers, params, options).perform(&callback)
60
+ def head(path, options = nil, &callback)
61
+ request(:head, path, options, &callback)
45
62
  end
46
63
 
47
- def patch(path, params = nil, options = nil, &callback)
48
- Request.new(:patch, base_url + path, headers, params, options).perform(&callback)
64
+ def options(path, options = nil, &callback)
65
+ request(:options, path, options, &callback)
49
66
  end
50
67
 
51
- def delete(path, params = nil, options = nil, &callback)
52
- Request.new(:delete, base_url + path, headers, params, options).perform(&callback)
68
+ def trace(path, options = nil, &callback)
69
+ request(:trace, path, options, &callback)
70
+ end
71
+
72
+ def request(http_method, path, options = nil, &callback)
73
+ options ||= {}
74
+ headers_dup = headers.dup
75
+ if options[:headers]
76
+ options.delete(:headers).each {|key, value| headers_dup.set(key, value) }
77
+ end
78
+ options[:headers] = headers_dup
79
+ Request.new(http_method, base_url + path, options).perform(&callback)
53
80
  end
54
81
  end
55
82
  end
@@ -2,12 +2,27 @@ class Motion
2
2
  class HTTP
3
3
  class Headers
4
4
  def initialize(headers = {})
5
- @headers = headers
5
+ @headers = {}
6
+ if headers
7
+ headers.each {|key, value| set(key, value) }
8
+ end
9
+ end
10
+
11
+ def get(key)
12
+ @headers[key.downcase]
13
+ end
14
+ # alias :[] :get # FIXME: doesn't work in Android
15
+ def [](key)
16
+ get(key)
6
17
  end
7
18
 
8
19
  def set(key, value)
9
20
  @headers[key.downcase] = value
10
21
  end
22
+ # alias :[]= :set # FIXME: doesn't work in Android
23
+ def []=(key, value)
24
+ set(key, value)
25
+ end
11
26
 
12
27
  def add(key, value)
13
28
  key = key.downcase
@@ -17,13 +32,22 @@ class Motion
17
32
  end
18
33
  @headers[key] << value
19
34
  end
35
+ # alias :<< :add # FIXME: doesn't work in Android
36
+ def <<(key, value)
37
+ add(key, value)
38
+ end
20
39
 
21
40
  def each(&block)
22
41
  @headers.each(&block)
23
42
  end
24
43
 
25
- def [](key)
26
- @headers[key.downcase]
44
+ def to_hash
45
+ @headers # TODO: flatten array values
46
+ end
47
+
48
+ # FIXME: Android doesn't support dup (Java exception raised: java.lang.CloneNotSupportedException: Class com.yourcompany.motion_http.Headers doesn't implement Cloneable)
49
+ def dup
50
+ Headers.new(@headers)
27
51
  end
28
52
  end
29
53
  end
@@ -1,36 +1,76 @@
1
1
  class Motion
2
2
  class HTTP
3
3
  class Logger
4
- def log(message)
5
- puts message # TODO: add option to enable/disable logging
4
+ attr_reader :enabled
5
+
6
+ def initialize(enabled = true)
7
+ @enabled = enabled
8
+ end
9
+
10
+ def enable!
11
+ @enabled = true
12
+ end
13
+
14
+ def disable!
15
+ @enabled = false
16
+ end
17
+
18
+ def log(message, color = :white)
19
+ puts colorize(color, message) if enabled
20
+ end
21
+
22
+ def error(message)
23
+ puts colorize(:red, message)
6
24
  end
7
25
 
8
26
  def log_request(request)
9
- log "Request:\n#{request.http_method.to_s.upcase} #{request.url}"
27
+ log "\nRequest:\n#{request.http_method.to_s.upcase} #{request.url}", :dark_gray
10
28
 
11
29
  if request.headers
12
30
  request.headers.each do |k,v|
13
- log "#{k}: #{v.inspect}"
31
+ log "#{k}: #{v}", :dark_gray
14
32
  end
15
33
  end
16
34
 
17
- if request.params
18
- # log serialized_params
19
- request.params.each do |k,v|
20
- log "\t#{k}=#{v.inspect}"
21
- end
22
- end
23
- log "\n"
35
+ log(request.body, :dark_gray) if request.body
24
36
  end
25
37
 
26
38
  def log_response(response)
27
- log "Response:"
28
- log "URL: #{response.original_request.url}"
29
- log "Status: #{response.status_code}"
30
- response.headers.each do |key, value|
31
- log "#{key}: #{value}"
39
+ log "\nResponse:", :dark_gray
40
+ if response.original_request
41
+ log "URL: #{response.original_request.url}", :dark_gray
42
+ end
43
+ log "Status: #{response.status_code}", :dark_gray
44
+ response.headers.each do |k,v|
45
+ log "#{k}: #{v}", :dark_gray
46
+ end
47
+ log "\n#{response.body}", :dark_gray
48
+ end
49
+
50
+ def colorize(color, string)
51
+ return string unless simulator? # Only colorize in the simulator
52
+
53
+ code = {
54
+ red: 31,
55
+ dark_gray: 90,
56
+ }[color]
57
+
58
+ if code
59
+ "\e[#{code}m#{string}\e[0m"
60
+ else
61
+ string
62
+ end
63
+ end
64
+
65
+ # Copied from https://github.com/rubymotion/BubbleWrap/blob/8eaf99a0966f2b375e774f5940279a704c10ad29/motion/core/ios/device.rb#L46
66
+ def simulator?
67
+ @simulator_state ||= begin
68
+ if defined?(NSObject) # iOS
69
+ !NSBundle.mainBundle.bundlePath.start_with?('/var/')
70
+ else # android
71
+ false
72
+ end
32
73
  end
33
- log "\n#{response.body}"
34
74
  end
35
75
  end
36
76
  end
@@ -1,14 +1,55 @@
1
1
  class Motion
2
2
  class HTTP
3
3
  class Request
4
- attr_reader :http_method, :url, :headers, :params, :options
4
+ attr_reader :http_method, :url, :headers, :body, :options
5
5
 
6
- def initialize(http_method, url, headers = nil, params = nil, options = nil)
6
+ def initialize(http_method, url, options = nil)
7
7
  @http_method = http_method
8
8
  @url = url
9
- @headers = headers || Headers.new
10
- @params = params
11
- @options = options
9
+ @options = options ||= {}
10
+ @headers = @options.delete(:headers) || Headers.new
11
+ @body = @options.delete(:body)
12
+
13
+ if @options[:params]
14
+ @params = @options.delete(:params)
15
+ flatten_params!
16
+ encode_params!
17
+ @url = "#{url}?#{@params.map{|k,v|"#{k}=#{v}"}.join('&')}"
18
+
19
+ elsif @options[:form]
20
+ @headers['Content-Type'] ||= 'application/x-www-form-urlencoded'
21
+ @params = @options.delete(:form)
22
+ flatten_params!
23
+ encode_params!
24
+ @body = @params.map{|k,v|"#{k}=#{v}"}.join('&')
25
+
26
+ elsif @options[:json]
27
+ @headers['Content-Type'] ||= 'application/json; charset=utf-8'
28
+ @body = @options.delete(:json).to_json
29
+ end
30
+ end
31
+
32
+ def flatten_params!
33
+ new_params = {}
34
+ @params.each do |k,v|
35
+ if v.is_a? Hash
36
+ v.each do |nested_k, nested_v|
37
+ new_params["#{k}[#{nested_k}]"] = nested_v
38
+ end
39
+ else
40
+ new_params[k] = v
41
+ end
42
+ end
43
+ @params = new_params
44
+ flatten_params! if @params.any? {|k,v| v.is_a? Hash }
45
+ end
46
+
47
+ def encode_params!
48
+ new_params = {}
49
+ @params.each do |k,v|
50
+ new_params[ParamsEncoder.encode(k)] = ParamsEncoder.encode(v)
51
+ end
52
+ @params = new_params
12
53
  end
13
54
 
14
55
  def perform(&callback)
@@ -11,8 +11,19 @@ class Motion
11
11
  end
12
12
 
13
13
  def success?
14
- return false unless status_code
15
- status_code >= 200 && status_code < 300
14
+ status_code && (200..299) === status_code
15
+ end
16
+
17
+ def redirect?
18
+ status_code && (300..399) === status_code
19
+ end
20
+
21
+ def client_error?
22
+ status_code && (400..499) === status_code
23
+ end
24
+
25
+ def server_error?
26
+ status_code && (500..599) === status_code
16
27
  end
17
28
 
18
29
  def object
data/lib/motion-http.rb CHANGED
@@ -12,9 +12,8 @@ Motion::Project::App.setup do |app|
12
12
  when :android
13
13
  require "motion-gradle"
14
14
  app.files.unshift(*Dir.glob(File.join(lib_dir_path, "android/**/*.rb")))
15
- app.gradle do
16
- dependency "com.squareup.okhttp3:okhttp:3.9.0"
17
- end
15
+ app.permissions << :internet
16
+ app.gradle { dependency 'com.android.volley:volley:1.1.1' }
18
17
  when :ios, :tvos, :osx, :watchos, :'ios-extension'
19
18
  app.files.unshift(*Dir.glob(File.join(lib_dir_path, "cocoa/**/*.rb")))
20
19
  else
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motion-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Havens
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-15 00:00:00.000000000 Z
11
+ date: 2019-03-28 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A cross-platform HTTP client for RubyMotion that's quick and easy to
14
14
  use.
@@ -20,10 +20,14 @@ extra_rdoc_files: []
20
20
  files:
21
21
  - README.md
22
22
  - lib/android/adapter.rb
23
+ - lib/android/base64.rb
23
24
  - lib/android/json.rb
25
+ - lib/android/params_encoder.rb
26
+ - lib/android/volley_request.rb
24
27
  - lib/cocoa/adapter.rb
25
- - lib/cocoa/form_data_serializer.rb
28
+ - lib/cocoa/base64.rb
26
29
  - lib/cocoa/json.rb
30
+ - lib/cocoa/params_encoder.rb
27
31
  - lib/common/http.rb
28
32
  - lib/common/http/client.rb
29
33
  - lib/common/http/headers.rb
@@ -1,29 +0,0 @@
1
- class FormDataSerializer
2
- def self.serialize(params)
3
- flattened_params = {}
4
- params.each do |k, v|
5
- add_param(flattened_params, k, v)
6
- end
7
- serialized_params = []
8
- flattened_params.each do |k, v|
9
- serialized_params << "#{k}=#{serialize_value(v)}"
10
- end
11
- serialized_params.join('&')
12
- end
13
-
14
- def self.add_param(hash, k, v)
15
- if v.is_a? Hash
16
- v.each do |sub_k, sub_v|
17
- add_param(hash, "#{k}[#{sub_k}]", sub_v)
18
- end
19
- else
20
- hash[k] = v
21
- end
22
- end
23
-
24
- def self.serialize_value(v)
25
- allowed_characters = NSCharacterSet.URLQueryAllowedCharacterSet.mutableCopy
26
- allowed_characters.removeCharactersInString(":#[]@!$&'()*+,;=")
27
- NSString.alloc.initWithString(v.to_s).stringByAddingPercentEncodingWithAllowedCharacters(allowed_characters)
28
- end
29
- end