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 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
+