crossroads 0.0.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/bin/crossroads +61 -0
- data/lib/crossroads.rb +29 -0
- data/lib/crossroads/log.rb +43 -0
- data/lib/crossroads/route.rb +48 -0
- data/lib/crossroads/router.rb +69 -0
- data/lib/crossroads/runner.rb +75 -0
- data/lib/crossroads/stomp.rb +85 -0
- metadata +116 -0
data/bin/crossroads
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'crossroads'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
configfile = "/etc/crossroads/crossroads.yaml"
|
8
|
+
daemon = false
|
9
|
+
pidfile = nil
|
10
|
+
|
11
|
+
Version = Crossroads.version
|
12
|
+
|
13
|
+
opt = OptionParser.new
|
14
|
+
|
15
|
+
opt.on("--config [FILE]", "Configuration File") do |v|
|
16
|
+
configfile = v
|
17
|
+
end
|
18
|
+
|
19
|
+
opt.on("--daemonize", "-d", "Daemonize the process") do |v|
|
20
|
+
daemon = true
|
21
|
+
end
|
22
|
+
|
23
|
+
opt.on("--pid [PIDFILE]", "-p", "Write a pidfile") do |v|
|
24
|
+
pidfile = v
|
25
|
+
end
|
26
|
+
|
27
|
+
opt.parse!
|
28
|
+
|
29
|
+
raise "The directory #{configfile} does not exist" unless File.exist?(configfile)
|
30
|
+
|
31
|
+
def stop_and_exit(daemon, pidfile, signal)
|
32
|
+
Crossroads::Log.info("Exiting after signal #{signal}")
|
33
|
+
|
34
|
+
if daemon && pidfile
|
35
|
+
File.unlink(pidfile)
|
36
|
+
end
|
37
|
+
|
38
|
+
exit
|
39
|
+
end
|
40
|
+
|
41
|
+
Signal.trap('INT') { stop_and_exit(daemon, pidfile, :int) }
|
42
|
+
Signal.trap('TERM') { stop_and_exit(daemon, pidfile, :term) }
|
43
|
+
|
44
|
+
cr = Crossroads.runner("t.yaml")
|
45
|
+
|
46
|
+
if daemon
|
47
|
+
raise "Pidfile #{pidfile} exist" if pidfile && File.exist?(pidfile)
|
48
|
+
|
49
|
+
Crossroads.daemonize do
|
50
|
+
if pidfile
|
51
|
+
begin
|
52
|
+
File.open(pidfile, 'w') {|f| f.write(Process.pid) }
|
53
|
+
rescue
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
cr.run!
|
58
|
+
end
|
59
|
+
else
|
60
|
+
cr.run!
|
61
|
+
end
|
data/lib/crossroads.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Crossroads
|
2
|
+
VERSION = "0.0.1"
|
3
|
+
|
4
|
+
["jgrep", "logger", "yaml", "json", "stomp", "crossroads/route", "crossroads/router",
|
5
|
+
"crossroads/log", "crossroads/runner", "crossroads/stomp"].each do |r|
|
6
|
+
require r
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.version
|
10
|
+
VERSION
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.runner(configfile)
|
14
|
+
Runner.new(configfile)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.daemonize
|
18
|
+
fork do
|
19
|
+
Process.setsid
|
20
|
+
exit if fork
|
21
|
+
Dir.chdir('/tmp')
|
22
|
+
STDIN.reopen('/dev/null')
|
23
|
+
STDOUT.reopen('/dev/null', 'a')
|
24
|
+
STDERR.reopen('/dev/null', 'a')
|
25
|
+
|
26
|
+
yield
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Crossroads
|
2
|
+
class Log
|
3
|
+
@configured = false
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def log(msg, severity=:debug)
|
7
|
+
configure unless @configured
|
8
|
+
|
9
|
+
@logger.add(valid_levels[severity.to_sym]) { "#{from} #{msg}" }
|
10
|
+
rescue Exception => e
|
11
|
+
STDERR.puts("Failed to log: #{e.class}: #{e}: original log message: #{severity}: #{msg}")
|
12
|
+
STDERR.puts(e.backtrace.join("\n\t"))
|
13
|
+
end
|
14
|
+
|
15
|
+
def configure(logfile, keep=10, maxsize=1024, level=:info)
|
16
|
+
@logger = Logger.new(logfile, keep, maxsize)
|
17
|
+
@logger.formatter = Logger::Formatter.new
|
18
|
+
@logger.level = valid_levels[level.to_sym]
|
19
|
+
@configured = true
|
20
|
+
end
|
21
|
+
|
22
|
+
# figures out the filename that called us
|
23
|
+
def from
|
24
|
+
from = File.basename(caller[4])
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid_levels
|
28
|
+
{:info => Logger::INFO,
|
29
|
+
:warn => Logger::WARN,
|
30
|
+
:debug => Logger::DEBUG,
|
31
|
+
:fatal => Logger::FATAL,
|
32
|
+
:error => Logger::ERROR}
|
33
|
+
end
|
34
|
+
|
35
|
+
def method_missing(level, *args, &block)
|
36
|
+
super unless [:info, :warn, :debug, :fatal, :error].include?(level)
|
37
|
+
|
38
|
+
log(args[0], level)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Crossroads
|
2
|
+
# expression "headers.foo = 1 and body.bar = 1"
|
3
|
+
#
|
4
|
+
# processor do |headers, body|
|
5
|
+
# target "/queue/foo" if body....
|
6
|
+
# end
|
7
|
+
class Route
|
8
|
+
def initialize(name)
|
9
|
+
@name = name
|
10
|
+
@jgrep = JGrep::JGrep.new
|
11
|
+
@expression = nil
|
12
|
+
@processor = nil
|
13
|
+
@route_count = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_route(file)
|
17
|
+
Log.info("Loading route from file #{file} for router #{@name}")
|
18
|
+
eval(File.read(file))
|
19
|
+
end
|
20
|
+
|
21
|
+
def route!(msg)
|
22
|
+
@targets = []
|
23
|
+
|
24
|
+
@start = Time.now
|
25
|
+
|
26
|
+
if @expression && @processor && @jgrep.match_value({"headers" => msg.headers, "body" => JSON.parse(msg.body)})
|
27
|
+
@processor.call(msg.headers, msg.body)
|
28
|
+
end
|
29
|
+
|
30
|
+
@route_count += 1
|
31
|
+
|
32
|
+
@targets
|
33
|
+
end
|
34
|
+
|
35
|
+
def target(trgt)
|
36
|
+
@targets << {:target => trgt, :name => @name, :process_time => (Time.now - @start).to_f}
|
37
|
+
end
|
38
|
+
|
39
|
+
def processor(&blk)
|
40
|
+
@processor = blk
|
41
|
+
end
|
42
|
+
|
43
|
+
def expression(exp)
|
44
|
+
@expression = exp
|
45
|
+
@jgrep.expression = exp
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Crossroads
|
2
|
+
class Router
|
3
|
+
def initialize(dir)
|
4
|
+
raise "Cannot find routers directory #{dir}" unless File.directory?(dir)
|
5
|
+
|
6
|
+
@routesdir = dir
|
7
|
+
@triggerfile = File.join(@routesdir, "reload.txt")
|
8
|
+
@checktime = 0
|
9
|
+
|
10
|
+
loadroutes
|
11
|
+
end
|
12
|
+
|
13
|
+
def reload_routes
|
14
|
+
if (Time.now - @checktime).to_i > 30
|
15
|
+
if File.exist?(@triggerfile)
|
16
|
+
triggermtime = File::Stat.new(@triggerfile).mtime
|
17
|
+
if triggermtime > @loadtime
|
18
|
+
loadroutes
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
@checktime = Time.now
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def route(msg)
|
27
|
+
reload_routes
|
28
|
+
|
29
|
+
targets = []
|
30
|
+
|
31
|
+
@routes.each do |r|
|
32
|
+
begin
|
33
|
+
targets.concat(r.route!(msg))
|
34
|
+
rescue
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
targets
|
39
|
+
end
|
40
|
+
|
41
|
+
def routefiles
|
42
|
+
Dir.entries(@routesdir).grep(/\.route$/).map do |f|
|
43
|
+
File.join([@routesdir, f])
|
44
|
+
end.sort
|
45
|
+
end
|
46
|
+
|
47
|
+
def loadroute(route)
|
48
|
+
route_name = File.basename(route, ".route")
|
49
|
+
|
50
|
+
r = Route.new(route_name)
|
51
|
+
r.load_route(route)
|
52
|
+
|
53
|
+
@routes << r
|
54
|
+
rescue Exception => e
|
55
|
+
STDERR.puts "Failed to load route #{route}"
|
56
|
+
@routers.delete route_name rescue nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def loadroutes
|
60
|
+
@routes = []
|
61
|
+
|
62
|
+
routefiles.each do |route|
|
63
|
+
loadroute route
|
64
|
+
end
|
65
|
+
|
66
|
+
@loadtime = Time.now
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Crossroads
|
2
|
+
class Runner
|
3
|
+
attr_reader :router
|
4
|
+
|
5
|
+
def initialize(configfile)
|
6
|
+
defaults = {:configdir => "/etc/crossroads",
|
7
|
+
:consume => nil,
|
8
|
+
:logfile => "/dev/stderr",
|
9
|
+
:loglevel => :info,
|
10
|
+
:keeplogs => 10,
|
11
|
+
:maxlogsize => 1024,
|
12
|
+
:daemonie => false,
|
13
|
+
:stomp => [{:server => "localhost",
|
14
|
+
:port => 61613,
|
15
|
+
:user => nil,
|
16
|
+
:password => nil}]}
|
17
|
+
|
18
|
+
@config = defaults.merge(YAML.load_file(configfile))
|
19
|
+
|
20
|
+
raise "Config file does not have input sources defined" unless @config[:consume]
|
21
|
+
|
22
|
+
Log.configure(@config[:logfile], @config[:keeplogs], @config[:maxlogsize], @config[:loglevel])
|
23
|
+
|
24
|
+
Log.info("Crossroads version #{Crossroads.version} starting with configfile #{configfile}")
|
25
|
+
|
26
|
+
@router = Router.new(@config[:configdir])
|
27
|
+
|
28
|
+
subscribe
|
29
|
+
end
|
30
|
+
|
31
|
+
def run!
|
32
|
+
process
|
33
|
+
end
|
34
|
+
|
35
|
+
def process
|
36
|
+
loop do
|
37
|
+
begin
|
38
|
+
msg = @stomp.receive
|
39
|
+
|
40
|
+
targets = route(msg)
|
41
|
+
|
42
|
+
targets.each do |target|
|
43
|
+
headers = msg.headers.merge({"crossroads_route" => target[:name], "crossroads_time" => target[:process_time]})
|
44
|
+
|
45
|
+
["destination", "timestamp", "message-id"].each do |h|
|
46
|
+
headers["crossroads_orig_#{h}"] = headers[h]
|
47
|
+
headers.delete h
|
48
|
+
end
|
49
|
+
|
50
|
+
@stomp.publish(target[:target], msg.body, headers)
|
51
|
+
end
|
52
|
+
rescue Interrupt, SystemExit
|
53
|
+
break
|
54
|
+
rescue Exception => e
|
55
|
+
Log.error("Failed to consume from the middleware: #{e.class}: #{e}")
|
56
|
+
|
57
|
+
sleep 1
|
58
|
+
retry
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def subscribe
|
64
|
+
@stomp = Stomp.new(@config[:stomp])
|
65
|
+
|
66
|
+
[@config[:consume]].flatten.each do |source|
|
67
|
+
@stomp.subscribe source
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def route(msg)
|
72
|
+
@router.route(msg)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Crossroads
|
2
|
+
class Stomp
|
3
|
+
attr_reader :subscriptions
|
4
|
+
|
5
|
+
class EventLogger
|
6
|
+
def on_connecting(params=nil)
|
7
|
+
Log.info("Connection attempt %d to %s" % [params[:cur_conattempts], stomp_url(params)])
|
8
|
+
rescue
|
9
|
+
end
|
10
|
+
|
11
|
+
def on_connected(params=nil)
|
12
|
+
Log.info("Conncted to #{stomp_url(params)}")
|
13
|
+
rescue
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_disconnect(params=nil)
|
17
|
+
Log.info("Disconnected from #{stomp_url(params)}")
|
18
|
+
rescue
|
19
|
+
end
|
20
|
+
|
21
|
+
def on_connectfail(params=nil)
|
22
|
+
Log.info("Connction to #{stomp_url(params)} failed on attempt #{params[:cur_conattempts]}")
|
23
|
+
rescue
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_miscerr(params, errstr)
|
27
|
+
Log.debug("Unexpected error on connection #{stomp_url(params)}: #{errstr}")
|
28
|
+
rescue
|
29
|
+
end
|
30
|
+
|
31
|
+
def stomp_url(params)
|
32
|
+
"stomp://%s@%s:%d" % [params[:cur_login], params[:cur_host], params[:cur_port]]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(config)
|
37
|
+
connect(config)
|
38
|
+
|
39
|
+
@subscriptions = []
|
40
|
+
end
|
41
|
+
|
42
|
+
def connect(config)
|
43
|
+
hosts = []
|
44
|
+
|
45
|
+
[config].flatten.each do |c|
|
46
|
+
host = {}
|
47
|
+
|
48
|
+
host[:host] = c[:server]
|
49
|
+
host[:port] = c[:port]
|
50
|
+
host[:login] = c[:user] if c[:user]
|
51
|
+
host[:passcode] = c[:password] if c[:password]
|
52
|
+
|
53
|
+
hosts << host
|
54
|
+
end
|
55
|
+
|
56
|
+
raise "No hosts defined for Stomp connection" if hosts.size == 0
|
57
|
+
|
58
|
+
connection = {:hosts => hosts, :logger => EventLogger.new}
|
59
|
+
|
60
|
+
@connection = ::Stomp::Connection.new(connection)
|
61
|
+
end
|
62
|
+
|
63
|
+
def subscribe(source)
|
64
|
+
Log.info("Subscribing to #{source}")
|
65
|
+
|
66
|
+
@connection.subscribe source
|
67
|
+
@subscriptions << source
|
68
|
+
end
|
69
|
+
|
70
|
+
def unsubscribe(source)
|
71
|
+
Log.info("Unsubscribing from #{source}")
|
72
|
+
|
73
|
+
@connection.unsubscribe source
|
74
|
+
@subscriptions.delete source
|
75
|
+
end
|
76
|
+
|
77
|
+
def publish(target, msg, headers)
|
78
|
+
@connection.publish(target, msg, headers)
|
79
|
+
end
|
80
|
+
|
81
|
+
def receive
|
82
|
+
@connection.receive
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: crossroads
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- R.I.Pienaar
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-12-15 00:00:00 +00:00
|
19
|
+
default_executable: crossroads
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: stomp
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: jgrep
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 25
|
44
|
+
segments:
|
45
|
+
- 1
|
46
|
+
- 3
|
47
|
+
- 1
|
48
|
+
version: 1.3.1
|
49
|
+
type: :runtime
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: json
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
type: :runtime
|
64
|
+
version_requirements: *id003
|
65
|
+
description: A router that consumes STOMP middleware and routes data to other destinations based on simple rules expressed in Ruby
|
66
|
+
email: rip@devco.net
|
67
|
+
executables:
|
68
|
+
- crossroads
|
69
|
+
extensions: []
|
70
|
+
|
71
|
+
extra_rdoc_files: []
|
72
|
+
|
73
|
+
files:
|
74
|
+
- bin/crossroads
|
75
|
+
- lib/crossroads/stomp.rb
|
76
|
+
- lib/crossroads/router.rb
|
77
|
+
- lib/crossroads/route.rb
|
78
|
+
- lib/crossroads/log.rb
|
79
|
+
- lib/crossroads/runner.rb
|
80
|
+
- lib/crossroads.rb
|
81
|
+
has_rdoc: true
|
82
|
+
homepage: https://github.com/ripienaar/crossroads/
|
83
|
+
licenses: []
|
84
|
+
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
hash: 3
|
96
|
+
segments:
|
97
|
+
- 0
|
98
|
+
version: "0"
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
hash: 3
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
108
|
+
requirements: []
|
109
|
+
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 1.3.7
|
112
|
+
signing_key:
|
113
|
+
specification_version: 3
|
114
|
+
summary: Routing system for JSON messages on STOMP middleware
|
115
|
+
test_files: []
|
116
|
+
|