magellan-rails 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +37 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +76 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +7 -0
- data/bin/magellan-rails +7 -0
- data/gems/magellan-publisher/.gitignore +37 -0
- data/gems/magellan-publisher/Gemfile +11 -0
- data/gems/magellan-publisher/Gemfile.lock +51 -0
- data/gems/magellan-publisher/LICENSE.txt +22 -0
- data/gems/magellan-publisher/README.md +29 -0
- data/gems/magellan-publisher/Rakefile +14 -0
- data/gems/magellan-publisher/lib/magellan/publisher/version.rb +5 -0
- data/gems/magellan-publisher/lib/magellan/publisher.rb +37 -0
- data/gems/magellan-publisher/magellan-publisher.gemspec +27 -0
- data/gems/magellan-publisher/spec/magellan/publisher_spec.rb +39 -0
- data/gems/magellan-publisher/spec/spec_helper.rb +8 -0
- data/lib/generators/install_generator.rb +20 -0
- data/lib/generators/templates/Magellan.yml +115 -0
- data/lib/magellan/extentions/rails/engine.rb +28 -0
- data/lib/magellan/extentions/rails.rb +2 -0
- data/lib/magellan/extentions.rb +2 -0
- data/lib/magellan/rails/executor.rb +35 -0
- data/lib/magellan/rails/railtie.rb +12 -0
- data/lib/magellan/rails/request.rb +111 -0
- data/lib/magellan/rails/response.rb +35 -0
- data/lib/magellan/rails/version.rb +5 -0
- data/lib/magellan/rails.rb +10 -0
- data/lib/magellan/subscriber/base.rb +26 -0
- data/lib/magellan/subscriber/executor.rb +21 -0
- data/lib/magellan/subscriber/mapper.rb +105 -0
- data/lib/magellan/subscriber/request.rb +25 -0
- data/lib/magellan/subscriber/rspec.rb +7 -0
- data/lib/magellan/subscriber/testing/integration.rb +22 -0
- data/lib/magellan/subscriber.rb +10 -0
- data/lib/magellan/worker/config.rb +33 -0
- data/lib/magellan/worker/core.rb +70 -0
- data/lib/magellan/worker/executor.rb +38 -0
- data/lib/magellan/worker.rb +14 -0
- data/lib/magellan.rb +28 -0
- data/magellan-rails.gemspec +28 -0
- data/spec/magellan/rails/request_spec.rb +103 -0
- data/spec/magellan/rails/response_spec.rb +58 -0
- data/spec/magellan/subscriber/mapper_spec.rb +264 -0
- data/spec/magellan/worker/core_spec.rb +70 -0
- data/spec/spec_helper.rb +4 -0
- metadata +167 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
require 'bunny'
|
5
|
+
require 'bunny_mock'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
describe Magellan::Publisher do
|
9
|
+
|
10
|
+
describe "#publish" do
|
11
|
+
|
12
|
+
before do
|
13
|
+
@topic = "topic/aaa"
|
14
|
+
@rabbitmq_topic = "topic.aaa"
|
15
|
+
@payload = "Hello World"
|
16
|
+
@bunny = BunnyMock.new
|
17
|
+
channel = @bunny.create_channel
|
18
|
+
@exchange = @bunny.exchange('test_exchange', type: :direct, durable: false, auto_delete: false)
|
19
|
+
expect(@exchange).to receive(:publish).with(@payload, routing_key: @rabbitmq_topic)
|
20
|
+
|
21
|
+
allow(Bunny ).to receive(:new).and_return(@bunny)
|
22
|
+
allow(@bunny ).to receive(:create_channel).and_return(channel)
|
23
|
+
allow(channel).to receive(:exchange).with('test_exchange', no_declare: true).and_return(@exchange)
|
24
|
+
|
25
|
+
ENV["MAGELLAN_PUBLISH_EXCHANGE"] = "test_exchange"
|
26
|
+
@publisher = Magellan::Publisher.new(host: "127.0.0.1",
|
27
|
+
port: 5672,
|
28
|
+
vhost: "customer1.sample_project",
|
29
|
+
user: "customer1.sample_project",
|
30
|
+
pass: "pasw1")
|
31
|
+
end
|
32
|
+
|
33
|
+
it do
|
34
|
+
@publisher.publish(@topic, @payload)
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
module Magellan
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < ::Rails::Generators::Base
|
7
|
+
source_root File.expand_path("../templates", __FILE__)
|
8
|
+
|
9
|
+
desc "Creates config files for Magellan"
|
10
|
+
|
11
|
+
def copy_magellan_yaml
|
12
|
+
template "Magellan.yml", "Magellan.yml"
|
13
|
+
end
|
14
|
+
|
15
|
+
def rails_4?
|
16
|
+
::Rails::VERSION::MAJOR == 4
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
trdb:
|
2
|
+
image: redis:2.8.13
|
3
|
+
# Dockerfile: https://raw.githubusercontent.com/docker-library/redis/docker-2.8.12/Dockerfile
|
4
|
+
after_tasks:
|
5
|
+
- name: "setup TD data"
|
6
|
+
docker:
|
7
|
+
detach: False
|
8
|
+
name: magellan-tr-data
|
9
|
+
image: groovenauts/magellan-tr-data
|
10
|
+
links:
|
11
|
+
- trdb:redis
|
12
|
+
|
13
|
+
|
14
|
+
rabbitmq:
|
15
|
+
before_tasks:
|
16
|
+
- name: "stop RabbitMQ container"
|
17
|
+
command: docker stop rabbitmq
|
18
|
+
ignore_errors: yes
|
19
|
+
- name: "remove RabbitMQ container"
|
20
|
+
command: docker rm rabbitmq
|
21
|
+
ignore_errors: yes
|
22
|
+
|
23
|
+
- name: "make RabbitMQ log directory"
|
24
|
+
file:
|
25
|
+
path: /var/log/rabbitmq
|
26
|
+
force: yes
|
27
|
+
state: directory
|
28
|
+
|
29
|
+
- name: "change owner /var/log/rabbitmq to rabbitmq"
|
30
|
+
docker:
|
31
|
+
name: rabbitmq_dir_owner
|
32
|
+
image: tutum/rabbitmq
|
33
|
+
volumes:
|
34
|
+
- /var/log/rabbitmq:/var/log/rabbitmq
|
35
|
+
command: "chown -R rabbitmq:rabbitmq /var/log/rabbitmq"
|
36
|
+
|
37
|
+
image: tutum/rabbitmq
|
38
|
+
# Dockerfile: https://raw.githubusercontent.com/tutumcloud/tutum-docker-rabbitmq/master/Dockerfile
|
39
|
+
ports:
|
40
|
+
- 5672:5672
|
41
|
+
- 15672:15672
|
42
|
+
env:
|
43
|
+
- RABBITMQ_USER=magellan
|
44
|
+
- RABBITMQ_PASS=mypass
|
45
|
+
volumes:
|
46
|
+
- /var/log/rabbitmq:/var/log/rabbitmq
|
47
|
+
|
48
|
+
after_tasks:
|
49
|
+
- name: "wait to ready rabbitmq"
|
50
|
+
wait_for: delay=3 port=5672
|
51
|
+
- name: "wait to ready rabbitmq admin"
|
52
|
+
wait_for: port=15672
|
53
|
+
- name: "setup Queues and Exchanges"
|
54
|
+
docker:
|
55
|
+
detach: False
|
56
|
+
name: magellan-setup_tools
|
57
|
+
image: groovenauts/magellan-setup_tools:0.1.1
|
58
|
+
links:
|
59
|
+
- trdb:redis
|
60
|
+
- rabbitmq:rabbitmq
|
61
|
+
command: ruby ./rabbitmq customer1.magellan-rails-example 0.0.1 rails transaction-router pswd1 pswd2
|
62
|
+
|
63
|
+
|
64
|
+
tr:
|
65
|
+
before_tasks:
|
66
|
+
- name: "stop tr container"
|
67
|
+
command: docker stop tr
|
68
|
+
ignore_errors: yes
|
69
|
+
- name: "remove tr container"
|
70
|
+
command: docker rm tr
|
71
|
+
ignore_errors: yes
|
72
|
+
|
73
|
+
image: groovenauts/magellan-transaction-router:0.0.1-with_signature
|
74
|
+
# Dockerfile: https://raw.githubusercontent.com/tengine/magellan-transaction-router-broadleaf/feature/integration01/Dockerfile?token=18912__eyJzY29wZSI6IlJhd0Jsb2I6dGVuZ2luZS9tYWdlbGxhbi10cmFuc2FjdGlvbi1yb3V0ZXItYnJvYWRsZWFmL2ZlYXR1cmUvaW50ZWdyYXRpb24wMS9Eb2NrZXJmaWxlIiwiZXhwaXJlcyI6MTQwNzIzMDg4MX0%3D--2ca5fb4de69eca8cbc5cda747b8716ed85ce89a2
|
75
|
+
ports:
|
76
|
+
- 3000:3000
|
77
|
+
links:
|
78
|
+
- trdb:redis
|
79
|
+
- rabbitmq:rabbitmq
|
80
|
+
tty: True
|
81
|
+
stdin_open: True
|
82
|
+
command: /bin/bash ./boot.sh
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
app:
|
88
|
+
before_tasks:
|
89
|
+
- name: "stop app container"
|
90
|
+
command: docker stop app
|
91
|
+
ignore_errors: yes
|
92
|
+
- name: "remove app container"
|
93
|
+
command: docker rm app
|
94
|
+
ignore_errors: yes
|
95
|
+
|
96
|
+
- name: "make application log directory"
|
97
|
+
file:
|
98
|
+
path: /var/log/application
|
99
|
+
force: yes
|
100
|
+
state: directory
|
101
|
+
|
102
|
+
image: groovenauts/magellan-rails-example
|
103
|
+
# Dockerfile: "./Dockerfile"
|
104
|
+
links:
|
105
|
+
- rabbitmq:rabbitmq
|
106
|
+
env:
|
107
|
+
- VHOST=/customer1.magellan-rails-example
|
108
|
+
- REQUEST_QUEUE=customer1.magellan-rails-example.0.0.1.rails
|
109
|
+
- RESPONSE_EXCHANGE=customer1.magellan-rails-example.reply
|
110
|
+
- RABBITMQ_USER=customer1.magellan-rails-example
|
111
|
+
- RABBITMQ_PASS=pswd2
|
112
|
+
- SECRET_KEY_BASE={{ app_secret_key_base }}
|
113
|
+
volumes:
|
114
|
+
- /var/log/application:/usr/src/app/log
|
115
|
+
command: bundle exec magellan-rails
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require "rails/engine"
|
4
|
+
require "magellan/worker/config"
|
5
|
+
|
6
|
+
module Magellan
|
7
|
+
module Extentions
|
8
|
+
module Rails
|
9
|
+
module Engine
|
10
|
+
def eager_load!
|
11
|
+
config = Magellan::Worker.worker.config
|
12
|
+
unless config[:http_worker]
|
13
|
+
self.config.eager_load_paths -= [::Rails.root.join("app/controllers").to_s]
|
14
|
+
end
|
15
|
+
unless config[:subscriber_worker]
|
16
|
+
self.config.eager_load_paths -= [::Rails.root.join("app/subscribers").to_s]
|
17
|
+
end
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class ::Rails::Engine
|
26
|
+
prepend Magellan::Extentions::Rails::Engine
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'magellan/rails'
|
3
|
+
require "bunny"
|
4
|
+
|
5
|
+
class Magellan::Rails::Executor
|
6
|
+
def initialize(exchange)
|
7
|
+
@exchange = exchange
|
8
|
+
@app = ::Rails.application
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute(reply_to, correlation_id, delivery_tag, request_message)
|
12
|
+
response = Magellan::Rails::Response.new()
|
13
|
+
|
14
|
+
request = Magellan::Rails::Request.new()
|
15
|
+
request.parse_message(request_message)
|
16
|
+
|
17
|
+
begin
|
18
|
+
rack_response = @app.call(request.to_rack_env)
|
19
|
+
response.parse_rack_response(rack_response)
|
20
|
+
rescue Exception => e
|
21
|
+
response.status = '500'
|
22
|
+
response.headers = {'Content-Type' => 'text/plain', 'charset' => 'utf-8'}
|
23
|
+
response.body = 'Internal Server Error'
|
24
|
+
Magellan.logger.error(e)
|
25
|
+
ensure
|
26
|
+
@exchange.publish(
|
27
|
+
response.to_message,
|
28
|
+
{
|
29
|
+
expiration: request.reply_ttl,
|
30
|
+
correlation_id: correlation_id,
|
31
|
+
routing_key: reply_to
|
32
|
+
})
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'magellan/rails'
|
3
|
+
require 'set'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
class Magellan::Rails::Request
|
7
|
+
attr_reader :headers, :options, :body, :body_encoding
|
8
|
+
|
9
|
+
RACK_HEADER = Set.new ['CONTENT_LENGTH', 'CONTENT_TYPE', 'GATEWAY_INTERFACE',
|
10
|
+
'Path-Info', 'Query-String', "Method", "Server-Name", "Server-Port"]
|
11
|
+
|
12
|
+
VALID_ENCODING = %w(plain base64)
|
13
|
+
|
14
|
+
def option(key)
|
15
|
+
options[key]
|
16
|
+
end
|
17
|
+
|
18
|
+
def reply_ttl
|
19
|
+
# TRからのメッセージにワーカー処理結果の送信時に指定するttlが含まれていない場合, 1秒を設定します
|
20
|
+
ttl = options['reply_ttl'] || 1000
|
21
|
+
ttl.to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_message(request_message)
|
25
|
+
# @options = request_message['options']
|
26
|
+
# TRから送られるメッセージのoptionsの型が本来はHash型なのですが、
|
27
|
+
# 配列型になってしまっているため、暫定的に空のHashをセットします
|
28
|
+
@options = {}
|
29
|
+
@headers = request_message['headers']
|
30
|
+
@body_encoding = request_message['body_encoding']
|
31
|
+
@body_encoding = (@body_encoding.present? and VALID_ENCODING.include?(@body_encoding)) ? @body_encoding.to_sym : :plain
|
32
|
+
@body = decode_body(request_message['body'], @body_encoding)
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_rack_env
|
36
|
+
env = Hash.new
|
37
|
+
|
38
|
+
scripe_name = @headers['Path-Info'][@headers['Path-Info'].rindex('/') + 1..-1]
|
39
|
+
query_string = @headers['Query-String']
|
40
|
+
|
41
|
+
# Rackにクエリーストリングを渡す時は
|
42
|
+
query_string.slice!(0) if (query_string[0] == '?')
|
43
|
+
|
44
|
+
ct = @headers["Content-Type"]
|
45
|
+
|
46
|
+
env["CONTENT_LENGTH"] = @body.bytesize
|
47
|
+
env["CONTENT_TYPE"] = ct.dup if ct
|
48
|
+
env["GATEWAY_INTERFACE"] = "CGI/1.1"
|
49
|
+
env["PATH_INFO"] = @headers['Path-Info'] # "/hello/index",
|
50
|
+
env["QUERY_STRING"] = @headers['Query-String'] #
|
51
|
+
|
52
|
+
# TRから送られてきていないので一旦コメントアウト
|
53
|
+
# env["REMOTE_ADDR"] = @peeraddr[3]
|
54
|
+
# env["REMOTE_HOST"] = @peeraddr[2]
|
55
|
+
# env["REMOTE_USER"] = @user
|
56
|
+
|
57
|
+
env["REQUEST_METHOD"] = @headers['Method'] # GET, PUT, DELETE...
|
58
|
+
env["REQUEST_URI"] = "#{env["PATH_INFO"]}?#{env["QUERY_STRING"]}"
|
59
|
+
env["SCRIPT_NAME"] = '' # /scripts/sample?a=c の場合 ルートなので ''
|
60
|
+
env["SERVER_NAME"] = @headers['Server-Name'] # "localhost",
|
61
|
+
env["SERVER_PORT"] = @headers['Server-Port'].to_s # "3000",
|
62
|
+
|
63
|
+
# 一旦決め打ち
|
64
|
+
env["SERVER_PROTOCOL"] = "HTTP/1.1"
|
65
|
+
|
66
|
+
env["SERVER_SOFTWARE"] = 'magellan'
|
67
|
+
|
68
|
+
@headers.each{|key, val|
|
69
|
+
next if RACK_HEADER.include? key
|
70
|
+
name = "HTTP_" + key
|
71
|
+
name.gsub!(/-/o, "_")
|
72
|
+
name.upcase!
|
73
|
+
env[name] = val
|
74
|
+
}
|
75
|
+
|
76
|
+
env["rack.version"] = [1, 5, 2]
|
77
|
+
env["rack.url_scheme"] = "http"
|
78
|
+
env["rack.multithread"] = true
|
79
|
+
env["rack.multiprocess"] = false
|
80
|
+
env["rack.run_once"] = false
|
81
|
+
env["rack.hijack?"] = false
|
82
|
+
|
83
|
+
env['rack.input'] = StringIO.new(@body.to_s)
|
84
|
+
|
85
|
+
# 本来はロガーに渡すべきだが、設計するまで一旦標準エラー出力に出します
|
86
|
+
env["rack.errors"] = $stderr
|
87
|
+
|
88
|
+
env["HTTP_VERSION"] = env["SERVER_PROTOCOL"]
|
89
|
+
env["REQUEST_PATH"] = env["PATH_INFO"]
|
90
|
+
env['rack.logger'] = Magellan.logger
|
91
|
+
|
92
|
+
env
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
# @doc レクエストボディをencodingに応じてデコードする。
|
97
|
+
# 現在対応しているencodingは以下。
|
98
|
+
# - base64: Base64デコードする
|
99
|
+
# - plain: デコードせずそのままの値を返す
|
100
|
+
#
|
101
|
+
# @param [String] body リクエストボディ
|
102
|
+
# @param [String] encoding エンコード形式
|
103
|
+
def decode_body(body, encoding)
|
104
|
+
case encoding.to_sym
|
105
|
+
when :base64 then
|
106
|
+
Base64.decode64(body)
|
107
|
+
else
|
108
|
+
body
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'magellan/rails'
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require 'base64'
|
6
|
+
|
7
|
+
class Magellan::Rails::Response
|
8
|
+
attr_accessor :headers, :body, :status, :body_encoding
|
9
|
+
|
10
|
+
def parse_rack_response(response)
|
11
|
+
@status = response[0]
|
12
|
+
@headers = response[1]
|
13
|
+
@body = ''
|
14
|
+
@body_encoding = :plain
|
15
|
+
body_proxy = response[2]
|
16
|
+
body_proxy.each{|b| @body << b}
|
17
|
+
body_proxy.close
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_message
|
21
|
+
json_generate_errors = [JSON::GeneratorError, Encoding::UndefinedConversionError]
|
22
|
+
begin
|
23
|
+
{
|
24
|
+
headers: @headers,
|
25
|
+
status: @status,
|
26
|
+
body: @body,
|
27
|
+
body_encoding: @body_encoding.to_s,
|
28
|
+
}.to_json
|
29
|
+
rescue *json_generate_errors
|
30
|
+
@body = Base64.strict_encode64(@body)
|
31
|
+
@body_encoding = :base64
|
32
|
+
to_message
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "magellan"
|
3
|
+
require 'magellan/rails/railtie'
|
4
|
+
|
5
|
+
module Magellan::Rails
|
6
|
+
autoload :Executor, 'magellan/rails/executor'
|
7
|
+
autoload :Request, 'magellan/rails/request'
|
8
|
+
autoload :Response, 'magellan/rails/response'
|
9
|
+
autoload :VERSION, 'magellan/rails/version'
|
10
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'magellan/rails'
|
3
|
+
|
4
|
+
class Magellan::Subscriber::Base < AbstractController::Base
|
5
|
+
|
6
|
+
include AbstractController::Callbacks # for (before,after,around)_filter
|
7
|
+
|
8
|
+
def self.logger=(logger)
|
9
|
+
@logger = logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.logger
|
13
|
+
@logger || ::Rails.logger
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :request, :topic, :body
|
17
|
+
def initialize(request)
|
18
|
+
@request = request
|
19
|
+
@topic = request.topic
|
20
|
+
@body = request.body
|
21
|
+
end
|
22
|
+
|
23
|
+
def logger
|
24
|
+
Magellan::Subscriber::Base.logger
|
25
|
+
end
|
26
|
+
end
|