armstrong 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/armstrong.gemspec +1 -1
- data/demo/armstrong_test.rb +1 -1
- data/lib/armstrong/connection.rb +32 -15
- data/lib/armstrong/main_actors.rb +12 -4
- data/lib/armstrong.rb +12 -3
- metadata +4 -4
data/armstrong.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Gem::Specification.new 'armstrong', '0.
|
1
|
+
Gem::Specification.new 'armstrong', '0.4.0' do |s|
|
2
2
|
s.description = "Armstrong is an Mongrel2 fronted, actor-based web development framework similar in style to sinatra. With natively-threaded interpreters (Rubinius2), Armstrong provides true concurrency and high stability, by design."
|
3
3
|
s.summary = "Highly concurrent, sinatra-like framework"
|
4
4
|
s.author = "Artem Titoulenko"
|
data/demo/armstrong_test.rb
CHANGED
data/lib/armstrong/connection.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
require 'ffi'
|
1
2
|
require 'ffi-rzmq'
|
3
|
+
require 'json'
|
2
4
|
|
3
5
|
class Connection
|
4
|
-
attr_reader :app_id, :sub_addr, :pub_addr, :request_sock, :response_sock
|
6
|
+
attr_reader :app_id, :sub_addr, :pub_addr, :request_sock, :response_sock, :context
|
5
7
|
|
6
8
|
def initialize(app_id, zmq_sub_pub_addr=["tcp://127.0.0.1", 9999, "tcp://127.0.0.1", 9998])
|
7
9
|
@app_id = app_id
|
@@ -12,27 +14,29 @@ class Connection
|
|
12
14
|
end
|
13
15
|
|
14
16
|
def connect
|
15
|
-
context = ZMQ::Context.new 1
|
16
|
-
@request_sock = context.socket ZMQ::PULL
|
17
|
+
@context = ZMQ::Context.new 1
|
18
|
+
@request_sock = @context.socket ZMQ::PULL
|
17
19
|
@request_sock.connect @sub_addr
|
18
20
|
|
19
|
-
@response_sock = context.socket ZMQ::PUB
|
21
|
+
@response_sock = @context.socket ZMQ::PUB
|
20
22
|
@response_sock.setsockopt ZMQ::IDENTITY, @app_id
|
21
23
|
@response_sock.connect @pub_addr
|
22
24
|
end
|
23
25
|
|
24
|
-
#raw recv
|
26
|
+
#raw recv, unparsed message
|
25
27
|
def recv
|
26
28
|
msg = ""
|
27
|
-
@request_sock.recv_string
|
29
|
+
rc = @request_sock.recv_string(msg)
|
30
|
+
puts "errno [#{ZMQ::Util.errno}] with description [#{ZMQ::Util.error_string}]" unless ZMQ::Util.resultcode_ok?(rc)
|
28
31
|
msg
|
29
32
|
end
|
30
33
|
|
31
34
|
#parse the request, this is the best way to get stuff back, as a Hash
|
32
35
|
def receive
|
33
|
-
parse(
|
36
|
+
parse(recv)
|
34
37
|
end
|
35
38
|
|
39
|
+
# sends the message off, formatted for Mongrel2 to understand
|
36
40
|
def send(uuid, conn_id, msg)
|
37
41
|
header = "%s %d:%s" % [uuid, conn_id.join(' ').length, conn_id.join(' ')]
|
38
42
|
string = header + ', ' + msg
|
@@ -40,12 +44,14 @@ class Connection
|
|
40
44
|
@response_sock.send_string string, ZMQ::NOBLOCK
|
41
45
|
end
|
42
46
|
|
43
|
-
|
44
|
-
|
47
|
+
# reply to an env with `message` string
|
48
|
+
def reply(env, message)
|
49
|
+
self.send(env[:sender], [env[:conn_id]], message)
|
45
50
|
end
|
46
51
|
|
47
|
-
|
48
|
-
|
52
|
+
# reply to a req with a valid http header
|
53
|
+
def reply_http(env, body, code=200, headers={"Content-type" => "text/html"})
|
54
|
+
self.reply(env, http_response(body, code, headers))
|
49
55
|
end
|
50
56
|
|
51
57
|
private
|
@@ -56,14 +62,25 @@ class Connection
|
|
56
62
|
"HTTP/1.1 #{code} #{StatusMessage[code.to_i]}\r\n#{headers_s}\r\n\r\n#{body}"
|
57
63
|
end
|
58
64
|
|
65
|
+
def parse_netstring(ns)
|
66
|
+
len, rest = ns.split(':', 2)
|
67
|
+
len = len.to_i
|
68
|
+
raise "Netstring did not end in ','" unless rest[len].chr == ','
|
69
|
+
[ rest[0...len], rest[(len+1)..-1] ]
|
70
|
+
end
|
71
|
+
|
59
72
|
def parse(msg)
|
60
|
-
if
|
73
|
+
if msg.nil? || msg.empty?
|
61
74
|
return nil
|
62
75
|
end
|
63
76
|
|
64
|
-
|
65
|
-
|
66
|
-
|
77
|
+
env = {}
|
78
|
+
env[:sender], env[:conn_id], env[:path], rest = msg.split(' ', 4)
|
79
|
+
env[:headers], head_rest = parse_netstring(rest)
|
80
|
+
env[:body], _ = parse_netstring(head_rest)
|
81
|
+
|
82
|
+
env[:headers] = JSON.parse(env[:headers])
|
83
|
+
return env
|
67
84
|
end
|
68
85
|
|
69
86
|
# From WEBrick: thanks dawg.
|
@@ -7,6 +7,16 @@ module Aleph
|
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
+
# take the route and pattern and keys and this function will match the keyworded params in
|
11
|
+
# the url with the pattern. Example:
|
12
|
+
#
|
13
|
+
# url: /user/2/view/345
|
14
|
+
# pattern: /user/:id/view/:comment
|
15
|
+
#
|
16
|
+
# returns:
|
17
|
+
#
|
18
|
+
# params = {id: 2, comment: 345}
|
19
|
+
#
|
10
20
|
def process_route(route, pattern, keys, values = [])
|
11
21
|
return unless match = pattern.match(route)
|
12
22
|
values += match.captures.map { |v| URI.decode(v) if v }
|
@@ -32,7 +42,7 @@ Aleph::Base.replier_proc = Proc.new do
|
|
32
42
|
end
|
33
43
|
msg.when(Reply) do |m|
|
34
44
|
begin
|
35
|
-
conn.reply_http(m.env, m.
|
45
|
+
conn.reply_http(m.env, m.body, m.code, m.headers)
|
36
46
|
rescue Exception => e
|
37
47
|
puts "Actor[:replier]: I messed up with exception: #{e.message}"
|
38
48
|
end
|
@@ -44,7 +54,6 @@ end
|
|
44
54
|
# this nifty mess helps us just to use the output of the Procs that handle
|
45
55
|
# the request instead of making the user manually catch messages and send
|
46
56
|
# them out to the replier.
|
47
|
-
|
48
57
|
Aleph::Base.container_proc = Proc.new do
|
49
58
|
data = Actor.receive
|
50
59
|
env, proccess = data.env, data.proccess
|
@@ -53,7 +62,7 @@ Aleph::Base.container_proc = Proc.new do
|
|
53
62
|
#just like Rack: env, code, headers, body. HINT HINT
|
54
63
|
Aleph::Base.replier << Reply.new(env, response[0], response[1], response[2])
|
55
64
|
else
|
56
|
-
Aleph::Base.replier << Reply.new(env, 200, {"Content-Type"
|
65
|
+
Aleph::Base.replier << Reply.new(env, 200, {"Content-Type", "text/html;charset=utf-8", "Connection", "keep-alive", "Server", "Armstrong", "X-Frame-Options", "sameorigin", "X-XSS_Protection", "1; mode=block"}, response)
|
57
66
|
end
|
58
67
|
end
|
59
68
|
|
@@ -73,7 +82,6 @@ Aleph::Base.request_handler_proc = Proc.new do
|
|
73
82
|
|
74
83
|
f.when(Request) do |r|
|
75
84
|
failure = true
|
76
|
-
r.env[:headers] = JSON.parse(r.env[:headers])
|
77
85
|
verb = r.env[:headers]["METHOD"]
|
78
86
|
routes[verb].each do |route|
|
79
87
|
if route.route[0].match(r.env[:path])
|
data/lib/armstrong.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
require 'actor'
|
2
2
|
require 'rubygems'
|
3
3
|
require 'lazy'
|
4
|
-
require 'open-uri'
|
5
|
-
require 'json'
|
6
4
|
|
7
5
|
libdir = File.dirname(__FILE__)
|
8
6
|
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
@@ -16,6 +14,7 @@ module Aleph
|
|
16
14
|
class << self
|
17
15
|
attr_accessor :conn, :routes
|
18
16
|
|
17
|
+
# uuid generator. There's a pretty low chance of collision.
|
19
18
|
def new_uuid
|
20
19
|
values = [
|
21
20
|
rand(0x0010000),
|
@@ -76,7 +75,16 @@ module Aleph
|
|
76
75
|
end
|
77
76
|
end
|
78
77
|
|
79
|
-
class Armstrong < Base
|
78
|
+
class Armstrong < Base
|
79
|
+
|
80
|
+
# the kicker. It all gets launched from here.
|
81
|
+
# this function makes a new connection object to handle the communication,
|
82
|
+
# promises to start the replier, request handler, and their supervisor,
|
83
|
+
# gives the replier the connection information, tells the request_handler
|
84
|
+
# what routes it should be able to match, then checks that all of the services
|
85
|
+
# are running correctly, gives us a launch time, then jumps into our main loop
|
86
|
+
# that waits for an incoming message, parses it, and sends it off to be
|
87
|
+
# operated on by the request handler. Boom.
|
80
88
|
def self.run!
|
81
89
|
uuid = new_uuid
|
82
90
|
puts "starting Armstrong as mongrel2 service #{uuid}"
|
@@ -138,6 +146,7 @@ module Aleph
|
|
138
146
|
self.target = Armstrong
|
139
147
|
end
|
140
148
|
|
149
|
+
# Sinatras secret sauce.
|
141
150
|
at_exit { Armstrong.run! }
|
142
151
|
end
|
143
152
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: armstrong
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 62288469761365696
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 4
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.4.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Artem Titoulenko
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-11-
|
18
|
+
date: 2011-11-20 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: ffi
|