motion-http 0.2.0 → 1.0.0

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