armstrong 0.3.0 → 0.4.0

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