motion-http 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ee2f69f478379a7838d88c114210624948da74f8
4
+ data.tar.gz: d5ac4b833aec97568819341f555a07ef48507fb3
5
+ SHA512:
6
+ metadata.gz: 32c6a1ade57a803e9a99416d6591d9b6afc510d3dbe06edba804c4f5c35cbe8b36f27282c718ed7231bfb0af22a98984831eba9c7256da2571310dc03a83f62a
7
+ data.tar.gz: ca7d792bb704dcd23da1d1454c138f426eadeb1fba2003750243b053a8df07b9cd2a94494283d58fae2b2b8dde1789b36e3411412e1de4cdcfabb3de78ea820b
data/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # motion-http
2
+
3
+ Motion::HTTP is a cross-platform HTTP Client for RubyMotion that's quick and easy to use.
4
+
5
+ Supported platforms:
6
+ - iOS, macOS, tvOS, watchOS
7
+ - Android
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'motion-http'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+ $ rake pod:install # for iOS apps
19
+ $ rake gradle:install # for Android apps
20
+
21
+ ## Usage
22
+
23
+ Using `Motion::HTTP` is quick and easy. You can use the simple approach for making one-off requests, or the advanced approach of creating a reusable API client for further customization.
24
+
25
+ ### Simple Usage
26
+
27
+ The basic syntax for a simple request looks like this:
28
+ ```ruby
29
+ Motion::HTTP.method(url, params) do |response|
30
+ # this block will be called asynchronously
31
+ end
32
+ ```
33
+
34
+ To make a simple `GET` request:
35
+ ```ruby
36
+ Motion::HTTP.get("http://www.example.com") do |response|
37
+ puts "status code: #{response.status_code}"
38
+ puts "response body: #{response.body}"
39
+ if response.success?
40
+ puts "Success!"
41
+ else
42
+ puts "Oops! Something went wrong."
43
+ end
44
+ end
45
+ ```
46
+
47
+ You can specify query params as the second argument:
48
+ ```ruby
49
+ Motion::HTTP.get("http://www.example.com/search", term: "my search term") do |response|
50
+ # ...
51
+ end
52
+ ```
53
+
54
+ `Motion::HTTP` will automatically parse JSON responses:
55
+ ```ruby
56
+ Motion::HTTP.get("http://api.example.com/people.json") do |response|
57
+ if response.success?
58
+ response.object["people"].each do |person|
59
+ puts "name: #{person["name"]}"
60
+ end
61
+ else
62
+ puts "Error: #{response.object["errors"]}"
63
+ end
64
+ end
65
+ ```
66
+
67
+ To make a simple `POST` request, the value passed as the second argument will be encoded as the request body:
68
+ ```ruby
69
+ json = { widget: { name: "Foobar" } }
70
+ Motion::HTTP.post("http://www.example.com/widgets", json) do |response|
71
+ if response.success?
72
+ puts "Widget created!"
73
+ elsif response.client_error?
74
+ puts "Oops, you did something wrong: #{response.object["error_message"]}"
75
+ else
76
+ puts "Oops! Something else went wrong."
77
+ end
78
+ end
79
+ ```
80
+
81
+ `PUT`, `PATCH`, and `DELETE` requests work the same way:
82
+ ```ruby
83
+ Motion::HTTP.put(url, params)
84
+ Motion::HTTP.patch(url, params)
85
+ Motion::HTTP.delete(url, params)
86
+ ```
87
+
88
+ ### Advanced Usage
89
+
90
+ A common use case is to create a reusable HTTP client that uses a common base URL or request headers.
91
+
92
+ ```ruby
93
+ client = Motion::HTTPClient.new("http://www.example.com")
94
+ # Set or replace a header
95
+ client.header "X-API-TOKEN", "abc123xyz"
96
+ # It is valid for some headers to appear multiple times (Accept, Vary, etc).
97
+ # Use add_header to append multiple headers of the same name.
98
+ client.add_header "Accept", "application/json"
99
+ client.add_header "Accept", "application/vnd.api+json"
100
+ ```
101
+
102
+ Then make your requests relative to the base URL that you specified when creating your client.
103
+ ```ruby
104
+ client.get("/people") do |response|
105
+ # ...
106
+ end
107
+ ```
108
+
109
+ ## Contributing
110
+
111
+ 1. Fork it
112
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
113
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
114
+ 4. Push to the branch (`git push origin my-new-feature`)
115
+ 5. Create new Pull Request
116
+
117
+ ## MIT License
118
+
119
+ Copyright 2018 Andrew Havens
120
+
121
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
122
+
123
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
124
+
125
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,59 @@
1
+ class Motion
2
+ class HTTP
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
+ def self.request(http_method, url, headers, params = nil, &callback)
11
+ puts "starting #{http_method.to_s.upcase} #{url}"
12
+ request = OkHttp3::Request::Builder.new
13
+ request.url(url) # TODO: encode GET params and append to URL prior to calling this method
14
+ headers.each do |key, value|
15
+ if value.is_a? Array
16
+ value.each {|val| request.addHeader(key, val) }
17
+ else
18
+ request.header(key, value)
19
+ end
20
+ end
21
+ if http_method != :get
22
+ puts "would have set body for #{http_method.to_s.upcase} #{url}"
23
+ # body = OkHttp3::RequestBody.create(JSONMediaType, params) # TODO: allow other content types
24
+ # request.method(http_method.to_s, body)
25
+ end
26
+ client.newCall(request.build).enqueue(OkhttpCallback.new(callback))
27
+ end
28
+
29
+ class OkhttpCallback
30
+ def initialize(callback)
31
+ @callback = callback
32
+ end
33
+
34
+ def onFailure(call, e)
35
+ puts "Error: #{e.getMessage}"
36
+ @callback.call(Response.new(nil, Headers.new, e.getMessage))
37
+ end
38
+
39
+ def onResponse(call, response)
40
+ @callback.call(parse_response(response))
41
+ end
42
+
43
+ def parse_response(response)
44
+ headers = Headers.new
45
+ i = 0
46
+ while i < response.headers.size
47
+ key = response.headers.name(i)
48
+ value = response.headers.value(i)
49
+ headers.add(key, value)
50
+ i += 1
51
+ end
52
+ body_string = response.body.string
53
+ json = JSON.load(body_string) if headers['content-type'] =~ /application\/json/
54
+ Response.new(response.code, headers, body_string, json)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,36 @@
1
+ # NOTE: Copied from https://github.com/HipByte/Flow/blob/master/flow/json/android/json.rb
2
+ class JSON
3
+ def self.load(str)
4
+ tok = Org::JSON::JSONTokener.new(str)
5
+ obj = tok.nextValue
6
+ if obj == nil
7
+ raise "Can't deserialize object from JSON"
8
+ end
9
+
10
+ # Transform pure-Java JSON objects to Ruby types.
11
+ convert_java(obj)
12
+ end
13
+
14
+ def self.convert_java(obj)
15
+ case obj
16
+ when Org::JSON::JSONArray
17
+ obj.length.times.map { |i| convert_java(obj.get(i)) }
18
+ when Org::JSON::JSONObject
19
+ iter = obj.keys
20
+ hash = Hash.new
21
+ loop do
22
+ break unless iter.hasNext
23
+ key = iter.next
24
+ value = obj.get(key)
25
+ hash[convert_java(key)] = convert_java(value)
26
+ end
27
+ hash
28
+ when Java::Lang::String
29
+ obj.to_s
30
+ when Org::JSON::JSONObject::NULL
31
+ nil
32
+ else
33
+ obj
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,45 @@
1
+ class Motion
2
+ class HTTP
3
+ class Adapter
4
+ def self.manager
5
+ @manager ||= AFHTTPSessionManager.manager
6
+ end
7
+
8
+ def self.request(http_method, url, headers, params = nil, &callback)
9
+ progress = nil
10
+ on_success = lambda do |task, response_object|
11
+ response = task.response
12
+ callback.call(Response.new(response.statusCode, Headers.new(response.allHeaderFields), nil, response_object))
13
+ end
14
+ on_error = lambda do |operation, error|
15
+ NSLog("Error: %@", error)
16
+ response = operation.response
17
+ status_code = response.statusCode if response
18
+ response_headers = response.allHeaderFields if response
19
+ error_message = error.localizedDescription
20
+ error_message += error.userInfo[NSLocalizedDescriptionKey] if error.userInfo[NSLocalizedDescriptionKey]
21
+ callback.call(
22
+ Response.new(status_code, Headers.new(response_headers), error_message)
23
+ )
24
+ end
25
+
26
+ case http_method
27
+ when :get
28
+ manager.GET url, parameters: params, progress: progress, success: on_success, failure: on_error
29
+ when :post
30
+ manager.POST url, parameters: params, progress: progress, success: on_success, failure: on_error
31
+ when :put
32
+ manager.PUT url, parameters: params, progress: progress, success: on_success, failure: on_error
33
+ when :patch
34
+ manager.PATCH url, parameters: params, progress: progress, success: on_success, failure: on_error
35
+ when :delete
36
+ manager.DELETE url, parameters: params, progress: progress, success: on_success, failure: on_error
37
+ end
38
+
39
+ # FIXME: dynamically calling the method using send results in a crash
40
+ # method_signature = "#{http_method.to_s.upcase}:parameters:progress:success:failure:"
41
+ # # manager.send(method_signature, url, params, nil, on_success, on_error)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,36 @@
1
+ class Motion
2
+ class HTTP
3
+ class << self
4
+ def client
5
+ @client ||= Client.new
6
+ end
7
+
8
+ # FIXME: doesn't work on Android
9
+ # [:get, :post, :put, :patch, :delete].each do |method|
10
+ # define_method "#{method}", do |url, params = nil, &callback|
11
+ # client.send(method, url, params, &callback)
12
+ # end
13
+ # end
14
+
15
+ def get(url, params = nil, &callback)
16
+ client.get(url, params, &callback)
17
+ end
18
+
19
+ def post(url, params = nil, &callback)
20
+ client.post(url, params, &callback)
21
+ end
22
+
23
+ def put(url, params = nil, &callback)
24
+ client.put(url, params, &callback)
25
+ end
26
+
27
+ def patch(url, params = nil, &callback)
28
+ client.patch(url, params, &callback)
29
+ end
30
+
31
+ def delete(url, params = nil, &callback)
32
+ client.delete(url, params, &callback)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ class Motion
2
+ class HTTP
3
+ class Client
4
+ attr_reader :base_url, :headers
5
+
6
+ def initialize(base_url = nil)
7
+ @base_url = base_url || ""
8
+ @headers = Headers.new
9
+ end
10
+
11
+ def header(key, value)
12
+ headers.set(key, value)
13
+ end
14
+
15
+ def add_header(key, value)
16
+ headers.add(key, value)
17
+ end
18
+
19
+ # FIXME: doesn't work on Android
20
+ # [:get, :post, :put, :patch, :delete].each do |method|
21
+ # define_method "#{method}", do |path, params = nil, &callback|
22
+ # Request.new(method, base_url + path, headers, params, &callback).call
23
+ # end
24
+ # end
25
+
26
+ def get(path, params = nil, &callback)
27
+ Request.new(:get, base_url + path, headers, params, &callback).call
28
+ end
29
+
30
+ def post(path, params = nil, &callback)
31
+ Request.new(:post, base_url + path, headers, params, &callback).call
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,29 @@
1
+ class Motion
2
+ class HTTP
3
+ class Headers
4
+ def initialize(headers = {})
5
+ @headers = headers
6
+ end
7
+
8
+ def set(key, value)
9
+ @headers[key.downcase] = value
10
+ end
11
+
12
+ def add(key, value)
13
+ key = key.downcase
14
+ if @headers[key] && !@headers[key].is_a?(Array)
15
+ @headers[key] = [@headers[key]]
16
+ end
17
+ @headers[key] << value
18
+ end
19
+
20
+ def each(&block)
21
+ @headers.each(&block)
22
+ end
23
+
24
+ def [](key)
25
+ @headers[key.downcase]
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ class Motion
2
+ class HTTP
3
+ class Request
4
+ attr_reader :method, :url, :headers, :body, :callback
5
+
6
+ def initialize(method, url, headers = nil, params = nil, &callback)
7
+ @method = method
8
+ @url = url
9
+ @headers = headers || Headers.new
10
+ @body = params # TODO: turn params into body and set content-type?
11
+ @callback = callback || ->(response) {}
12
+ end
13
+
14
+ def call
15
+ # TODO: maybe pass self instead of args
16
+ Adapter.request(method, url, headers, body, &callback)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ class Motion
2
+ class HTTP
3
+ class Response
4
+ attr_reader :status_code, :headers, :body, :object
5
+
6
+ def initialize(status_code, headers, body_string, body_object = nil)
7
+ @status_code = status_code
8
+ @headers = headers
9
+ @body = body_string
10
+ @object = body_object
11
+ end
12
+
13
+ def success?
14
+ return false unless status_code
15
+ status_code >= 200 && status_code < 300
16
+ end
17
+
18
+ def inspect
19
+ "<Motion::HTTP::Response status_code:#{status_code} headers:<#{headers.class}> body:<#{body.class}>>"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ unless defined?(Motion::Project::Config)
4
+ raise "This gem is only intended to be used in a RubyMotion project."
5
+ end
6
+
7
+ lib_dir_path = File.dirname(File.expand_path(__FILE__))
8
+ Motion::Project::App.setup do |app|
9
+ app.files.unshift(*Dir.glob(File.join(lib_dir_path, "common/**/*.rb")))
10
+
11
+ case Motion::Project::App.template
12
+ when :android
13
+ require "motion-gradle"
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
18
+ when :ios, :tvos, :osx, :watchos, :'ios-extension'
19
+ require "motion-cocoapods"
20
+ app.files.unshift(*Dir.glob(File.join(lib_dir_path, "cocoa/**/*.rb")))
21
+ app.pods do
22
+ pod "AFNetworking", "~> 3.1"
23
+ end
24
+ else
25
+ raise "Project template #{Motion::Project::App.template} not supported by motion-http"
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: motion-http
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Havens
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-03-16 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A cross-platform HTTP client for RubyMotion that's quick and easy to
14
+ use.
15
+ email:
16
+ - email@andrewhavens.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - README.md
22
+ - lib/android/adapter.rb
23
+ - lib/android/json.rb
24
+ - lib/cocoa/adapter.rb
25
+ - lib/common/http.rb
26
+ - lib/common/http/client.rb
27
+ - lib/common/http/headers.rb
28
+ - lib/common/http/request.rb
29
+ - lib/common/http/response.rb
30
+ - lib/motion-http.rb
31
+ homepage: https://github.com/andrewhavens/motion-http
32
+ licenses:
33
+ - MIT
34
+ metadata: {}
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubyforge_project:
51
+ rubygems_version: 2.6.13
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: A cross-platform HTTP client for RubyMotion that's quick and easy to use.
55
+ test_files: []