fire-and-forget 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
@@ -0,0 +1,5 @@
1
+ # FireAndForget
2
+
3
+
4
+
5
+ ## 0.1.0 First version
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fire-and-forget.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Matt Aimonetti
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,80 @@
1
+ # Fire and Forget HTTP client
2
+
3
+ Using this gem is probably a very bad idea in at least 98% of the cases
4
+ I can think of. Think of a it as a HTTP client that doesn't care about
5
+ the response and doesn't care about basically anything besides sending
6
+ its payload to a server. An alternative name for this client is
7
+ "scumbag-HTTP-client".
8
+
9
+ So, unless you are really sure you want to use this lib, you're probably
10
+ better off looking at the hundreds other Ruby HTTP libraries.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ gem 'fire-and-forget', :require => 'fire/forget'
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install fire-and-forget
25
+
26
+ ## Usage
27
+
28
+ ```ruby
29
+ FAF.post "http://example.com", {:foo => 'bar'}, {"X-Custom" => true}
30
+ ```
31
+ In the example above, the request will be sent as a JSON request and the
32
+ body (a Ruby hash) will be converted to json if `to_json` is available on
33
+ the object (otherwise `to_s` will be used).
34
+
35
+ ```ruby
36
+ FAF.post "http://example.com", "{\"language\": {\"created_at\": \"2010/11/23 19:47:05 +0000\",\"updated_at\": \"2010/11/23 19:47:05 +0000\",\"active\": true, \"code\": \"en\", \"id\":37}}"
37
+ ```
38
+
39
+ Would send the request passing the JSON string as the body, strings
40
+ aren't converted when passed as the request body.
41
+
42
+ Sometimes, you might want to debug the response or potentially slow down
43
+ the application code. You can pass a block to do that:
44
+
45
+ ```ruby
46
+ FAF.post "http://example.com", {:foo => 'bar'} do |socket|
47
+ sleep(0.02)
48
+ end
49
+ ```
50
+
51
+ or
52
+
53
+ ```ruby
54
+ FAF.post "http://example.com", {:foo => 'bar'} do |socket|
55
+ while output = socket.gets
56
+ print output
57
+ end
58
+ end
59
+ ```
60
+
61
+ This will not "forget" about the response, but instead wait for data to
62
+ come down the socket so we can print.
63
+
64
+ Currently, FAF only supports basic options, no
65
+ authentication unless you pass all the details via the headers.
66
+
67
+ Once the request is sent, the socket is closed which might or might not
68
+ please the server receiving the request (you might want to use a
69
+ block/sleep
70
+ to slow down the closing of the socket if your proxy/cache doesn't like
71
+ that FAF closes the connection right away.
72
+
73
+
74
+ ## Contributing
75
+
76
+ 1. Fork it
77
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
78
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
79
+ 4. Push to the branch (`git push origin my-new-feature`)
80
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'fire/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "fire-and-forget"
8
+ spec.version = FireAndForget::VERSION
9
+ spec.authors = ["Matt Aimonetti"]
10
+ spec.email = ["mattaimonetti@gmail.com"]
11
+ spec.description = %q{Allows developers to fire HTTP requests and not worry about the response (only the initial connection is verified).}
12
+ spec.summary = %q{For whenever you need to push data to an endpoint but don't care about the response (or its status).}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", ">= 1.2"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_runtime_dependency "json"
25
+ end
@@ -0,0 +1,34 @@
1
+ require "fire/version"
2
+ require 'fire/request'
3
+
4
+ module FireAndForget
5
+ def self.get(url, headers={}, &block)
6
+ Request.new(:method => :get, :url => url, :headers => headers, :callback => block).execute
7
+ end
8
+
9
+ def self.post(url, payload, headers={}, &block)
10
+ Request.new(:method => :post, :url => url, :payload => payload, :headers => headers, :callback => block).execute
11
+ end
12
+
13
+ def self.patch(url, payload, headers={}, &block)
14
+ Request.new(:method => :patch, :url => url, :payload => payload, :headers => headers, :callback => block).execute
15
+ end
16
+
17
+ def self.put(url, payload, headers={}, &block)
18
+ Request.new(:method => :put, :url => url, :payload => payload, :headers => headers, :callback => block).execute
19
+ end
20
+
21
+ def self.delete(url, headers={}, &block)
22
+ Request.new(:method => :delete, :url => url, :headers => headers, :callback => block).execute
23
+ end
24
+
25
+ def self.head(url, headers={}, &block)
26
+ Request.new(:method => :head, :url => url, :headers => headers, :callback => block).execute
27
+ end
28
+
29
+ def self.options(url, headers={}, &block)
30
+ Request.new(:method => :options, :url => url, :headers => headers, :callback => block).execute
31
+ end
32
+ end
33
+
34
+ FAF = FireAndForget
@@ -0,0 +1,96 @@
1
+ require 'cgi'
2
+ require 'socket'
3
+ require 'uri'
4
+ begin
5
+ require 'json'
6
+ rescue LoadError
7
+ puts "The json lib wasn't available, payload objects won't be automatically converted to json."
8
+ end
9
+
10
+ module FireAndForget
11
+ class Request
12
+ attr_reader :method, :url, :content_type, :payload, :headers, :callback
13
+
14
+ def initialize(args)
15
+ @method = args[:method] || raise(ArgumentError.new("must pass :method"))
16
+ @headers = args[:headers] || {}
17
+ raise ArgumentError, ":headers should be nil or a Hash" unless (@headers.respond_to?(:keys) && @headers.respond_to?(:each))
18
+ @content_type = args[:content_type] || headers['Content-Type'] || "application/json"
19
+ @headers = {'Content-Type' => @content_type}.merge(headers)
20
+ if args[:url]
21
+ @url = args[:url]
22
+ else
23
+ raise ArgumentError, "must pass :url"
24
+ end
25
+ @callback = args[:callback]
26
+ @payload = args[:payload]
27
+ @args = args
28
+ end
29
+
30
+ def execute
31
+ uri = URI.parse(url)
32
+ req = []
33
+ req << "#{method.respond_to?(:upcase) ? method.upcase : method.to_s.upcase} #{uri.request_uri} HTTP/1.0"
34
+ if uri.port != 80
35
+ req << "Host: #{uri.host}:#{uri.port}"
36
+ else
37
+ req << "Host: #{uri.host}"
38
+ end
39
+ req << "User-Agent: FAF #{FireAndForget::VERSION}"
40
+ req << "Accept: */*"
41
+ processed_headers.each do |part|
42
+ req << part
43
+ end
44
+ req << "Content-Length: #{body_length}"
45
+
46
+ socket = TCPSocket.open(uri.host, uri.port)
47
+ req.each do |req_part|
48
+ socket.puts(req_part + "\r\n")
49
+ end
50
+ #puts (req << body).inspect
51
+ socket.puts "\r\n"
52
+ socket.puts body
53
+ # For debugging purposes you can pass a block to read the socket or sleep for a bit.
54
+ # You can also set a callback when creating the instance and it will be called passing the socket.
55
+ if callback
56
+ callback.call(socket)
57
+ elsif block_given?
58
+ yield(socket)
59
+ end
60
+ ensure
61
+ socket.close if socket
62
+ end
63
+
64
+ def body
65
+ if @body
66
+ @body
67
+ else
68
+ if payload.nil?
69
+ @body = ""
70
+ elsif payload.is_a?(String)
71
+ @body = payload
72
+ elsif payload.respond_to?(:to_json)
73
+ @body = payload.to_json
74
+ else
75
+ @body = ""
76
+ end
77
+ end
78
+ end
79
+
80
+ def body_length
81
+ body ? body.bytesize : 0
82
+ end
83
+
84
+ def processed_headers
85
+ chunks = []
86
+ headers.each do |key, value|
87
+ if key.is_a? Symbol
88
+ key = key.to_s.split(/_/).map { |w| w.capitalize }.join('-')
89
+ end
90
+ chunks << "#{key}: #{value.to_s}"
91
+ end
92
+ chunks
93
+ end
94
+
95
+ end
96
+ end
@@ -0,0 +1,3 @@
1
+ module FireAndForget
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe FireAndForget::Request do
4
+ describe "#initialize" do
5
+
6
+ def new_req(args)
7
+ FireAndForget::Request.new(args)
8
+ end
9
+
10
+ def min_valid_args
11
+ {:method => :get, :url => "http://matt.aimonetti.net/"}
12
+ end
13
+
14
+ it "requires a method argument" do
15
+ lambda{ new_req({}) }.should raise_error(ArgumentError, "must pass :method")
16
+ end
17
+
18
+ it "requires a url argument" do
19
+ lambda{ new_req(:method => :get) }.should raise_error(ArgumentError, "must pass :url")
20
+ end
21
+
22
+ it "requires both a method and url" do
23
+ lambda{ new_req(:method => :get, :url => "http://google.com") }.should_not raise_error
24
+ end
25
+
26
+ it "takes custom headers" do
27
+ req = new_req(min_valid_args.merge({:headers => {"X-Request-Id" => 42}}))
28
+ req.headers["X-Request-Id"].should eq(42)
29
+ end
30
+
31
+ it "has default headers" do
32
+ req = new_req(min_valid_args)
33
+ req.headers.should_not be_nil
34
+ req.headers.should_not be_empty
35
+ end
36
+
37
+ describe "content type" do
38
+
39
+ it "defaults to json" do
40
+ req = new_req(min_valid_args)
41
+ req.headers['Content-Type'].should eq("application/json")
42
+ end
43
+
44
+ it "can be set via argument" do
45
+ req = new_req(min_valid_args.merge(:content_type => 'text/xml'))
46
+ req.content_type.should eq('text/xml')
47
+ req.headers['Content-Type'].should eq('text/xml')
48
+ end
49
+
50
+ it "can be set via headers" do
51
+ req = new_req(min_valid_args.merge(:headers => {'Content-Type' => 'text/xml'}))
52
+ req.content_type.should eq('text/xml')
53
+ req.headers['Content-Type'].should eq('text/xml')
54
+ end
55
+
56
+ end
57
+
58
+ describe "body" do
59
+
60
+ it "is extracted from the payload as-is, if passed as a string" do
61
+ text = "this is a test"
62
+ req = new_req(min_valid_args.merge(:payload => text))
63
+ req.body.should eq(text)
64
+ end
65
+
66
+ it "becomes an empty string if passed as nil" do
67
+ req = new_req(min_valid_args)
68
+ req.body.should eq("")
69
+ end
70
+
71
+ it "is converted to json if it's not a string or nil" do
72
+ payload = {:foo => :bar}
73
+ req = new_req(min_valid_args.merge(:payload => payload))
74
+ req.body.should eq(payload.to_json)
75
+ end
76
+ end
77
+
78
+ describe "converted headers" do
79
+ it "are represented as an array of strings" do
80
+ req = new_req(min_valid_args)
81
+ req.processed_headers.each do |header_line|
82
+ header_line.is_a?(String).should be_true
83
+ header_line.should match(/.*?:\s.+/)
84
+ end
85
+ end
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,2 @@
1
+ $: << File.expand_path(File.dirname(__FILE__), '../lib')
2
+ require 'fire/forget'
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fire-and-forget
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Matt Aimonetti
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '1.2'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '1.2'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: json
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Allows developers to fire HTTP requests and not worry about the response
79
+ (only the initial connection is verified).
80
+ email:
81
+ - mattaimonetti@gmail.com
82
+ executables: []
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - .gitignore
87
+ - .rspec
88
+ - CHANGELOG.md
89
+ - Gemfile
90
+ - LICENSE.txt
91
+ - README.md
92
+ - Rakefile
93
+ - fire-and-forget.gemspec
94
+ - lib/fire/forget.rb
95
+ - lib/fire/request.rb
96
+ - lib/fire/version.rb
97
+ - spec/request_spec.rb
98
+ - spec/spec_helper.rb
99
+ homepage: ''
100
+ licenses:
101
+ - MIT
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ segments:
113
+ - 0
114
+ hash: 3818509771490622433
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ! '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ segments:
122
+ - 0
123
+ hash: 3818509771490622433
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 1.8.24
127
+ signing_key:
128
+ specification_version: 3
129
+ summary: For whenever you need to push data to an endpoint but don't care about the
130
+ response (or its status).
131
+ test_files:
132
+ - spec/request_spec.rb
133
+ - spec/spec_helper.rb