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 +6 -0
- data/README.rdoc +66 -0
- data/Rakefile +29 -0
- data/bin/json_proxy +117 -0
- data/bin/json_proxy_worker +28 -0
- data/bin/json_proxy_worker~ +24 -0
- data/bin/json_proxy~ +117 -0
- data/test/functional/server_spec.rb +138 -0
- data/test/functional/server_spec.rb~ +137 -0
- data/test/functional/spec_helper.rb +14 -0
- data/test/functional/spec_helper.rb~ +16 -0
- data/test/functional/test_service.rb +17 -0
- data/test/functional/test_service.rb~ +0 -0
- data/test/spec/handlers/cache_handler_spec.rb +206 -0
- data/test/spec/handlers/cache_handler_spec.rb~ +206 -0
- data/test/spec/handlers/queue_handler_spec.rb +78 -0
- data/test/spec/handlers/route_handler_spec.rb +72 -0
- data/test/spec/handlers/route_handler_spec.rb~ +72 -0
- data/test/spec/json_spec.rb~ +38 -0
- data/test/spec/queue/queue_daemon_spec.rb +21 -0
- data/test/spec/queue/queue_spec.rb +18 -0
- data/test/spec/spec_helper.rb +9 -0
- data/test/spec/spec_helper.rb~ +8 -0
- data/test/spec/utils/json_spec.rb +50 -0
- data/test/spec/utils/json_spec.rb~ +44 -0
- metadata +111 -0
data/History.txt
ADDED
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
|