json_proxy 0.5.1

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