rflow-components-http 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ \#*
6
+ .\#*
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+ require 'rdoc/task'
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ RSpec::Core::RakeTask.new(:spec) do |t|
7
+ t.verbose = true
8
+ t.rspec_opts = '--tty --color'
9
+ end
10
+
11
+ Rake::RDocTask.new do |rd|
12
+ rd.main = "README"
13
+ rd.rdoc_files.include("README", "lib/**/*.rb")
14
+ rd.rdoc_dir = File.join('doc', 'html')
15
+ end
File without changes
@@ -0,0 +1,68 @@
1
+ class RFlow
2
+ module Components
3
+ module HTTP
4
+
5
+ # The set of extensions to add capability to HTTP data types
6
+ module Extensions
7
+
8
+ module IPConnectionExtension
9
+ # Default accessors
10
+ # TODO: validate the stuffs
11
+ ['client_ip', 'client_port', 'server_ip', 'server_port'].each do |name|
12
+ define_method name do |*args|
13
+ data_object[name]
14
+ end
15
+ define_method :"#{name}=" do |*args|
16
+ data_object[name] = args.first
17
+ end
18
+ end
19
+ end
20
+
21
+ # Need to be careful when extending to not clobber data already in data_object
22
+ module HTTPRequestExtension
23
+ def self.extended(base_data)
24
+ base_data.data_object ||= {'uri' => '/', 'method' => 'GET', 'protocol' => 'HTTP/1.0', 'headers'=>{}, 'content' => ''}
25
+ end
26
+
27
+ # Default accessors
28
+ ['method', 'uri', 'query_string', 'protocol', 'headers', 'content'].each do |name|
29
+ define_method name do |*args|
30
+ data_object[name]
31
+ end
32
+ define_method :"#{name}=" do |*args|
33
+ data_object[name] = args.first
34
+ end
35
+ end
36
+ end
37
+
38
+ # Need to be careful when extending to not clobber data already in data_object
39
+ module HTTPResponseExtension
40
+ def self.extended(base_data)
41
+ base_data.data_object ||= {'protocol' => 'HTTP/1.0', 'status_code' => 200, 'status_reason_phrase' => 'OK', 'headers' => {}, 'content' => ''}
42
+ end
43
+
44
+ # Default accessors
45
+ ['protocol', 'status_reason_phrase', 'headers', 'content'].each do |name|
46
+ define_method name do |*args|
47
+ data_object[name]
48
+ end
49
+ define_method :"#{name}=" do |*args|
50
+ data_object[name] = args.first
51
+ end
52
+ end
53
+
54
+ ['status_code'].each do |name|
55
+ define_method name do |*args|
56
+ data_object[name]
57
+ end
58
+ define_method :"#{name}=" do |*args|
59
+ data_object[name] = args.first.to_i
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,123 @@
1
+ require 'eventmachine'
2
+ require 'evma_httpserver'
3
+
4
+ require 'rflow'
5
+
6
+ class RFlow
7
+ module Components
8
+ module HTTP
9
+
10
+ class Server < RFlow::Component
11
+ input_port :response_port
12
+ output_port :request_port
13
+
14
+ attr_accessor :port, :listen, :server_signature, :connections
15
+
16
+ def configure!(config)
17
+ @listen = config['listen'] ? config['listen'] : '127.0.0.1'
18
+ @port = config['port'] ? config['port'].to_i : 8000
19
+ @connections = Hash.new
20
+ end
21
+
22
+ def run!
23
+ @server_signature = EM.start_server(@listen, @port, Connection) do |conn|
24
+ conn.server = self
25
+ self.connections[conn.signature.to_s] = conn
26
+ end
27
+ end
28
+
29
+ # Getting all messages to response_port, which we need to filter for
30
+ # those that pertain to this component and have active connections.
31
+ # This is done by inspecting the provenance, specifically the
32
+ # context attribute that we stored originally
33
+ def process_message(input_port, input_port_key, connection, message)
34
+ RFlow.logger.debug "Received a message"
35
+ return unless message.data_type_name == 'RFlow::Message::Data::HTTP::Response'
36
+
37
+
38
+ RFlow.logger.debug "Received a HTTP::Response message, determining if its mine"
39
+ my_events = message.provenance.find_all {|processing_event| processing_event.component_instance_uuid == instance_uuid}
40
+ RFlow.logger.debug "Found #{my_events.size} processing events from me"
41
+ # Attempt to send the data to each context match
42
+ my_events.each do |processing_event|
43
+ RFlow.logger.debug "Inspecting #{processing_event.context}"
44
+ connection_signature = processing_event.context
45
+ if connections[connection_signature]
46
+ RFlow.logger.debug "Found connection for #{processing_event.context}"
47
+ connections[connection_signature].send_http_response message
48
+ end
49
+ end
50
+ end
51
+
52
+ class Connection < EventMachine::Connection
53
+ include EventMachine::HttpServer
54
+
55
+ attr_accessor :server
56
+ attr_reader :client_ip, :client_port, :server_ip, :server_port
57
+
58
+ def post_init
59
+ @client_port, @client_ip = Socket.unpack_sockaddr_in(get_peername) rescue ["?", "?.?.?.?"]
60
+ @server_port, @server_ip = Socket.unpack_sockaddr_in(get_sockname) rescue ["?", "?.?.?.?"]
61
+ RFlow.logger.debug "Connection from #{@client_ip}:#{@client_port} to #{@server_ip}:#{@server_port}"
62
+ super
63
+ no_environment_strings
64
+ end
65
+
66
+
67
+ def receive_data(data)
68
+ RFlow.logger.debug "Received #{data.bytesize} bytes of data from #{client_ip}:#{client_port} to #{@server_ip}:#{@server_port}"
69
+ super
70
+ end
71
+
72
+
73
+ def process_http_request
74
+ RFlow.logger.debug "Received a HTTP request from #{client_ip}:#{client_port} to #{@server_ip}:#{@server_port}"
75
+
76
+ processing_event = RFlow::Message::ProcessingEvent.new(server.instance_uuid, Time.now.utc)
77
+
78
+ request_message = RFlow::Message.new('RFlow::Message::Data::HTTP::Request')
79
+ request_message.data.uri = @http_request_uri
80
+
81
+ processing_event.context = signature
82
+ processing_event.completed_at = Time.now.utc
83
+ request_message.provenance << processing_event
84
+
85
+ server.request_port.send_message request_message
86
+ end
87
+
88
+
89
+ def send_http_response(response_message=nil)
90
+ RFlow.logger.debug "Sending an HTTP response to #{client_ip}:#{client_port}"
91
+ resp = EventMachine::DelegatedHttpResponse.new(self)
92
+
93
+ # Default values
94
+ resp.status = 200
95
+ resp.content = ""
96
+ resp.headers["Content-Type"] = "text/html"
97
+ resp.headers["Server"] = "Apache"
98
+
99
+ if response_message
100
+ resp.status = response_message.data.status_code
101
+ resp.content = response_message.data.content
102
+ response_message.data.headers.each do |header, value|
103
+ resp[headers] = value
104
+ end
105
+ end
106
+
107
+ resp.send_response
108
+ close_connection_after_writing
109
+ end
110
+
111
+
112
+ # Called when a connection is torn down for whatever reason.
113
+ # Remove this connection from the server's list
114
+ def unbind(reason=nil)
115
+ RFlow.logger.debug "Disconnected from HTTP client #{client_ip}:#{client_port} due to '#{reason}'"
116
+ server.connections.delete(self.signature)
117
+ end
118
+ end
119
+ end
120
+
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,7 @@
1
+ class RFlow
2
+ module Components
3
+ module HTTP
4
+ VERSION = "0.0.2"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ require 'rflow/components/http/extensions'
2
+ require 'rflow/components/http/server'
3
+
4
+ class RFlow
5
+ module Components
6
+ module HTTP
7
+ # Load the schemas
8
+ SCHEMA_DIRECTORY = ::File.expand_path(::File.join(::File.dirname(__FILE__), '..', '..', '..', 'schema'))
9
+
10
+ SCHEMA_FILES = {
11
+ 'http_response.avsc' => 'RFlow::Message::Data::HTTP::Response',
12
+ 'http_request.avsc' => 'RFlow::Message::Data::HTTP::Request',
13
+ }
14
+
15
+ SCHEMA_FILES.each do |file_name, data_type_name|
16
+ schema_string = ::File.read(::File.join(SCHEMA_DIRECTORY, file_name))
17
+ RFlow::Configuration.add_available_data_type data_type_name, 'avro', schema_string
18
+ end
19
+
20
+ # Load the data extensions
21
+ RFlow::Configuration.add_available_data_extension('RFlow::Message::Data::HTTP::Request',
22
+ RFlow::Components::HTTP::Extensions::IPConnectionExtension)
23
+ RFlow::Configuration.add_available_data_extension('RFlow::Message::Data::HTTP::Request',
24
+ RFlow::Components::HTTP::Extensions::HTTPRequestExtension)
25
+
26
+
27
+ RFlow::Configuration.add_available_data_extension('RFlow::Message::Data::HTTP::Response',
28
+ RFlow::Components::HTTP::Extensions::IPConnectionExtension)
29
+ RFlow::Configuration.add_available_data_extension('RFlow::Message::Data::HTTP::Response',
30
+ RFlow::Components::HTTP::Extensions::HTTPResponseExtension)
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,2 @@
1
+ require 'rflow'
2
+ require 'rflow/components/http'
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rflow/components/http/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rflow-components-http"
7
+ s.version = RFlow::Components::HTTP::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.required_ruby_version = '~> 1.9'
10
+ s.authors = ["Michael L. Artz"]
11
+ s.email = ["michael.artz@redjack.com"]
12
+ s.homepage = ""
13
+ s.summary = %q{HTTP client and server components for the RFlow FBP framework}
14
+ s.description = %q{HTTP client and server components for the RFlow FBP framework. Also includes the necessary HTTP::Request and HTTP::Response message types}
15
+
16
+ s.rubyforge_project = "rflow-components-http"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+
23
+ s.add_dependency 'rflow', '~> 0.0'
24
+ s.add_dependency 'eventmachine_httpserver', '~> 0.2'
25
+
26
+ s.add_development_dependency 'rspec', '~> 2.6'
27
+ s.add_development_dependency 'rake', '>= 0.8.7'
28
+ end
@@ -0,0 +1,19 @@
1
+ {
2
+ "type": "record",
3
+ "name": "Request",
4
+ "namespace": "org.rflow.message.data.http",
5
+ "aliases": [],
6
+ "fields": [
7
+ {"name": "client_ip", "type": ["string", "null"]},
8
+ {"name": "client_port", "type": ["int", "null"]},
9
+ {"name": "server_ip", "type": ["string", "null"]},
10
+ {"name": "server_port", "type": ["int", "null"]},
11
+
12
+ {"name": "method", "type": "string"},
13
+ {"name": "uri", "type": "string"},
14
+ {"name": "query_string", "type": ["string", "null"]},
15
+ {"name": "protocol", "type": "string"},
16
+ {"name": "headers", "type": {"type": "map", "values": ["string", "null"]}},
17
+ {"name": "content", "type": ["bytes", "null"]}
18
+ ]
19
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "type": "record",
3
+ "name": "Response",
4
+ "namespace": "org.rflow.message.data.http",
5
+ "aliases": [],
6
+ "fields": [
7
+ {"name": "client_ip", "type": ["string", "null"]},
8
+ {"name": "client_port", "type": ["int", "null"]},
9
+ {"name": "server_ip", "type": ["string", "null"]},
10
+ {"name": "server_port", "type": ["int", "null"]},
11
+
12
+ {"name": "protocol", "type": "string"},
13
+ {"name": "status_code", "type": "int"},
14
+ {"name": "status_reason_phrase", "type": "string"},
15
+ {"name": "headers", "type": {"type": "map", "values": "string"}},
16
+ {"name": "content", "type": "bytes"}
17
+ ]
18
+ }
@@ -0,0 +1,43 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe RFlow::Components::HTTP::Extensions::HTTPRequestExtension do
4
+ before(:each) do
5
+ @schema_string = RFlow::Configuration.available_data_types['RFlow::Message::Data::HTTP::Request']['avro']
6
+ end
7
+
8
+ it "should work" do
9
+ RFlow::Configuration.available_data_extensions['RFlow::Message::Data::HTTP::Request'].should include(described_class)
10
+
11
+ request = RFlow::Message.new('RFlow::Message::Data::HTTP::Request')
12
+ request.data.uri.should == '/'
13
+ request.data.method.should == 'GET'
14
+ request.data.query_string.should == nil
15
+ request.data.protocol.should == 'HTTP/1.0'
16
+ request.data.headers.should == {}
17
+
18
+ request.data.uri = 'POST'
19
+ request.data.uri.should == 'POST'
20
+ end
21
+
22
+ end
23
+
24
+ describe RFlow::Components::HTTP::Extensions::HTTPResponseExtension do
25
+ before(:each) do
26
+ @schema_string = RFlow::Configuration.available_data_types['RFlow::Message::Data::HTTP::Response']['avro']
27
+ end
28
+
29
+ it "should work" do
30
+ RFlow::Configuration.available_data_extensions['RFlow::Message::Data::HTTP::Response'].should include(described_class)
31
+
32
+ response = RFlow::Message.new('RFlow::Message::Data::HTTP::Response')
33
+ response.data.protocol.should == 'HTTP/1.0'
34
+ response.data.status_code.should == 200
35
+ response.data.status_reason_phrase.should == 'OK'
36
+ response.data.headers.should == {}
37
+ response.data.content.should == ''
38
+
39
+ response.data.status_code = 404
40
+ response.data.status_code.should == 404
41
+ end
42
+
43
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe 'RFlow::Message::Data::HTTP::Request Avro Schema' do
4
+ before(:each) do
5
+ @schema_string = RFlow::Configuration.available_data_types['RFlow::Message::Data::HTTP::Request']['avro']
6
+ end
7
+
8
+ it "should encode and decode an object" do
9
+ request = {
10
+ 'method' => 'METHOD',
11
+ 'uri' => 'URI',
12
+ 'query_string' => 'QUERYSTRING',
13
+ 'protocol' => 'PROTOCOL',
14
+ 'headers' => {
15
+ 'header1' => 'HEADER1',
16
+ 'header2' => 'HEADER2',
17
+ },
18
+ 'content' => '',
19
+ }
20
+
21
+ expect {encode_avro(@schema_string, request)}.to_not raise_error
22
+ avro_encoded_request = encode_avro(@schema_string, request)
23
+
24
+ expect {decode_avro(@schema_string, avro_encoded_request)}.to_not raise_error
25
+ decoded_request = decode_avro(@schema_string, avro_encoded_request)
26
+
27
+ decoded_request['method'].should == request['method']
28
+ decoded_request['uri'].should == request['uri']
29
+ decoded_request['query_string'].should == request['query_string']
30
+ decoded_request['protocol'].should == request['protocol']
31
+ decoded_request['headers'].should == request['headers']
32
+ decoded_request['content'].should == request['content']
33
+ end
34
+ end
35
+
36
+
37
+ describe 'RFlow::Message::Data::HTTP::Response Avro Schema' do
38
+ before(:each) do
39
+ @schema_string = RFlow::Configuration.available_data_types['RFlow::Message::Data::HTTP::Response']['avro']
40
+ end
41
+
42
+ it "should encode and decode an object" do
43
+ response = {
44
+ 'protocol' => 'METHOD',
45
+ 'status_code' => 200,
46
+ 'status_reason_phrase' => 'STATUSREASONPHRASE',
47
+ 'headers' => {
48
+ 'header1' => 'HEADER1',
49
+ 'header2' => 'HEADER2',
50
+ },
51
+ 'content' => 'CONTENT',
52
+ }
53
+
54
+ expect {encode_avro(@schema_string, response)}.to_not raise_error
55
+ avro_encoded_response = encode_avro(@schema_string, response)
56
+
57
+ expect {decode_avro(@schema_string, avro_encoded_response)}.to_not raise_error
58
+ decoded_response = decode_avro(@schema_string, avro_encoded_response)
59
+
60
+ decoded_response['protocol'].should == response['protocol']
61
+ decoded_response['status_code'].should == response['status_code']
62
+ decoded_response['status_reason_phrase'].should == response['status_reason_phrase']
63
+ decoded_response['headers'].should == response['headers']
64
+ decoded_response['content'].should == response['content']
65
+ end
66
+ end
67
+
@@ -0,0 +1,18 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'rflow-components-http'))
2
+
3
+ require 'logger'
4
+
5
+ RFlow.logger = Logger.new STDOUT
6
+
7
+ def decode_avro(schema_string, serialized_object)
8
+ schema = Avro::Schema.parse(schema_string)
9
+ sio = StringIO.new(serialized_object)
10
+ Avro::IO::DatumReader.new(schema, schema).read Avro::IO::BinaryDecoder.new(sio)
11
+ end
12
+
13
+ def encode_avro(schema_string, object)
14
+ schema = Avro::Schema.parse(schema_string)
15
+ sio = StringIO.new
16
+ Avro::IO::DatumWriter.new(schema).write object, Avro::IO::BinaryEncoder.new(sio)
17
+ sio.string
18
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rflow-components-http
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michael L. Artz
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-03-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rflow
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '0.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: eventmachine_httpserver
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '0.2'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '0.2'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.6'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.6'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 0.8.7
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 0.8.7
78
+ description: HTTP client and server components for the RFlow FBP framework. Also
79
+ includes the necessary HTTP::Request and HTTP::Response message types
80
+ email:
81
+ - michael.artz@redjack.com
82
+ executables: []
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - .gitignore
87
+ - Gemfile
88
+ - Rakefile
89
+ - lib/rflow-components-http.rb
90
+ - lib/rflow/components/http.rb
91
+ - lib/rflow/components/http/client.rb
92
+ - lib/rflow/components/http/extensions.rb
93
+ - lib/rflow/components/http/server.rb
94
+ - lib/rflow/components/http/version.rb
95
+ - rflow-components-http.gemspec
96
+ - schema/http_request.avsc
97
+ - schema/http_response.avsc
98
+ - spec/extensions_spec.rb
99
+ - spec/schema_spec.rb
100
+ - spec/spec_helper.rb
101
+ homepage: ''
102
+ licenses: []
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ~>
111
+ - !ruby/object:Gem::Version
112
+ version: '1.9'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project: rflow-components-http
121
+ rubygems_version: 1.8.24
122
+ signing_key:
123
+ specification_version: 3
124
+ summary: HTTP client and server components for the RFlow FBP framework
125
+ test_files:
126
+ - spec/extensions_spec.rb
127
+ - spec/schema_spec.rb
128
+ - spec/spec_helper.rb