jenode 0.0.7-java

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