crossroads 0.0.1

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