jenode 0.0.7-java

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c9e0e8fa2c4cc37e19770a6ad5dd6492726f0b74
4
+ data.tar.gz: a9f9252de5ae787f8306710492147cefdc7369dc
5
+ SHA512:
6
+ metadata.gz: eeecb5022bd3d48eb32b66b06c5dc14fdd189ed7380c7d66dcb3a85477ea8ece2b4b6d79b4625d2450f916a522644224c4d9d4694613aa253e0bafc810c8756a
7
+ data.tar.gz: 6ca1450a7eaa0c3888f1f7e54997b2ad429e5d5f7783e2446b249d1dfd296f4f74667a405fd8f358d92a64424dffa64ad5b62595d3a5571595979bbf33ec67d4
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ .DS_Store
2
+ /tmp
3
+ /*.gem
4
+ /templates
5
+ /contact_lists
6
+ .tags
7
+ /log
8
+ /pkg
9
+ .bundle/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in enode-new.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,48 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ jenode (0.0.6-java)
5
+ dkim (~> 1.0.0)
6
+ jrjackson (~> 0.2.0)
7
+ march_hare (~> 2.8.0)
8
+ multi_json (~> 1.10.0)
9
+ nokogiri (~> 1.6.0)
10
+ rack (~> 1.6.0)
11
+ thor (~> 0.19.0)
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ diff-lcs (1.2.5)
17
+ dkim (1.0.0)
18
+ jrjackson (0.2.9)
19
+ march_hare (2.8.0-java)
20
+ multi_json (1.10.1)
21
+ nokogiri (1.6.6.2-java)
22
+ rack (1.6.4)
23
+ rake (10.3.2)
24
+ rr (1.1.2)
25
+ rspec (3.1.0)
26
+ rspec-core (~> 3.1.0)
27
+ rspec-expectations (~> 3.1.0)
28
+ rspec-mocks (~> 3.1.0)
29
+ rspec-core (3.1.7)
30
+ rspec-support (~> 3.1.0)
31
+ rspec-expectations (3.1.2)
32
+ diff-lcs (>= 1.2.0, < 2.0)
33
+ rspec-support (~> 3.1.0)
34
+ rspec-mocks (3.1.3)
35
+ rspec-support (~> 3.1.0)
36
+ rspec-support (3.1.2)
37
+ thor (0.19.1)
38
+
39
+ PLATFORMS
40
+ java
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ bundler (~> 1.8.0)
45
+ jenode!
46
+ rake (~> 10.3.0)
47
+ rr (~> 1.1.0)
48
+ rspec (~> 3.1.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 vad4msiu
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Enode::New
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'enode-new'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install enode-new
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/enode-new/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/jenode ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env jruby
2
+
3
+ lib = File.expand_path('../../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'thor'
7
+ require 'jenode'
8
+
9
+ class EnodeLauncherCLI < Thor
10
+ THREADS_PER_IP = 50
11
+ DEFAULT_PREFETCH = 10 * THREADS_PER_IP
12
+
13
+ desc "start ...ARGS", "Run enode"
14
+
15
+ option 'exclude', aliases: "-e", desc: "Exclude IPs (separator ',')", default: ''
16
+ option 'queue_address', aliases: "-q", desc: "Queue address", default: 'localhost'
17
+ option 'queue_user', aliases: "-u", desc: "Queue user", default: 'enode'
18
+ option 'queue_password', aliases: "-p", desc: "Queue password", required: true
19
+ option 'threads_per_ip', aliases: "-n", desc: "Thread per one IP (default #{ THREADS_PER_IP })", default: THREADS_PER_IP
20
+ option 'prefetch', aliases: "-c", desc: "Prefetch (default #{ DEFAULT_PREFETCH })", default: DEFAULT_PREFETCH
21
+
22
+ def start
23
+ ips = Socket.getifaddrs.select {|i| i.addr.ipv4? }.map { |i| i.addr.ip_address }
24
+ exclude_ips = options[:exclude].split(',').map(&:strip)
25
+ ips.delete_if { |q|
26
+ exclude_ips.include?(q)
27
+ }
28
+
29
+ Jenode.run(
30
+ queue_address: options[:queue_address],
31
+ queue_user: options[:queue_user],
32
+ queue_password: options[:queue_password],
33
+ threads_per_ip: options[:threads_per_ip].to_i,
34
+ prefetch: options[:prefetch].to_i,
35
+ ips: ips
36
+ )
37
+ end
38
+ end
39
+
40
+ EnodeLauncherCLI.start
data/jenode.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "jenode"
6
+ spec.version = '0.0.7'
7
+ spec.authors = ["vad4msiu"]
8
+ spec.email = ["vad4msiu@gmail.com"]
9
+ spec.summary = %q{Write a short summary. Required.}
10
+ spec.description = %q{Write a longer description. Optional.}
11
+ spec.homepage = ""
12
+ spec.license = "MIT"
13
+ spec.platform = 'java'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency 'multi_json', '~> 1.10.0'
21
+ spec.add_dependency 'jrjackson', '~> 0.2.0'
22
+ spec.add_dependency 'thor', '~> 0.19.0'
23
+ spec.add_dependency 'march_hare', '~> 2.8.0'
24
+ spec.add_dependency 'dkim', '~> 1.0.0'
25
+ spec.add_dependency 'rack', '~> 1.6.0'
26
+ spec.add_dependency 'nokogiri', '~> 1.6.0'
27
+
28
+ spec.add_development_dependency 'bundler', '~> 1.8.0'
29
+ spec.add_development_dependency 'rake', '~> 10.3.0'
30
+ spec.add_development_dependency 'rspec', '~> 3.1.0'
31
+ spec.add_development_dependency 'rr', '~> 1.1.0'
32
+ end
@@ -0,0 +1,26 @@
1
+ # Taken from the mail gem.
2
+ class String #:nodoc:
3
+ CRLF = "\r\n"
4
+ LF = "\n"
5
+
6
+ if RUBY_VERSION >= '1.9'
7
+ # This 1.9 only regex can save a reasonable amount of time (~20%)
8
+ # by not matching "\r\n" so the string is returned unchanged in
9
+ # the common case.
10
+ CRLF_REGEX = Regexp.new("(?<!\r)\n|\r(?!\n)")
11
+ else
12
+ CRLF_REGEX = /\n|\r\n|\r/
13
+ end
14
+
15
+ def to_crlf
16
+ to_str.gsub(CRLF_REGEX, CRLF)
17
+ end
18
+
19
+ def to_lf
20
+ to_str.gsub(/\r\n|\r/, LF)
21
+ end
22
+
23
+ def to_quoted_printable
24
+ [to_lf].pack("M").to_crlf
25
+ end
26
+ end
data/lib/jenode.rb ADDED
@@ -0,0 +1,52 @@
1
+ lib = File.expand_path('../../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ ENV['APP_ENV'] ||= 'development'
5
+
6
+ require 'resolv'
7
+ require 'openssl'
8
+ require 'dkim'
9
+ require "march_hare"
10
+ require "multi_json"
11
+ require "jrjackson"
12
+ require "base64"
13
+ require "nokogiri"
14
+ require "rack"
15
+ require "uri"
16
+ require 'erb'
17
+ require 'ostruct'
18
+ require 'benchmark'
19
+ require 'logger'
20
+ require 'core_extensions/string'
21
+ require 'smtp_with_source_ip'
22
+ require 'jenode/job'
23
+ require 'jenode/email'
24
+ require 'jenode/email_task'
25
+ require 'jenode/worker'
26
+ java_import java.util.concurrent.Executors
27
+
28
+ module Jenode
29
+ module_function
30
+ def run(options = {})
31
+ threads = []
32
+ $queue_connection = MarchHare.connect(
33
+ host: options[:queue_address],
34
+ user: options[:queue_user],
35
+ password: options[:queue_password],
36
+ vhost: ENV['APP_ENV']
37
+ )
38
+
39
+ options[:ips].each do |ip|
40
+ options[:threads_per_ip].times do |i|
41
+ thread = Thread.start do
42
+ worker = Jenode::Worker.new('email_to_send.empty', ip, i, options[:prefetch])
43
+ worker.run
44
+ end
45
+
46
+ threads.push(thread)
47
+ end
48
+ end
49
+
50
+ threads.each(&:join)
51
+ end
52
+ end
@@ -0,0 +1,123 @@
1
+ module Jenode
2
+ class Email
3
+ DEFAULT_SELECTOR = 'default'
4
+ CLICK_SUBDOMAIN = 'click'
5
+
6
+ class << self
7
+ def mail_exchangers(mail_host)
8
+ @cache_mail_exchangers ||= {}
9
+ mail_exchangers = @cache_mail_exchangers[mail_host]
10
+ return mail_exchangers if mail_exchangers
11
+
12
+ @cache_mail_exchangers[mail_host] = begin
13
+ Resolv::DNS.open(nameserver: '8.8.8.8').getresources(mail_host, Resolv::DNS::Resource::IN::MX).
14
+ sort_by(&:preference).map(&:exchange).map(&:to_s)
15
+ end
16
+ end
17
+ end
18
+
19
+ attr_reader(
20
+ :source_ip, :recipient, :sender, :template, :dkim_key, :template_vars,
21
+ :template_binding, :dont_touch_template, :utm_attributes
22
+ )
23
+
24
+ def initialize(options = {})
25
+ @source_ip = options[:source_ip]
26
+ @recipient = options[:recipient]
27
+ @sender = options[:sender]
28
+ @template = options[:template]
29
+ @dkim_key = options[:dkim_key]
30
+ @template_vars = {
31
+ 'sender' => sender,
32
+ 'recipient' => recipient,
33
+ 'source_ip' => source_ip,
34
+ 'sender_host' => sender_host,
35
+ 'recipient_host' => recipient_host
36
+ }
37
+ @template_vars.merge!(options[:template_vars] || {})
38
+ @template_binding = OpenStruct.new(@template_vars).instance_eval { binding }
39
+ @dont_touch_template = options[:dont_touch_template]
40
+ @utm_attributes = options[:utm_attributes]
41
+ end
42
+
43
+ def send
44
+ data = cook_email
45
+ mail_exchanger = mail_exchangers.sample
46
+ smtp = Net::SMTPWithSourceIp.new(mail_exchanger, 25, source_ip)
47
+ smtp.start(sender_host)
48
+ smtp.send_message(data, sender, recipient)
49
+ smtp.finish
50
+ end
51
+
52
+ private
53
+
54
+ def cook_email
55
+ result = template.result(template_binding)
56
+ headers, message = result.split("\r\n\r\n", 2)
57
+ processed_message = dont_touch_template ? message : process_message(message)
58
+ encoded_message = processed_message.to_quoted_printable
59
+ body = "#{headers}\r\n\r\n#{encoded_message}"
60
+ Dkim.sign(body, selector: DEFAULT_SELECTOR, private_key: dkim_key, domain: sender_host)
61
+ end
62
+
63
+ def process_message(message)
64
+ xml_document = Nokogiri::HTML(message)
65
+ fill_in_utm!(xml_document)
66
+ wrap_urls!(xml_document)
67
+ xml_document.to_s
68
+ end
69
+
70
+ def fill_in_utm!(xml_document)
71
+ xml_document.xpath('//a').each do |elem|
72
+ url = elem['href']
73
+ elem['href'] = fill_in_utm_in_url(url) unless url.nil?
74
+ end
75
+ xml_document
76
+ end
77
+
78
+ def wrap_urls!(xml_document)
79
+ xml_document.xpath('//a').each do |elem|
80
+ url = elem['href']
81
+ elem['href'] = wrap_url(url, '/click') unless url.nil?
82
+ end
83
+ xml_document.xpath('//img').each do |elem|
84
+ url = elem['src']
85
+ elem['src'] = wrap_url(url, '/proxy') unless url.nil?
86
+ end
87
+ xml_document
88
+ end
89
+
90
+ def fill_in_utm_in_url(url)
91
+ uri = URI(url)
92
+ query_params = Rack::Utils.parse_nested_query(uri.query)
93
+ query_params.merge!(utm_attributes)
94
+ uri.query = URI.encode_www_form(query_params)
95
+ uri.to_s
96
+ end
97
+
98
+ def wrap_url(url, path)
99
+ query_params = {
100
+ url: url,
101
+ user_id: template_vars['user']['id'],
102
+ email_campaign_id: template_vars['email_campaign']['id']
103
+ }
104
+ uri = URI::HTTP.build(
105
+ host: "#{ CLICK_SUBDOMAIN }.#{ sender_host }",
106
+ path: path,
107
+ query: URI.encode_www_form(query_params)
108
+ )
109
+ end
110
+
111
+ def sender_host
112
+ sender.split('@').last
113
+ end
114
+
115
+ def recipient_host
116
+ recipient.split('@').last
117
+ end
118
+
119
+ def mail_exchangers
120
+ self.class.mail_exchangers(recipient_host)
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,118 @@
1
+ module Jenode
2
+ class EmailTask
3
+ include Java::JavaUtilConcurrent::Callable
4
+
5
+ SKIP_ERROR_MESSAGES = [
6
+ 'invalid mailbox',
7
+ 'No such user',
8
+ 'Must be local recipient',
9
+ 'User unknown',
10
+ 'user not found'
11
+ ]
12
+ WAIT_ERROR_MESSAGES = [
13
+ 'try again later',
14
+ 'Connection refused'
15
+ ]
16
+ ERROR_QUEUE_NAME = "error"
17
+ EMAIL_SENDED_QUEUE_NAME = "sended_email"
18
+
19
+ class << self
20
+ attr_accessor :template, :sender, :dkim_key, :utm_attributes, :dont_touch_template
21
+
22
+ def generate_email_object(data, ip)
23
+ merged_utm_attributes = utm_attributes.merge(data['utm_attributes'])
24
+ email = Email.new(
25
+ source_ip: ip,
26
+ utm_attributes: merged_utm_attributes,
27
+ dont_touch_template: dont_touch_template,
28
+ recipient: data['recipient'],
29
+ template_vars: data['template_vars'],
30
+ template: template,
31
+ sender: sender,
32
+ dkim_key: dkim_key
33
+ )
34
+ end
35
+ end
36
+
37
+ attr_reader :job, :ip
38
+
39
+ def initialize(ip, job)
40
+ @ip = ip
41
+ @job = job
42
+ end
43
+
44
+ def send_email
45
+ email = self.class.generate_email_object(job.payload, ip)
46
+ email.send
47
+ end
48
+
49
+ def exchange
50
+ Thread.current[:exchange] ||= begin
51
+ channel = $queue_connection.create_channel
52
+ channel.default_exchange
53
+ end
54
+ end
55
+
56
+ def perform
57
+ begin
58
+ send_email
59
+ # email_sended_notify
60
+ job.ack
61
+ rescue Exception => e
62
+ proccess_error(e)
63
+ end
64
+ end
65
+
66
+ def proccess_error(e)
67
+ error_notify(e)
68
+
69
+ if is_skip_error?(e)
70
+ puts "Skip error=#{ e.message }"
71
+ job.ack
72
+ elsif is_wait_error?(e)
73
+ waiting_time = rand(180) + 30 # wait 30-210 seconds
74
+ puts "Wait #{waiting_time} seconds"
75
+ job.reject
76
+ sleep(waiting_time)
77
+ else
78
+ puts "Stop error=#{ e.message } backtrace=#{ e.backtrace }"
79
+ job.reject
80
+ Thread.stop
81
+ end
82
+ end
83
+
84
+ def is_skip_error?(e)
85
+ SKIP_ERROR_MESSAGES.any? do |error_message|
86
+ e.message.match(/#{error_message}/i)
87
+ end
88
+ end
89
+
90
+ def is_wait_error?(e)
91
+ WAIT_ERROR_MESSAGES.any? do |error_message|
92
+ e.message.match(/#{error_message}/i)
93
+ end
94
+ end
95
+
96
+ def error_notify(e)
97
+ body = MultiJson.dump(
98
+ ip: ip,
99
+ job: job.payload,
100
+ sender: self.class.sender,
101
+ error: {
102
+ message: e.message,
103
+ backtrace: e.backtrace,
104
+ }
105
+ )
106
+ exchange.publish(body, routing_key: ERROR_QUEUE_NAME)
107
+ end
108
+
109
+ # def email_sended_notify
110
+ # body = MultiJson.dump(
111
+ # ip: ip,
112
+ # job: job.payload,
113
+ # timestamp: Time.now.to_i
114
+ # )
115
+ # exchange.publish(body, routing_key: EMAIL_SENDED_QUEUE_NAME)
116
+ # end
117
+ end
118
+ end
data/lib/jenode/job.rb ADDED
@@ -0,0 +1,18 @@
1
+ module Jenode
2
+ class Job
3
+ attr_reader :payload, :metadata
4
+
5
+ def initialize(data, metadata)
6
+ @payload = MultiJson.load(data)
7
+ @metadata = metadata
8
+ end
9
+
10
+ def ack
11
+ metadata.ack
12
+ end
13
+
14
+ def reject
15
+ metadata.reject(requeue: true)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,155 @@
1
+ module Jenode
2
+ class Worker
3
+ COMMANDS = %w(
4
+ pause continue info test set_queue set_sender set_template set_utm_attributes
5
+ set_dont_touch_template set_dkim_key
6
+ )
7
+ INFO_RESULT_QUEUE_NAME = "info_result"
8
+ TEST_RESULT_QUEUE_NAME = "test_result"
9
+
10
+ attr_reader(
11
+ :ip, :queue_name, :number, :stop, :logger,
12
+ :consumer_queue, :prefetch, :worker_thread
13
+ )
14
+
15
+ def initialize(queue_name, ip, number, prefetch)
16
+ @ip = ip
17
+ @queue_name = queue_name
18
+ @number = number
19
+ @prefetch = prefetch
20
+ @worker_thread = Thread.current
21
+ @stop = false
22
+ @logger = Logger.new($stdout)
23
+ @logger.formatter = -> (severity, datetime, progname, msg) {
24
+ date_format = datetime.strftime("%Y-%m-%d %H:%M:%S.%L")
25
+ puts "#{ date_format } - #{ ip } - #{ number } #{ msg }\n"
26
+ }
27
+ end
28
+
29
+ def channel
30
+ Thread.current[:channel] ||= begin
31
+ channel = $queue_connection.create_channel
32
+ channel.prefetch = prefetch
33
+ channel
34
+ end
35
+ end
36
+
37
+ def exchange
38
+ Thread.current[:exchange] ||= begin
39
+ channel.default_exchange
40
+ end
41
+ end
42
+
43
+ def info(job)
44
+ body = MultiJson.dump(
45
+ ip: ip,
46
+ number: number,
47
+ queue_name: queue_name,
48
+ stop: stop,
49
+ sender: EmailTask.sender,
50
+ utm_attributes: EmailTask.utm_attributes,
51
+ dont_touch_template: EmailTask.dont_touch_template
52
+ )
53
+ exchange.publish(body, routing_key: INFO_RESULT_QUEUE_NAME)
54
+ end
55
+
56
+ def pause(job)
57
+ @stop = true
58
+ end
59
+
60
+ def continue(job)
61
+ @stop = false
62
+ worker_thread.run
63
+ end
64
+
65
+ def set_queue(job)
66
+ new_queue_name = job.payload['data']
67
+ return if queue_name == new_queue_name
68
+ @queue_name = new_queue_name
69
+ consumer_queue.cancel
70
+ end
71
+
72
+ def set_sender(job)
73
+ EmailTask.sender = job.payload['data']
74
+ end
75
+
76
+ def set_utm_attributes(job)
77
+ EmailTask.utm_attributes = job.payload['data']
78
+ end
79
+
80
+ def set_dont_touch_template(job)
81
+ EmailTask.dont_touch_template = job.payload['data']
82
+ end
83
+
84
+ def set_template(job)
85
+ EmailTask.template = ERB.new(job.payload['data'])
86
+ end
87
+
88
+ def set_dkim_key(job)
89
+ EmailTask.dkim_key = OpenSSL::PKey::RSA.new(job.payload['data'])
90
+ end
91
+
92
+ def test(job)
93
+ begin
94
+ email = EmailTask.generate_email_object(job.payload['data'], ip)
95
+ email.send
96
+ rescue Exception => e
97
+ error_message = e.message
98
+ error_backtrace = e.backtrace
99
+ end
100
+
101
+ body = MultiJson.dump(
102
+ ip: ip,
103
+ number: number,
104
+ job: job.payload,
105
+ error_message: error_message,
106
+ error_backtrace: error_backtrace
107
+ )
108
+ exchange.publish(body, routing_key: TEST_RESULT_QUEUE_NAME)
109
+ end
110
+
111
+ def subscribe_on_command_queue
112
+ command_queue = channel.queue("workers.#{ ip.gsub('.', '_') }.#{ number }", exclusive: true)
113
+ consumer_command_queue = command_queue.build_consumer(block: false) do |metadata, payload|
114
+ logger.info("Recived cmd=#{ payload }")
115
+ job = Jenode::Job.new(payload, metadata)
116
+ cmd = job.payload['cmd']
117
+ send(cmd, job)
118
+ end
119
+ command_queue.subscribe_with(consumer_command_queue)
120
+ end
121
+
122
+ def subscribe_on_queue
123
+ queue = channel.queue(queue_name, durable: true)
124
+ @consumer_queue = queue.build_consumer(block: true) do |metadata, payload|
125
+ begin
126
+ logger.info("Recived email_task=#{ payload }")
127
+ job = Job.new(payload, metadata)
128
+ if stop
129
+ logger.info("Stop")
130
+ job.reject
131
+ Thread.stop
132
+ logger.info("Run")
133
+ else
134
+ email_task = EmailTask.new(ip, job)
135
+ email_task.perform
136
+ end
137
+ rescue Exception => e
138
+ logger.info("Error: #{ e.message }")
139
+ logger.info("Error: #{ e.backtrace.join("\n") }")
140
+
141
+ job.reject
142
+ end
143
+ end
144
+ queue.subscribe_with(@consumer_queue, manual_ack: true)
145
+ end
146
+
147
+ def run
148
+ logger.info("Run jenode")
149
+ subscribe_on_command_queue
150
+ loop do
151
+ subscribe_on_queue
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,14 @@
1
+ require 'net/smtp'
2
+
3
+ module Net
4
+ class SMTPWithSourceIp < SMTP
5
+ def initialize(address, port = nil, source_ip = nil)
6
+ super(address, port)
7
+ @source_ip = source_ip
8
+ end
9
+
10
+ def tcp_socket(address, port)
11
+ TCPSocket.open address, port, @source_ip
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe Jenode::Email do
4
+ describe "#perform" do
5
+ let!(:source_ip) { '127.0.0.1' }
6
+ let!(:body) { <<-STR
7
+ Precedence: bulk
8
+ Date: <%= Time.now.strftime('%a, %d %b %Y %H:%M:%S %z') %>
9
+ MIME-Version: 1.0
10
+ Message-ID: <<%= "#{rand(1000)}-#{rand(1000)}-#{rand(1000)}-#{rand(1000)}-#{rand(1000)}" %>@<%= sender_host %>>
11
+ Content-Type: text/html; charset="UTF-8"
12
+ Content-transfer-encoding: quoted-printable
13
+ To: <%= recipient %>
14
+ From: =?UTF-8?B?<%= Base64.strict_encode64("Тест") %>?= <<%= sender %>>
15
+ Subject: =?UTF-8?B?<%= Base64.strict_encode64("Тест") %>?=
16
+
17
+ Тест
18
+ STR
19
+ }
20
+ let!(:template) { ERB.new(body) }
21
+ let!(:sender) { 'test@test.com' }
22
+ let!(:recipient) { 'test@test.com' }
23
+
24
+ it "send message" do
25
+ any_instance_of(Net::SMTPWithSourceIp) do |klass|
26
+ mock(klass).start { }
27
+ .with('test.com')
28
+ end
29
+
30
+ any_instance_of(Net::SMTPWithSourceIp) do |klass|
31
+ mock(klass).finish { }
32
+ end
33
+
34
+ any_instance_of(Net::SMTPWithSourceIp) do |klass|
35
+ mock(klass).send_message.with_any_args { }
36
+ end
37
+
38
+ mock(Jenode::Email).mail_exchangers { |_|
39
+ ['127.0.0.1']
40
+ }.with_any_args
41
+
42
+ stub(Dkim).sign.with_any_args { '' }
43
+
44
+ email = Jenode::Email.new(
45
+ source_ip: source_ip,
46
+ template: template,
47
+ sender: sender,
48
+ recipient: recipient
49
+ )
50
+
51
+ email.send
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,95 @@
1
+ require 'rr'
2
+ require_relative '../lib/jenode'
3
+
4
+ ENV['APP_ENV'] ||= 'test'
5
+
6
+ # This file was generated by the `rspec --init` command. Conventionally, all
7
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
8
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
9
+ # file to always be loaded, without a need to explicitly require it in any files.
10
+ #
11
+ # Given that it is always loaded, you are encouraged to keep this file as
12
+ # light-weight as possible. Requiring heavyweight dependencies from this file
13
+ # will add to the boot time of your test suite on EVERY test run, even for an
14
+ # individual file that may not need all of that loaded. Instead, consider making
15
+ # a separate helper file that requires the additional dependencies and performs
16
+ # the additional setup, and require it from the spec files that actually need it.
17
+ #
18
+ # The `.rspec` file also contains a few flags that are not defaults but that
19
+ # users commonly want.
20
+ #
21
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
22
+ RSpec.configure do |config|
23
+ # rspec-expectations config goes here. You can use an alternate
24
+ # assertion/expectation library such as wrong or the stdlib/minitest
25
+ # assertions if you prefer.
26
+ config.expect_with :rspec do |expectations|
27
+ # This option will default to `true` in RSpec 4. It makes the `description`
28
+ # and `failure_message` of custom matchers include text for helper methods
29
+ # defined using `chain`, e.g.:
30
+ # be_bigger_than(2).and_smaller_than(4).description
31
+ # # => "be bigger than 2 and smaller than 4"
32
+ # ...rather than:
33
+ # # => "be bigger than 2"
34
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
35
+ end
36
+
37
+ # rspec-mocks config goes here. You can use an alternate test double
38
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
39
+ config.mock_with :rr
40
+ # config.mock_with :rspec do |mocks|
41
+ # # Prevents you from mocking or stubbing a method that does not exist on
42
+ # # a real object. This is generally recommended, and will default to
43
+ # # `true` in RSpec 4.
44
+ # mocks.verify_partial_doubles = true
45
+ # end
46
+
47
+ # The settings below are suggested to provide a good initial experience
48
+ # with RSpec, but feel free to customize to your heart's content.
49
+ =begin
50
+ # These two settings work together to allow you to limit a spec run
51
+ # to individual examples or groups you care about by tagging them with
52
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
53
+ # get run.
54
+ config.filter_run :focus
55
+ config.run_all_when_everything_filtered = true
56
+
57
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
58
+ # For more details, see:
59
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
60
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
61
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
62
+ config.disable_monkey_patching!
63
+
64
+ # This setting enables warnings. It's recommended, but in some cases may
65
+ # be too noisy due to issues in dependencies.
66
+ config.warnings = true
67
+
68
+ # Many RSpec users commonly either run the entire suite or an individual
69
+ # file, and it's useful to allow more verbose output when running an
70
+ # individual spec file.
71
+ if config.files_to_run.one?
72
+ # Use the documentation formatter for detailed output,
73
+ # unless a formatter has already been configured
74
+ # (e.g. via a command-line flag).
75
+ config.default_formatter = 'doc'
76
+ end
77
+
78
+ # Print the 10 slowest examples and example groups at the
79
+ # end of the spec run, to help surface which specs are running
80
+ # particularly slow.
81
+ config.profile_examples = 10
82
+
83
+ # Run specs in random order to surface order dependencies. If you find an
84
+ # order dependency and want to debug it, you can fix the order by providing
85
+ # the seed, which is printed after each run.
86
+ # --seed 1234
87
+ config.order = :random
88
+
89
+ # Seed global randomization in this process using the `--seed` CLI option.
90
+ # Setting this allows you to use `--seed` to deterministically reproduce
91
+ # test failures related to randomization by passing the same `--seed` value
92
+ # as the one that triggered the failure.
93
+ Kernel.srand config.seed
94
+ =end
95
+ end
metadata ADDED
@@ -0,0 +1,219 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jenode
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7
5
+ platform: java
6
+ authors:
7
+ - vad4msiu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: multi_json
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.10.0
20
+ requirement: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - "~>"
23
+ - !ruby/object:Gem::Version
24
+ version: 1.10.0
25
+ prerelease: false
26
+ type: :runtime
27
+ - !ruby/object:Gem::Dependency
28
+ name: jrjackson
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.0
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: 0.2.0
39
+ prerelease: false
40
+ type: :runtime
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.19.0
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: 0.19.0
53
+ prerelease: false
54
+ type: :runtime
55
+ - !ruby/object:Gem::Dependency
56
+ name: march_hare
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.8.0
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: 2.8.0
67
+ prerelease: false
68
+ type: :runtime
69
+ - !ruby/object:Gem::Dependency
70
+ name: dkim
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.0.0
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: 1.0.0
81
+ prerelease: false
82
+ type: :runtime
83
+ - !ruby/object:Gem::Dependency
84
+ name: rack
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.6.0
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: 1.6.0
95
+ prerelease: false
96
+ type: :runtime
97
+ - !ruby/object:Gem::Dependency
98
+ name: nokogiri
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 1.6.0
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: 1.6.0
109
+ prerelease: false
110
+ type: :runtime
111
+ - !ruby/object:Gem::Dependency
112
+ name: bundler
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 1.8.0
118
+ requirement: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: 1.8.0
123
+ prerelease: false
124
+ type: :development
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake
127
+ version_requirements: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 10.3.0
132
+ requirement: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - "~>"
135
+ - !ruby/object:Gem::Version
136
+ version: 10.3.0
137
+ prerelease: false
138
+ type: :development
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ version_requirements: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 3.1.0
146
+ requirement: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - "~>"
149
+ - !ruby/object:Gem::Version
150
+ version: 3.1.0
151
+ prerelease: false
152
+ type: :development
153
+ - !ruby/object:Gem::Dependency
154
+ name: rr
155
+ version_requirements: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 1.1.0
160
+ requirement: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - "~>"
163
+ - !ruby/object:Gem::Version
164
+ version: 1.1.0
165
+ prerelease: false
166
+ type: :development
167
+ description: Write a longer description. Optional.
168
+ email:
169
+ - vad4msiu@gmail.com
170
+ executables:
171
+ - jenode
172
+ extensions: []
173
+ extra_rdoc_files: []
174
+ files:
175
+ - ".gitignore"
176
+ - ".rspec"
177
+ - Gemfile
178
+ - Gemfile.lock
179
+ - LICENSE.txt
180
+ - README.md
181
+ - Rakefile
182
+ - bin/jenode
183
+ - jenode.gemspec
184
+ - lib/core_extensions/string.rb
185
+ - lib/jenode.rb
186
+ - lib/jenode/email.rb
187
+ - lib/jenode/email_task.rb
188
+ - lib/jenode/job.rb
189
+ - lib/jenode/worker.rb
190
+ - lib/smtp_with_source_ip.rb
191
+ - spec/lib/jenode/email_spec.rb
192
+ - spec/spec_helper.rb
193
+ homepage: ''
194
+ licenses:
195
+ - MIT
196
+ metadata: {}
197
+ post_install_message:
198
+ rdoc_options: []
199
+ require_paths:
200
+ - lib
201
+ required_ruby_version: !ruby/object:Gem::Requirement
202
+ requirements:
203
+ - - ">="
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
206
+ required_rubygems_version: !ruby/object:Gem::Requirement
207
+ requirements:
208
+ - - ">="
209
+ - !ruby/object:Gem::Version
210
+ version: '0'
211
+ requirements: []
212
+ rubyforge_project:
213
+ rubygems_version: 2.4.5
214
+ signing_key:
215
+ specification_version: 4
216
+ summary: Write a short summary. Required.
217
+ test_files:
218
+ - spec/lib/jenode/email_spec.rb
219
+ - spec/spec_helper.rb