json_proxy 0.5.1

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.
data/History.txt ADDED
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2008-09-29
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
data/README.rdoc ADDED
@@ -0,0 +1,66 @@
1
+ = Readme
2
+
3
+ Json Proxy is a small server/dsl intending for serving JSON documents, and in particular JSON documents created from external webservices.
4
+ JsonProxy supports
5
+ * Caching - with either CouchDb or a simple Filesystem based cache
6
+ * Asynchronous Operation - Json Proxy supports asynchronous data loading via a HTTP polling mechanism
7
+
8
+ == Prequisites
9
+
10
+ * Couchdb (For caching, alternative FileCache exists)
11
+ * Starling (For queue)
12
+ * Rack (For server)
13
+
14
+ == DSL
15
+
16
+ The JsonProxy DSL is used for creating new services
17
+
18
+ np_namespace "echo" do |ns|
19
+ ns.route 'echo', [:message] do |message|
20
+ {:message => message}
21
+ end
22
+ end
23
+
24
+ Each route should return an object with a #to_json method.
25
+
26
+ == Cache
27
+
28
+ All queries are cached, the cache can be controlled by setting the cache_expiry and cache_key options.
29
+
30
+ == JSON Envelope
31
+
32
+ The response is always a JSON object, this object is an envelope around the data.
33
+ The data can be accessed through the data property of the response, the other properties
34
+ of the response are used for meta-information such as the response code and response message.
35
+
36
+ http://localhost/echo/echo?message=blah&jsonp=callback
37
+ => {status : 200, statusMessage : "Ok", data : {message : "blah" } }
38
+
39
+
40
+ == JSON-P
41
+
42
+ If the request has an argument 'jsonp' the server will prefix the response with the argument value, allowing the
43
+ response to be used to trigger callbacks.
44
+
45
+ http://localhost/echo/echo?message=blah&jsonp=callback
46
+ => callback({status : 200, statusMessage : "Ok", data : {message : "blah" } })
47
+
48
+ == Asynchronous Operation
49
+
50
+ JSON Proxy supports asynchronous operation by returning immediately if the data is not in the cache.
51
+ The server returns a 202 status code in this scenario. The operation is then performed in the background,
52
+ while the client polls the server. Once the operation has completed the data will be available in the cache and will be
53
+ sent to the client on the next request.
54
+
55
+ Loop while status is 202
56
+ http://localhost/echo/echo?message=blah
57
+ => {status : 202, statusMessage : "Processing. Please retry shortly" }
58
+ end
59
+ http://localhost/echo/echo?message=blah
60
+ => {status : 200, statusMessage : "Ok", data : {message : "blah" } }
61
+
62
+ Background processing is performed by using a shared queue (implemented using starling). When a cache miss occurs the
63
+ server adds the current url to a queue including an extra force parameter. The QueueDaemon gets urls from the queue and
64
+ fetches them. The force parameter ensures that the operation is processed synchronously.
65
+
66
+
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ begin
3
+ require 'rake'
4
+ rescue LoadError
5
+ puts 'This script should only be accessed via the "rake" command.'
6
+ puts 'Installation: gem install rake -y'
7
+ exit
8
+ end
9
+ require 'rake'
10
+ require 'rake/clean'
11
+ require 'rake/packagetask'
12
+ require 'rake_remote_task'
13
+ require 'spec/rake/spectask'
14
+
15
+ $:.unshift File.dirname(__FILE__) + "/lib"
16
+ require 'json_proxy'
17
+
18
+ $:.unshift File.dirname(__FILE__) + "/tasks/lib"
19
+
20
+ role :app, "gandrew.com"
21
+ role :rubyforge, "rubyforge.org"
22
+
23
+ DEPLOY_ROOT = "/var/web/projects/json_proxy"
24
+ RUBYFORGE_ROOT = "/var/www/gforge-projects/json-proxy/"
25
+
26
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
27
+
28
+
29
+
data/bin/json_proxy ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'rack'
5
+ require 'activesupport'
6
+ require 'configatron'
7
+ require 'choice'
8
+
9
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
10
+ require 'json_proxy'
11
+
12
+
13
+ Choice.options do
14
+
15
+ footer ''
16
+ footer ' Example: json_proxy -p 8080 -c config/config.rb --config=lib/*.rb'
17
+ footer ''
18
+
19
+
20
+ option :port do
21
+ short '-p'
22
+ long '--port=PORT'
23
+ desc 'The default port to listen on (default 4567)'
24
+ cast Integer
25
+ default 4567
26
+ end
27
+
28
+ option :config do
29
+ short '-c'
30
+ long '--config=CONFIG'
31
+ desc 'Specify the configuration file'
32
+ end
33
+
34
+ option :services do
35
+ short '-s'
36
+ long '--services=*SERVICES'
37
+ desc 'Specify the services you would like to run'
38
+ end
39
+
40
+ option :help do
41
+ long '--help'
42
+ desc 'Show this message'
43
+ end
44
+
45
+ option :version do
46
+ short '-v'
47
+ long '--version'
48
+ desc 'Show version'
49
+ action do
50
+ puts "#{JsonProxy::NAME}-#{JsonProxy::VERSION}"
51
+ exit
52
+ end
53
+ end
54
+
55
+ option :workers do
56
+ short '-w'
57
+ long '--workers'
58
+ default 1
59
+ end
60
+ end
61
+
62
+ if Choice.choices[:config]
63
+ require Choice.choices[:config]
64
+ end
65
+
66
+ if Choice.choices[:services]
67
+ include Server::DSL
68
+ Choice.choices[:services].each do |service|
69
+ require service
70
+ end
71
+ end
72
+
73
+
74
+ appThread = Thread.new do
75
+
76
+ # Rack::ShowExceptions.new
77
+ app = Rack::Reloader.new Server::Server.new
78
+
79
+ Rack::Handler::Mongrel.run(app, :Port => Choice.choices[:port]) do |server|
80
+ puts "== json_proxy running on port #{Choice.choices[:port]}"
81
+ trap("INT") do
82
+ puts "\n== Good night."
83
+ exit()
84
+ end
85
+ end
86
+
87
+ end
88
+
89
+ workers = Choice.choices[:workers].to_i
90
+ if workers > 0
91
+ workers.times do
92
+
93
+ workerThread = Thread.new do
94
+ begin
95
+ worker = UrlQueue::QueueDaemon.new
96
+ puts "Worker started"
97
+ sleeping = false
98
+ loop do
99
+ processed = worker.process
100
+ if processed == 0 && !sleeping
101
+ sleeping = true
102
+ # puts "Queue empty \n"
103
+ elsif processed > 0
104
+ # puts "Processed #{processed}\n"
105
+ sleeping = false
106
+ end
107
+ sleep(1)
108
+ end
109
+ rescue Exception =>e
110
+ puts "Worker Exception: " + e
111
+ exit(1)
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ appThread.join
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'rack'
5
+ require 'activesupport'
6
+ require 'configatron'
7
+ require 'choice'
8
+
9
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
10
+ require 'json_proxy'
11
+
12
+ worker = UrlQueue::QueueDaemon.new
13
+
14
+ sleeping = false
15
+
16
+ puts "Worker started"
17
+
18
+ loop do
19
+ processed = worker.process
20
+ if processed == 0 && !sleeping
21
+ sleeping = true
22
+ # puts "Queue empty \n"
23
+ elsif processed > 0
24
+ # puts "Processed #{processed}\n"
25
+ sleeping = false
26
+ end
27
+ sleep(1)
28
+ end
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'rack'
5
+ require 'activesupport'
6
+ require 'configatron'
7
+ require 'choice'
8
+
9
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
10
+ require 'json_proxy'
11
+
12
+ puts "Worker started"
13
+
14
+ loop do
15
+ processed = worker.process
16
+ if processed == 0 && !sleeping
17
+ sleeping = true
18
+ # puts "Queue empty \n"
19
+ elsif processed > 0
20
+ # puts "Processed #{processed}\n"
21
+ sleeping = false
22
+ end
23
+ sleep(1)
24
+ end
data/bin/json_proxy~ ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'rack'
5
+ require 'activesupport'
6
+ require 'configatron'
7
+ require 'choice'
8
+
9
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
10
+ require 'json_proxy'
11
+
12
+
13
+ Choice.options do
14
+
15
+ footer ''
16
+ footer ' Example: json_proxy -p 8080 -c config/config.rb --config=lib/*.rb'
17
+ footer ''
18
+
19
+
20
+ option :port do
21
+ short '-p'
22
+ long '--port=PORT'
23
+ desc 'The default port to listen on (default 4567)'
24
+ cast Integer
25
+ default 4567
26
+ end
27
+
28
+ option :config do
29
+ short '-c'
30
+ long '--config=CONFIG'
31
+ desc 'Specify the configuration file'
32
+ end
33
+
34
+ option :services do
35
+ short '-s'
36
+ long '--services=*SERVICES'
37
+ desc 'Specify the services you would like to run'
38
+ end
39
+
40
+ option :help do
41
+ long '--help'
42
+ desc 'Show this message'
43
+ end
44
+
45
+ option :version do
46
+ short '-v'
47
+ long '--version'
48
+ desc 'Show version'
49
+ action do
50
+ puts "#{JsonProxy::NAME}-#{JsonProxy::VERSION}"
51
+ exit
52
+ end
53
+ end
54
+
55
+ option :workers do
56
+ short '-w'
57
+ long '--workers'
58
+ default 1
59
+ end
60
+ end
61
+
62
+ if Choice.choices[:config]
63
+ require Choice.choices[:config]
64
+ end
65
+
66
+ if Choice.choices[:services]
67
+ include Server::DSL
68
+ Choice.choices[:services].each do |service|
69
+ require service
70
+ end
71
+ end
72
+
73
+
74
+ appThread = Thread.new do
75
+
76
+ # Rack::ShowExceptions.new
77
+ app = Rack::Reloader.new Server::Server.new
78
+
79
+ Rack::Handler::Mongrel.run(app, :Port => Choice.choices[:port]) do |server|
80
+ puts "== json_proxy running on port #{Choice.choices[:port]}"
81
+ trap("INT") do
82
+ puts "\n== Good night."
83
+ exit()
84
+ end
85
+ end
86
+
87
+ end
88
+
89
+ workers = Choice.choices[:workers].to_i
90
+ if workers > 0
91
+ workers.times do
92
+
93
+ workerThread = Thread.new do
94
+ begin
95
+ worker = UrlQueue::QueueDaemon.new
96
+ puts "Worker started"
97
+ sleeping = false
98
+ loop do
99
+ processed = worker.process
100
+ if processed == 0 && !sleeping
101
+ sleeping = true
102
+ # puts "Queue empty \n"
103
+ elsif processed > 0
104
+ # puts "Processed #{processed}\n"
105
+ sleeping = false
106
+ end
107
+ sleep(1)
108
+ end
109
+ rescue Exception =>e
110
+ puts e
111
+ exit(1)
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ appThread.join
@@ -0,0 +1,138 @@
1
+ require 'rubygems'
2
+ require 'net/http'
3
+ require 'active_support'
4
+
5
+ require File.dirname(__FILE__) + '/spec_helper'
6
+ include Server::DSL
7
+ require File.dirname(__FILE__) + '/test_service.rb'
8
+
9
+
10
+
11
+ # Test basic server operation using echo server
12
+
13
+ describe "Server" do
14
+
15
+ def start_server
16
+ app = Server::Server.new
17
+ Thread.new {
18
+ @acc = Rack::Handler::Mongrel.run(app, :Port => 4567) do |server|
19
+ puts "Server running"
20
+ end
21
+ }
22
+ sleep 2
23
+
24
+ end
25
+
26
+ def start_worker
27
+ Thread.new {
28
+ puts "Worker Started"
29
+ worker = UrlQueue::QueueDaemon.new
30
+ sleeping = false
31
+ loop do
32
+ processed = worker.process
33
+ if processed == 0 && !sleeping
34
+ sleeping = true
35
+ puts "Queue empty \n"
36
+ elsif processed > 0
37
+ puts "Processed #{processed}\n"
38
+ sleeping = false
39
+ end
40
+ sleep(1)
41
+ end
42
+ }
43
+ sleep 1
44
+ end
45
+
46
+ before(:all) do
47
+ start_server
48
+ start_worker
49
+ end
50
+
51
+
52
+ before(:each) do
53
+ end
54
+
55
+ def get(msg)
56
+ Net::HTTP.get_response(URI.parse('http://localhost:4567/echo/echo.js?message='+msg))
57
+ end
58
+
59
+ def force_get(msg)
60
+ Net::HTTP.get_response(URI.parse('http://localhost:4567/echo/echo.js?message='+msg+'&force=true'))
61
+ end
62
+
63
+ def random_msg
64
+ "random-#{rand(9999999)}"
65
+ end
66
+
67
+ describe "with missing service" do
68
+ it "response should be 404" do
69
+ response = Net::HTTP.get_response(URI.parse('http://localhost:4567/missing'))
70
+ response.code.should =="200"
71
+ response.body.should =="Handler not found"
72
+ end
73
+ end
74
+
75
+ describe "with echo service" do
76
+
77
+ describe "with cached message" do
78
+ before(:each) do
79
+ force_get "cached"
80
+ end
81
+
82
+ it "response should == message " do
83
+ response = get("cached")
84
+ response.code.should == "200"
85
+ body = ActiveSupport::JSON.decode(response.body)
86
+ body.should be :kind_of, Hash
87
+ body['status'].should == 200
88
+ body['data'].should be :kind_of, Hash
89
+ body['data']['message'].should == "cached"
90
+ end
91
+
92
+ end
93
+
94
+ describe "with uncached message" do
95
+
96
+ it "response should be 202 - Processing until complete then success" do
97
+ msg = random_msg
98
+ response = get(msg)
99
+ response.code.should == "202"
100
+ response.body.should_not be(:empty)
101
+ body = ActiveSupport::JSON.decode(response.body)
102
+ body.should be :kind_of, Hash
103
+ body['status'].should == 202
104
+ count = 1;
105
+ maxCount = 10;
106
+ while response = get(msg)
107
+ puts "Retry #{count} \n"
108
+ if (response.code != "202" || count > maxCount)
109
+ break
110
+ end
111
+ count += 1
112
+ sleep(1)
113
+ end
114
+ response.code.should == "200"
115
+ body = ActiveSupport::JSON.decode(response.body)
116
+ body['status'].should == 200
117
+ body['data'].should be :kind_of, Hash
118
+ body['data']['message'].should == msg
119
+ end
120
+
121
+ end
122
+
123
+ describe "with forced query" do
124
+
125
+ it "response should == message " do
126
+ msg = random_msg
127
+ response = force_get(msg)
128
+ response.code.should == "200"
129
+ body = ActiveSupport::JSON.decode(response.body)
130
+ body['status'].should == 200
131
+ body['data'].should be :kind_of, Hash
132
+ body['data']['message'].should == msg
133
+ end
134
+
135
+ end
136
+
137
+ end
138
+ end