postbin 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
@@ -0,0 +1,3 @@
1
+ ## 0.1.0 (2011-11-22)
2
+
3
+ * Initial gem release, v0.1.0
@@ -0,0 +1,33 @@
1
+ PostBin
2
+ =======
3
+
4
+ PostBin, a simple web service for testing WebHooks (HTTP POST requests).
5
+
6
+ Quick Start
7
+ -----------
8
+
9
+ $ gem install postbin
10
+ $ postbin
11
+ == PostBin online http://127.0.0.1:6969/
12
+ == CTRL+C to stop
13
+
14
+ When running postbin from the command line, requests are stored in a temporary
15
+ file database, they will be lost once the server terminates.
16
+
17
+ Rack Application
18
+ ----------------
19
+
20
+ You can run a more permeant install by running PostBin as a Rack application:
21
+
22
+ # example config.ru
23
+ require 'postbin'
24
+ # path of pstore file to use for storage.
25
+ pstore = File.expand_path(File.join(File.dirname(__FILE__), 'postbin.pstore'))
26
+ # start the server.
27
+ run PostBin::Server.new(pstore)
28
+
29
+ Command Line Options
30
+ --------------------
31
+
32
+ -a, --address HOST listen on HOST address (default: 127.0.0.1)
33
+ -p, --port PORT use PORT number (default: 6969)
@@ -0,0 +1,13 @@
1
+ require 'rubygems' unless defined?(Gem)
2
+ require 'rake/testtask'
3
+
4
+ # by default run unit tests.
5
+ task :default => 'test:unit'
6
+
7
+ namespace :test do
8
+ # unit tests.
9
+ Rake::TestTask.new(:unit) do |task|
10
+ task.test_files = FileList['test/**/*_test.rb']
11
+ task.verbose = true
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # --- use local code if possible, otherwise use gem --------------------------
4
+ begin
5
+ require 'postbin'
6
+ rescue LoadError => err
7
+ require 'rubygems'
8
+ path = ::File.dirname(::File.expand_path(__FILE__))
9
+ $LOAD_PATH.unshift path + '/../lib' if File.directory?(path) && !$LOAD_PATH.include?(path)
10
+ require 'postbin'
11
+ end
12
+
13
+ # --- cli option parsing -----------------------------------------------------
14
+ require 'optparse'
15
+
16
+ # default options.
17
+ options = { :bind => '127.0.0.1', :port => 6969, :server => 'thin', :enviroment => :production }
18
+
19
+ # available options.
20
+ opts = OptionParser.new('', 24, ' ') do |opts|
21
+ opts.banner = 'Usage: postbin [options]'
22
+ opts.separator ''
23
+ opts.separator 'PostBin options:'
24
+ opts.on('-v', '--version', 'show version number') { puts 'PostBin ' + PostBin::Version; exit }
25
+ opts.on('-h', '--help', 'show this message') { puts opts; exit; }
26
+ opts.separator ''
27
+ opts.separator 'Rack options:'
28
+ opts.on('-s', '--server SERVER', 'server (webrick, mongrel, thin, etc.)') { |s| options[:server] = s }
29
+ opts.on('-a', '--address HOST', 'listen on HOST address (default: 127.0.0.1)') { |host| options[:bind] = host }
30
+ opts.on('-p', '--port PORT', 'use PORT number (default: 6969)') { |port| options[:port] = port }
31
+ end.parse!
32
+
33
+ # --- start sinatra server ---------------------------------------------------
34
+ puts "== Starting PostBin on http://#{options[:bind]}:#{options[:port]}"
35
+ PostBin::Server.run!(options)
@@ -0,0 +1,8 @@
1
+ require 'pstore'
2
+ require 'json'
3
+ require 'sinatra/base'
4
+
5
+ require_relative 'postbin/post'
6
+ require_relative 'postbin/server'
7
+ require_relative 'postbin/storage'
8
+ require_relative 'postbin/version'
@@ -0,0 +1,26 @@
1
+ module PostBin
2
+
3
+ # Represents header/body information of a HTTP POST request.
4
+ class Post
5
+ attr_reader :received_at, :headers, :body
6
+
7
+ def initialize(headers, body, received_at = nil)
8
+ @received_at = received_at || Time.now
9
+ @headers = headers
10
+ @body = body
11
+ end
12
+
13
+ # Returns true only if the two posts contain equal data.
14
+ def ==(other)
15
+ return false unless Post === other
16
+ return false unless received_at == other.received_at
17
+ return false unless headers == other.headers
18
+ body == other.body
19
+ end
20
+
21
+ def to_json(*a)
22
+ { received_at: received_at, headers: headers, body: body }.to_json(*a)
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,71 @@
1
+ module PostBin
2
+ class Server < Sinatra::Base
3
+
4
+ dir = File.dirname(File.expand_path(__FILE__))
5
+
6
+ set :views, "#{dir}/server/views"
7
+ set :public_folder, "#{dir}/server/public"
8
+ set :static, true
9
+
10
+ # by default we store in a temp file.
11
+ set :pstore_file, Tempfile.new(['postbin', 'pstore'])
12
+
13
+ # Redirect lost users.
14
+ get '/?' do; redirect '/postbin/overview'; end
15
+ get '/postbin/?' do; redirect '/postbin/overview'; end
16
+
17
+ # Display main overview.
18
+ get '/postbin/overview' do
19
+ storage = PostBin::Storage.new(settings.pstore_file)
20
+ # pull out all urls and there posts.
21
+ @url_hits = storage.hits
22
+
23
+ @url_posts = @url_hits.inject({}) do |result, data|
24
+ url, hits = *data
25
+ result[url] = storage.posts(url)
26
+ result
27
+ end
28
+
29
+ erb :overview
30
+ end
31
+
32
+ # Display urls and there hit count as JSON.
33
+ get '/postbin/hits' do
34
+ storage = PostBin::Storage.new(settings.pstore_file)
35
+
36
+ content_type :json
37
+ storage.hits.to_json
38
+ end
39
+
40
+ # Display posts for the given URL as JSON.
41
+ get %r{/postbin/posts(.*)} do
42
+ url = params[:captures].first
43
+ storage = PostBin::Storage.new(settings.pstore_file)
44
+
45
+ content_type :json
46
+ storage.posts(url).to_json
47
+ end
48
+
49
+ # Catch all for post requests.
50
+ post '*' do
51
+ storage = PostBin::Storage.new(settings.pstore_file)
52
+ post = PostBin::Post.new(client_request_headers, request.body.read)
53
+ storage.store(request.path, post)
54
+ status 201
55
+ end
56
+
57
+ private
58
+ # Returns an array of all client side HTTP request headers.
59
+ def client_request_headers
60
+ # POST /some/url HTTP/1.1
61
+ # Accept: application/json
62
+ # Content-Type: application/json
63
+ headers = request.env.select { |k,v| k.start_with? 'HTTP_' }
64
+ .collect { |pair| [ pair[0].sub(/^HTTP_/, ''), pair[1] ] }
65
+ .collect { |pair| pair.join(': ') }
66
+
67
+ headers
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,24 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
+ <title>PostBin</title>
6
+ </head>
7
+ <body>
8
+ <div id="header">
9
+ <h1>PostBin</h1>
10
+ </div>
11
+
12
+ <div id="main">
13
+ <%= yield %>
14
+ </div>
15
+ <!--
16
+ <div id="footer">
17
+ <div class="block wat-cf">
18
+ <span class="links">http://github.com/lantins/postbin</span>
19
+ <span id="dev-info">v<%= PostBin::Version %></span>
20
+ </div>
21
+ </div>
22
+ -->
23
+ </body>
24
+ </html>
@@ -0,0 +1,45 @@
1
+ <!-- FIXME: replace with some async javascript magic and make pretty -->
2
+
3
+ <h3>URLs &amp; Hits</h3>
4
+
5
+ <table border="1">
6
+ <tr>
7
+ <th >Total Hits</th>
8
+ <th>URL</th>
9
+ </tr>
10
+ <% if @url_hits.empty? %>
11
+ <tr>
12
+ <td colspan="2"><em>No urls &amp; hits to display.</em></td>
13
+ </tr>
14
+ <% else %>
15
+ <% @url_hits.each do |url, hits| %>
16
+ <tr>
17
+ <td><%= hits %></td>
18
+ <td><%= url %></td>
19
+ </tr>
20
+ <% end %>
21
+ <% end %>
22
+ </table>
23
+
24
+ <hr>
25
+
26
+ <h3>Recent Post Requests</h3>
27
+
28
+ <% if @url_posts.empty? %>
29
+ <em>No post requests to display.</em>
30
+ <% else %>
31
+ <% @url_posts.each_pair do |url, posts| %>
32
+ <strong>URL:</strong> <%= url %><br /><br />
33
+ <div style="padding-left: 40px;">
34
+ <% posts.each do |post| %>
35
+ <strong>TIMESTAMP</strong>
36
+ <pre><%= post.received_at %></pre>
37
+ <strong>HEADERS</strong>
38
+ <pre><%= post.headers.join("\n") %></pre>
39
+ <strong>BODY</strong>
40
+ <pre><%= post.body %></pre>
41
+ <hr>
42
+ <% end %>
43
+ </div>
44
+ <% end %>
45
+ <% end %>
@@ -0,0 +1,47 @@
1
+ module PostBin
2
+
3
+ # Storage backend for PostBin, uses PStore under the hood.
4
+ class Storage
5
+
6
+ def initialize(pstore_file)
7
+ # setup file database for storage.
8
+ @db = PStore.new(pstore_file)
9
+ end
10
+
11
+ # Store a post in the database.
12
+ def store(url, post)
13
+ @db.transaction do
14
+ # init if no data exists.
15
+ @db['urls'] ||= {}
16
+ @db['urls'][url] ||= 0
17
+ @db[url] ||= []
18
+ # incr hit count.
19
+ @db['urls'][url] += 1
20
+ # store post.
21
+ @db[url] << post
22
+ end
23
+ end
24
+
25
+ # Returns hash, key being url and value being number of posts received.
26
+ def hits
27
+ @db.transaction(true) do
28
+ @db['urls'] || {}
29
+ end
30
+ end
31
+
32
+ # Returns array of urls that have been posted to.
33
+ def urls
34
+ @db.transaction(true) do
35
+ (@db['urls'] || {}).keys
36
+ end
37
+ end
38
+
39
+ # Returns array of posts for the given url.
40
+ def posts(url)
41
+ @db.transaction(true) do
42
+ @db[url] || []
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ module PostBin
2
+ Version = '0.1.0'
3
+ end
@@ -0,0 +1,31 @@
1
+ require './lib/postbin/version'
2
+
3
+ spec = Gem::Specification.new do |s|
4
+ s.name = 'postbin'
5
+ s.version = PostBin::Version
6
+ s.date = Time.now.strftime('%Y-%m-%d')
7
+ s.summary = 'PostBin, a simple web service for testing WebHooks (HTTP POST requests).'
8
+ s.homepage = 'https://github.com/lantins/postbin'
9
+ s.authors = ['Luke Antins']
10
+ s.email = ['luke@lividpenguin.com']
11
+ s.has_rdoc = false
12
+
13
+ s.files = %w(Rakefile README.md HISTORY.md Gemfile postbin.gemspec)
14
+ s.files += Dir.glob('{test/**/*,lib/**/*}')
15
+ s.require_paths = ['lib']
16
+ s.executables = ['postbin']
17
+ s.default_executable = 'postbin'
18
+
19
+ s.add_dependency('sinatra')
20
+ s.add_development_dependency('rake')
21
+ s.add_development_dependency('yard')
22
+ s.add_development_dependency('minitest')
23
+ s.add_development_dependency('simplecov')
24
+ s.add_development_dependency('rack-test')
25
+ s.add_development_dependency('bundler')
26
+
27
+ s.description = <<-EOL
28
+ A ruby command line/rack application for testing systems that send WebHooks
29
+ using a HTTP POST request. Requests can be viewed via a simple web interface.
30
+ EOL
31
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'test_helper'
2
+
3
+ class TestPost < MiniTest::Unit::TestCase
4
+
5
+ def test_has_a_header_and_body
6
+ post = PostBin::Post.new('header data', 'body data')
7
+ assert_equal 'header data', post.headers, 'post headers not as we expect'
8
+ assert_equal 'body data', post.body, 'post body not as we expect'
9
+ end
10
+
11
+ def test_has_received_at_timestamp
12
+ post = PostBin::Post.new('headers', 'body')
13
+ assert_kind_of Time, post.received_at
14
+ end
15
+
16
+ def test_we_can_set_received_at
17
+ now = Time.now
18
+ post = PostBin::Post.new('headers', 'body', now)
19
+ assert_equal now, post.received_at
20
+ end
21
+
22
+ def test_double_equals_comparison_method
23
+ now = Time.now
24
+ post1 = PostBin::Post.new('headers', 'body', now)
25
+ post2 = PostBin::Post.new('headers', 'body', now)
26
+ assert_equal post1, post2, 'should be the same'
27
+ end
28
+
29
+ end
@@ -0,0 +1,62 @@
1
+ require_relative 'test_helper'
2
+
3
+ class TestServer < MiniTest::Unit::TestCase
4
+ def app
5
+ PostBin::Server.new
6
+ # use a new pstore file for each request.
7
+ PostBin::Server.set :pstore_file, Tempfile.new(['postbin', 'pstore'])
8
+ end
9
+
10
+ def test_roots_should_redirect_to_overview
11
+ get '/'
12
+ follow_redirect!
13
+ assert_equal '/postbin/overview', last_request.path
14
+ assert last_response.ok?
15
+
16
+ get '/postbin'
17
+ follow_redirect!
18
+ assert_equal '/postbin/overview', last_request.path
19
+ assert last_response.ok?
20
+ end
21
+
22
+ def test_overview_returns_200
23
+ get '/postbin/overview'
24
+ assert_equal 200, last_response.status, 'status code should be 200 OK'
25
+ end
26
+
27
+ def test_accepts_post_requests
28
+ post '/hello', 'foobar'
29
+ assert_equal 201, last_response.status, 'status code should be 201 Created'
30
+ end
31
+
32
+ def test_accepts_posts_to_long_urls
33
+ post '/foo/bar/is/very/long.php', 'hello world'
34
+ assert_equal 201, last_response.status, 'status code should be 201 Created'
35
+ end
36
+
37
+ def test_json_url_hits
38
+ get '/postbin/hits'
39
+ assert_equal 200, last_response.status, 'status code should be 200 OK'
40
+ assert_includes last_response.headers['Content-Type'], 'application/json', 'Content-Type header should be json'
41
+ end
42
+
43
+ def test_json_url_posts
44
+ get '/postbin/posts'
45
+ assert_equal 200, last_response.status, 'status code should be 200 OK'
46
+ assert_includes last_response.headers['Content-Type'], 'application/json', 'Content-Type header should be json'
47
+ end
48
+
49
+ def test_post_and_read_back
50
+ # post some data.
51
+ post '/cats', 'they miaow'
52
+ get '/postbin/hits'
53
+ assert_equal '{"/cats":1}', last_response.body
54
+
55
+ # read it back.
56
+ get '/postbin/posts/cats'
57
+ post = JSON.parse(last_response.body)[0]
58
+ assert_equal 'they miaow', post['body']
59
+ end
60
+
61
+
62
+ end
@@ -0,0 +1,50 @@
1
+ require_relative 'test_helper'
2
+
3
+ class TestStorage < MiniTest::Unit::TestCase
4
+
5
+ def test_new_instance
6
+ assert PostBin::Storage.new(Tempfile.new(['postbin', 'pstore']))
7
+ end
8
+
9
+ def test_all_urls_returns_empty_hash_by_default
10
+ storage = PostBin::Storage.new(Tempfile.new(['postbin', 'pstore']))
11
+ assert_empty storage.urls, 'should return empty array by default'
12
+ end
13
+
14
+ def test_can_store_post
15
+ storage = PostBin::Storage.new(Tempfile.new(['postbin', 'pstore']))
16
+ post = PostBin::Post.new('1', '2')
17
+ assert storage.store('/test/url', post)
18
+ end
19
+
20
+ def test_can_get_all_urls
21
+ storage = PostBin::Storage.new(Tempfile.new(['postbin', 'pstore']))
22
+ post = PostBin::Post.new('1', '2')
23
+ storage.store('/foobar', post)
24
+
25
+ expected = ['/foobar']
26
+ assert_equal expected, storage.urls, 'all urls should be a populated array'
27
+ end
28
+
29
+ def test_keeps_count_of_posts_to_same_url
30
+ storage = PostBin::Storage.new(Tempfile.new(['postbin', 'pstore']))
31
+ post = PostBin::Post.new('1', '2')
32
+ storage.store('/foobar', post)
33
+ storage.store('/foobar', post)
34
+ storage.store('/hello', post)
35
+
36
+ hits = storage.hits
37
+ assert_equal 2, hits['/foobar'], 'count should be 2'
38
+ assert_equal 1, hits['/hello'], 'count should be 1'
39
+ end
40
+
41
+ def test_can_retrieve_saved_post_data
42
+ storage = PostBin::Storage.new(Tempfile.new(['postbin', 'pstore']))
43
+ post = PostBin::Post.new('4', '2')
44
+ storage.store('/retrieve', post)
45
+
46
+ posts = storage.posts('/retrieve')
47
+ assert_equal post, posts[0], 'should match the post we stored'
48
+ end
49
+
50
+ end
@@ -0,0 +1,24 @@
1
+ #
2
+ # Test helper init script.
3
+ #
4
+ require 'simplecov'
5
+
6
+ # code coverage groups.
7
+ SimpleCov.start do
8
+ add_filter 'test/'
9
+ end
10
+
11
+ # load our dependencies using bundler.
12
+ require 'bundler/setup'
13
+ Bundler.setup
14
+ require 'minitest/autorun'
15
+ require 'minitest/pride'
16
+ require 'rack/test'
17
+
18
+ # load postbin lib.
19
+ require_relative './../lib/postbin'
20
+
21
+ # extend main TestCase
22
+ class MiniTest::Unit::TestCase
23
+ include Rack::Test::Methods
24
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'test_helper'
2
+
3
+ class TestVersion < MiniTest::Unit::TestCase
4
+ def test_should_have_a_version
5
+ assert PostBin::Version, 'unable to access the version number'
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: postbin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Luke Antins
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-22 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sinatra
16
+ requirement: &2165957000 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2165957000
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &2165956540 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2165956540
36
+ - !ruby/object:Gem::Dependency
37
+ name: yard
38
+ requirement: &2165956060 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *2165956060
47
+ - !ruby/object:Gem::Dependency
48
+ name: minitest
49
+ requirement: &2165955600 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *2165955600
58
+ - !ruby/object:Gem::Dependency
59
+ name: simplecov
60
+ requirement: &2165955180 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *2165955180
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack-test
71
+ requirement: &2165954740 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *2165954740
80
+ - !ruby/object:Gem::Dependency
81
+ name: bundler
82
+ requirement: &2165954320 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *2165954320
91
+ description: ! " A ruby command line/rack application for testing systems that send
92
+ WebHooks\n using a HTTP POST request. Requests can be viewed via a simple web interface.\n"
93
+ email:
94
+ - luke@lividpenguin.com
95
+ executables:
96
+ - postbin
97
+ extensions: []
98
+ extra_rdoc_files: []
99
+ files:
100
+ - Rakefile
101
+ - README.md
102
+ - HISTORY.md
103
+ - Gemfile
104
+ - postbin.gemspec
105
+ - test/post_test.rb
106
+ - test/server_test.rb
107
+ - test/storage_test.rb
108
+ - test/test_helper.rb
109
+ - test/version_test.rb
110
+ - lib/postbin/post.rb
111
+ - lib/postbin/server/views/layout.erb
112
+ - lib/postbin/server/views/overview.erb
113
+ - lib/postbin/server.rb
114
+ - lib/postbin/storage.rb
115
+ - lib/postbin/version.rb
116
+ - lib/postbin.rb
117
+ - bin/postbin
118
+ homepage: https://github.com/lantins/postbin
119
+ licenses: []
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 1.8.10
139
+ signing_key:
140
+ specification_version: 3
141
+ summary: PostBin, a simple web service for testing WebHooks (HTTP POST requests).
142
+ test_files: []
143
+ has_rdoc: false