tecepe 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tecepe.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'rspec'
8
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Ismael Celis
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.
@@ -0,0 +1,51 @@
1
+ # Tecepe
2
+
3
+ Launch small (evented) TCP JSON services on a given host:port
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'tecepe'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install tecepe
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ Worker.listen("localhost", 5555) do
23
+
24
+ on 'perms' do |json|
25
+ redis.smember("permissions:#{json['user_id']}", json['product_id']) do |r|
26
+ reply allowed: r
27
+ end
28
+ end
29
+
30
+ end
31
+ ```
32
+
33
+ In reality you would do some more computation than just proxying Redis!
34
+
35
+ ## Clients
36
+
37
+ This is simple enough that it should be easy to write clients in different langs/stacks. See lib/tecepe/blocking_client.rb for a reference implementation.
38
+
39
+ ```ruby
40
+ PEMISSIONS_SERVICE = Tecepe::BlockingClient.new('localhost', 5555)
41
+
42
+ PEMISSIONS_SERVICE.call 'perms', user_id: current_user.id, @product.id # true/false
43
+ ```
44
+
45
+ ## Contributing
46
+
47
+ 1. Fork it
48
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
49
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
50
+ 4. Push to the branch (`git push origin my-new-feature`)
51
+ 5. Create new Pull Request
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,19 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'tecepe/blocking_client'
4
+
5
+ socket = Tecepe::BlockingClient.new('localhost', 5555)
6
+
7
+ delay = ARGV[0]
8
+
9
+ m = <<-eos
10
+ Hello this is a text
11
+
12
+ with line changes
13
+ And more
14
+ eos
15
+
16
+ 20.times do |i|
17
+ response = socket.call 'perms', delay: delay.to_f , m: m # Send request
18
+ puts "RESPONSE: #{response.class.name} - #{response}"
19
+ end
@@ -0,0 +1,13 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'tecepe'
4
+
5
+ Tecepe.listen("localhost", 5555) do
6
+
7
+ on 'perms' do |json|
8
+ EventMachine::Timer.new(json['delay'].to_f) do
9
+ reply message: ">>>you sent: #{json}\n"
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,32 @@
1
+ require "tecepe/version"
2
+ require "tecepe/connection"
3
+
4
+ module Tecepe
5
+ def self.events
6
+ @events ||= {}
7
+ end
8
+
9
+ def self.on(event, &handler)
10
+ events[event] = handler
11
+ end
12
+
13
+ def self.dispatch(connection, event_name, payload)
14
+ if handler = events[event_name]
15
+ connection.instance_exec payload, &handler
16
+ else
17
+ connection.send_error "No handler for event #{event_name}"
18
+ end
19
+ end
20
+
21
+ def self.listen(host, port, &block)
22
+ instance_eval &block
23
+ puts "[listen] #{host}:#{port}"
24
+ EventMachine::run {
25
+ EventMachine::start_server host, port, Tecepe::Connection
26
+ }
27
+ end
28
+
29
+ def self.clear_handlers
30
+ @events = {}
31
+ end
32
+ end
@@ -0,0 +1,87 @@
1
+ require 'socket'
2
+ require 'json'
3
+ require "timeout"
4
+
5
+ # Simple (blocking) client with basic reconnect functionality
6
+ # Some code taken from Redis client
7
+ # https://github.com/redis/redis-rb
8
+ #
9
+ # EXAMPLE:
10
+ # socket = Tecepe::BlockingClient.new('localhost', 5555)
11
+ #
12
+ # 20.times do |i|
13
+ # response = socket.call 'perms', user_id: 3 # Send request
14
+ # puts "RESPONSE: #{response}"
15
+ # end
16
+ #
17
+ module Tecepe
18
+
19
+ class BlockingClient
20
+
21
+ def initialize(host, port, cid = Process.pid)
22
+ @host, @port, @cid = host, port, cid
23
+ @socket = nil
24
+ @reconnect = true
25
+ connect
26
+ end
27
+
28
+ def connected?
29
+ !! @socket
30
+ end
31
+
32
+ def call(event_name, payload)
33
+ json = JSON.generate(event: event_name, cid: @cid, payload: payload)
34
+ ensure_connected do
35
+ @socket.print "#{json}\n"
36
+ JSON.parse(@socket.gets)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def connect
43
+ with_timeout 1 do
44
+ @socket = TCPSocket.new(@host, @port)
45
+ # @socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
46
+ puts "[open]"
47
+ end
48
+ end
49
+
50
+ def disconnect
51
+ return false unless connected?
52
+ @socket.close
53
+ rescue
54
+ ensure
55
+ puts "[close]"
56
+ @socket = nil
57
+ end
58
+
59
+ def with_timeout(seconds, &block)
60
+ Timeout.timeout(seconds, &block)
61
+ end
62
+
63
+ def ensure_connected(&block)
64
+ tries = 0
65
+
66
+ begin
67
+ connect unless connected?
68
+ tries += 1
69
+
70
+ yield
71
+ rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL
72
+ disconnect
73
+
74
+ if tries < 2 && @reconnect
75
+ retry
76
+ else
77
+ raise Errno::ECONNRESET
78
+ end
79
+ rescue Exception
80
+ disconnect
81
+ raise
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+ end
@@ -0,0 +1,71 @@
1
+ require 'eventmachine'
2
+ require 'json'
3
+
4
+ module Tecepe
5
+
6
+ module Connection
7
+
8
+ HEARTBEAT = 5.freeze
9
+
10
+ def post_init
11
+ @heartbeat = setup_heartbeat
12
+ @cid = nil
13
+ log :conn, signature
14
+ super
15
+ end
16
+
17
+ def unbind
18
+ log :bye
19
+ @heartbeat.cancel
20
+ super
21
+ end
22
+
23
+ def receive_data data
24
+ log :data, data
25
+ (@buffer ||= BufferedTokenizer.new).extract(data).each do |line|
26
+ receive_line(line)
27
+ end
28
+ end
29
+
30
+ def receive_line data
31
+ begin
32
+ json = JSON.parse(data)
33
+ @cid = json['cid']
34
+ log :rcvd, json['payload']
35
+ Tecepe.dispatch self, json['event'], json['payload']
36
+ rescue JSON::ParserError => e
37
+ send_error e.message
38
+ rescue Encoding::UndefinedConversionError => e
39
+ send_error e.message
40
+ end
41
+ end
42
+
43
+ def reply(payload = {}, status = 1)
44
+ json = JSON.generate(status: status, payload: payload)
45
+ log :repl, json
46
+ if error?
47
+ log :error
48
+ else
49
+ send_data "#{json}\n"
50
+ end
51
+ end
52
+
53
+ def send_error(msg)
54
+ reply({message: msg}, -1)
55
+ end
56
+
57
+ private
58
+
59
+ def setup_heartbeat
60
+ EventMachine::PeriodicTimer.new(HEARTBEAT) do
61
+ log :heartbeat
62
+ send_data ''
63
+ end
64
+ end
65
+
66
+ def log(key, msg = '')
67
+ puts "#{Process.pid} [#{key} #{@cid}] #{msg}"
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,3 @@
1
+ module Tecepe
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,69 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Tecepe::Connection do
4
+
5
+ def message(args)
6
+ "#{JSON.generate(args)}\n"
7
+ end
8
+
9
+ describe "#receive_data" do
10
+
11
+ before do
12
+ @test_klass = Class.new do
13
+ include Tecepe::Connection
14
+
15
+ attr_reader :replies
16
+
17
+ def send_data raw_data
18
+ (@replies ||= []) << JSON.parse(raw_data)
19
+ end
20
+
21
+ def error?
22
+ false
23
+ end
24
+ end
25
+
26
+ @connection = @test_klass.new
27
+ end
28
+
29
+ after do
30
+ Tecepe.clear_handlers
31
+ end
32
+
33
+ it "triggers event callbacks" do
34
+ Tecepe.on 'test1' do |json|
35
+ json['name'].should == 'ismael'
36
+ end
37
+ @connection.receive_data message(event: 'test1', payload: {name: 'ismael'})
38
+ end
39
+
40
+ it "replies in event callbacks" do
41
+ Tecepe.on 'test1' do |json|
42
+ reply message: "event received!", by: json['name']
43
+ end
44
+ @connection.receive_data message(event: 'test1', payload: {name: 'ismael'})
45
+ @connection.replies.last['status'].should == 1
46
+ @connection.replies.last['payload']['message'].should == 'event received!'
47
+ @connection.replies.last['payload']['by'].should == 'ismael'
48
+ end
49
+
50
+ it 'waits for line breaks to parse messages' do
51
+ Tecepe.on 'test1' do |json|
52
+ reply message: "event received!", pong: json['message']
53
+ end
54
+ @connection.receive_data '{"event":"test1"'
55
+ @connection.receive_data ',"payload":{"message":"hola!"}'
56
+ @connection.receive_data "}\n"
57
+
58
+ @connection.replies.last['payload']['message'].should == 'event received!'
59
+ @connection.replies.last['status'].should == 1
60
+ @connection.replies.last['payload']['pong'].should == 'hola!'
61
+ end
62
+
63
+ it 'replies with error if no handler found' do
64
+ @connection.receive_data message(event: 'foo', payload: {name: 'ismael'})
65
+ @connection.replies.last['status'].should == -1
66
+ @connection.replies.last['payload']['message'].should == 'No handler for event foo'
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.setup
4
+
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+
8
+ require 'tecepe'
9
+
10
+
11
+ require 'rspec'
12
+
13
+ module Support
14
+
15
+ # Forks process and launches a service
16
+ def fork_service(&block)
17
+ begin
18
+ pid = fork do
19
+ trap('TERM') { exit }
20
+ sleep 1
21
+ yield
22
+ end
23
+ ensure
24
+ if pid
25
+ Process.kill("TERM", pid)
26
+ Process.wait(pid)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/tecepe/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Ismael Celis"]
6
+ gem.email = ["ismaelct@gmail.com"]
7
+ gem.description = %q{Tiny evented TCP server for JSON services}
8
+ gem.summary = %q{Tiny evented TCP server for JSON services}
9
+ gem.homepage = ""
10
+
11
+ gem.add_dependency 'eventmachine'
12
+ gem.add_dependency 'json'
13
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
14
+ gem.files = `git ls-files`.split("\n")
15
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ gem.name = "tecepe"
17
+ gem.require_paths = ["lib"]
18
+ gem.version = Tecepe::VERSION
19
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tecepe
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Ismael Celis
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-06-10 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: eventmachine
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ type: :runtime
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: json
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: "0"
35
+ type: :runtime
36
+ version_requirements: *id002
37
+ description: Tiny evented TCP server for JSON services
38
+ email:
39
+ - ismaelct@gmail.com
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files: []
45
+
46
+ files:
47
+ - .gitignore
48
+ - Gemfile
49
+ - LICENSE
50
+ - README.md
51
+ - Rakefile
52
+ - examples/blocking_client.rb
53
+ - examples/latency.rb
54
+ - lib/tecepe.rb
55
+ - lib/tecepe/blocking_client.rb
56
+ - lib/tecepe/connection.rb
57
+ - lib/tecepe/version.rb
58
+ - spec/connection_spec.rb
59
+ - spec/spec_helper.rb
60
+ - tecepe.gemspec
61
+ homepage: ""
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options: []
66
+
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: "0"
81
+ requirements: []
82
+
83
+ rubyforge_project:
84
+ rubygems_version: 1.8.17
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: Tiny evented TCP server for JSON services
88
+ test_files:
89
+ - spec/connection_spec.rb
90
+ - spec/spec_helper.rb