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 CHANGED
@@ -1,4 +1,4 @@
1
- Gem::Specification.new 'armstrong', '0.3.0' do |s|
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"
@@ -1,4 +1,4 @@
1
- require '../lib/armstrong'
1
+ require File.join(File.dirname(__FILE__), "..", "lib/armstrong")
2
2
 
3
3
  get "/" do
4
4
  "hello world"
@@ -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 msg
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(self.recv)
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
- def reply(request, message)
44
- self.send(request[:uuid], [request[:id]], message)
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
- def reply_http(req, code=200, headers={"Content-type" => "text/html"}, body="")
48
- self.reply(req, http_response(body, code, headers))
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(msg.empty?)
73
+ if msg.nil? || msg.empty?
61
74
  return nil
62
75
  end
63
76
 
64
- uuid, id, path, header_size, headers, body_size, body = msg.match(/^(.{36}) (\d+) (.*?) (\d+):(.*?),(\d+):(.*?),$/).to_a[1..-1]
65
-
66
- return {:uuid => uuid, :id => id, :path => path, :header_size => header_size, :headers => headers, :body_size => body_size, :body => body}
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.code, m.headers, m.body)
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" => "text/html"}, response)
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: 3600368719662819624
4
+ hash: 62288469761365696
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 3
8
+ - 4
9
9
  - 0
10
- version: 0.3.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-17 00:00:00 Z
18
+ date: 2011-11-20 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: ffi