canson 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 19cfa746b443d79afafcaf8feeb8336e8f42ef80
4
+ data.tar.gz: 2169d634254c158235eea0fc4c49078fccb5934f
5
+ SHA512:
6
+ metadata.gz: 6423e71a01382ca72b94a1d5c90006add6197ca3b5b083dda1f187ae37e652d1312acb5e4a45c2bf52c948405834926fbdb3d7fd751bc7436a316855c6b893a7
7
+ data.tar.gz: cc584f2eab5c44b0a47773129821c9e82191007552a3038e5d7caa449a1e5673f159030d3669d7705394799e8e4de64871b6eba3d9bc45166e1f06c81a7d8702
data/.env ADDED
File without changes
@@ -0,0 +1,35 @@
1
+ # inherit_from: .rubocop_todo.yml
2
+
3
+ AllCops:
4
+ Exclude:
5
+ - 'bin/*'
6
+ - 'config/**/*'
7
+ - 'deps/**/*'
8
+ - 'spec/**/*'
9
+ - 'test/**/*'
10
+ - 'lib/erlport/**/*'
11
+ - 'Guardfile'
12
+ - 'Rakefile'
13
+ - 'Gemfile'
14
+ - 'canson.gemspec'
15
+ - '*.ex'
16
+ - '*.exs'
17
+
18
+ Metrics/CyclomaticComplexity:
19
+ Max: 9
20
+
21
+ Metrics/PerceivedComplexity:
22
+ Max: 9
23
+
24
+ Metrics/ClassLength:
25
+ Max: 250
26
+
27
+ # If you are using the --enable-frozen-string-literal flag
28
+ # You might also want to add this:
29
+ Style/FrozenStringLiteralComment:
30
+ Enabled: true
31
+
32
+ Style/SafeNavigation:
33
+ # Safe navigation may cause a statement to start returning `nil` in addition
34
+ # to whatever it used to return.
35
+ ConvertCodeThatCanStartToReturnNil: true
@@ -0,0 +1,15 @@
1
+ sudo: false
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.3.1
6
+ bundler_args: --jobs=2
7
+ cache: bundler
8
+ before_install: gem install bundler -v 1.13.6
9
+
10
+ script:
11
+ - bundle install
12
+ - rake
13
+ - gem install bundler-audit
14
+ - bundle-audit update
15
+ - bundle-audit check
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+ group :development, :test do
5
+ gem 'bundler', '~> 1.7'
6
+ gem 'rake', '~> 11.3.0'
7
+ gem 'byebug'
8
+ gem 'minitest', '~> 5.0'
9
+ gem 'guard', '~> 2.14.0'
10
+ gem 'guard-bundler', '~> 2.1.0'
11
+ gem 'guard-minitest', '~> 2.4.6'
12
+ gem 'rubocop', '~> 0.45.0'
13
+ gem 'guard-rubocop', '~> 1.2.0'
14
+ gem 'minitest-reporters', '~> 1.1.11'
15
+ gem 'minitest-vcr', '~> 1.4.0'
16
+ gem 'webmock', '~> 2.1.0'
17
+ gem "rack-test", "~> 0.6"
18
+ end
@@ -0,0 +1,24 @@
1
+ # require 'rubocop'
2
+ # require 'dotenv'
3
+ # Dotenv.load(File.expand_path('../.env.test', __FILE__))
4
+
5
+ guard :bundler do
6
+ require 'guard/bundler'
7
+ require 'guard/bundler/verify'
8
+ helper = Guard::Bundler::Verify.new
9
+ files = ['Gemfile']
10
+ files += Dir['*.gemspec'] if files.any? { |f| helper.uses_gemspec?(f) }
11
+ # Assume files are symlinked from somewhere
12
+ files.each { |file| watch(helper.real_path(file)) }
13
+ end
14
+
15
+ guard :minitest, test_folders: 'spec', test_file_patterns: '*_spec.rb', all_on_start: false do
16
+ watch(%r{^spec/(.*)_spec\.rb$})
17
+ watch(%r{^lib/canson/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
18
+ watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
19
+ end
20
+
21
+ guard :rubocop, all_on_start: false do
22
+ watch(%r{.+\.rb$})
23
+ watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
24
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Jaigouk Kim
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,132 @@
1
+ # Canson
2
+
3
+ small rack based framework that can run websocket. 20K connections can be handled
4
+
5
+ ## Run the example chat app
6
+
7
+ ```
8
+ cd spec/test_app_root
9
+ bundle install
10
+ bundler exec iodine -p 3000 -t 16 -w 4
11
+ ```
12
+
13
+ open localhost:3000 in browser
14
+
15
+ ## Example app
16
+
17
+ in `spec/test_app_root`
18
+
19
+ ```
20
+ require 'canson'
21
+ class TestApp < Canson::Base
22
+
23
+ def self.print_out
24
+ puts 'hijack'
25
+ end
26
+
27
+ get '/' do
28
+ print_out
29
+ {results: 'hi'}
30
+ end
31
+
32
+ get '/ask' do |params|
33
+ name = params[:name]
34
+ {results: name}
35
+ end
36
+
37
+ on_open do
38
+ puts '================================'
39
+ puts 'We have a websocket connection'
40
+ puts '================================'
41
+ end
42
+
43
+ on_close do
44
+ puts "Bye Bye... #{count} connections left..."
45
+ end
46
+
47
+ on_shutdown do
48
+ write 'The server is shutting down, goodbye.'
49
+ end
50
+
51
+ on_message do |params|
52
+ data = params[:data]
53
+ ws = params[:ws]
54
+ nickname = params[:nickname]
55
+ tmp = "#{nickname}: #{data}"
56
+ ws.write tmp
57
+ ws.each { |h| h.write tmp }
58
+ puts '================================'
59
+ puts "got message: #{data} encoded as #{data.encoding}"
60
+ puts "broadcasting #{tmp.bytesize} bytes with encoding #{tmp.encoding}"
61
+ puts '================================'
62
+ end
63
+ end
64
+ ```
65
+
66
+ ```
67
+ require './test_app.rb'
68
+ run TestApp.new
69
+ ```
70
+
71
+
72
+ ## Usage
73
+
74
+ Given the following piece of ruby code:
75
+
76
+ ```ruby
77
+ # config.ru
78
+
79
+ require "canson"
80
+
81
+ get "/index" do
82
+ { results: [1, 2, 3] }
83
+ end
84
+ ```
85
+
86
+ > The server is run via `bundle exec rackup --port 3000`.
87
+
88
+ When requested with `curl http://localhost:3000/bla -i`, it should return:
89
+
90
+ ```
91
+ HTTP/1.1 200 OK
92
+ Content-Type: application/json
93
+
94
+ {"results": [1, 2, 3]}
95
+ ```
96
+
97
+ Given the following piece of ruby code:
98
+
99
+ ```ruby
100
+ # config.ru
101
+
102
+ require "trialday"
103
+
104
+ get "/bla" do
105
+ { results: [1, 2, 3] }
106
+ end
107
+
108
+ post "/bla" do |params|
109
+ name = params[:name]
110
+
111
+ { name: name }
112
+ end
113
+ ```
114
+
115
+ When requested with `curl http://localhost:3000/index -i`, it should return:
116
+
117
+ ```
118
+ HTTP/1.1 200 OK
119
+ Content-Type: application/json
120
+
121
+ {"results": [1, 2, 3]}
122
+ ```
123
+
124
+ When requested with `curl -XPOST http://localhost:3000/bla -i -H "Content-Type: application/json" -d '{"name": "Mario"}'`, it should return:
125
+
126
+ ```
127
+ HTTP/1.1 200 OK
128
+ Content-Type: application/json
129
+
130
+ {"name": "Mario"}
131
+ ```
132
+
@@ -0,0 +1,13 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+ require 'dotenv'
4
+ require 'dotenv/tasks'
5
+ Dotenv.load(File.expand_path('../.env.test', __FILE__))
6
+
7
+ Rake::TestTask.new(test: :dotenv) do |t|
8
+ t.libs << 'spec'
9
+ t.libs << 'lib'
10
+ t.test_files = FileList['spec/**/*_spec.rb']
11
+ end
12
+
13
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "canson"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'iodine'
4
+ require 'rack'
5
+
6
+ if ARGV[0] =~ /(\-\?)|(help)|(\?)$/
7
+ puts <<-EOS
8
+ Iodine's HTTP/Websocket server version #{Iodine::VERSION}
9
+ Use:
10
+ iodine <options> <filename>
11
+ Both <options> and <filename> are optional.
12
+ Available options:
13
+ -p Port number. Default: 3000.
14
+ -t Number of threads. Default: 1 => single worker thread.
15
+ -w Number of worker processes. Default: 1 => a single process.
16
+ -www Public folder for static file serving. Default: nil (none).
17
+ -v Log responses.
18
+ -q Never log responses.
19
+ -warmup Warmup invokes autoloading (lazy loading) during server startup.
20
+ -maxbd Maximum Mb per HTTP message (max body size). Default: ~50Mb.
21
+ -maxms Maximum Bytes per Websocket message. Default: ~250Kb.
22
+ -ping Websocket ping interval in seconds. Default: 40 seconds.
23
+ <filename> Defaults to: config.ru
24
+ Example:
25
+ iodine -p 80
26
+ iodine -p 8080 path/to/app/conf.ru
27
+ iodine -p 8080 -w 4 -t 16
28
+ EOS
29
+ exit(0)
30
+ end
31
+
32
+ filename = (ARGV[-2].to_s[0] != '-' && ARGV[-1].to_s[0] != '-' && ARGV[-1]) || 'config.ru'
33
+ app, opt = Rack::Builder.parse_file filename
34
+ if ARGV.index('-maxbd') && ARGV[ARGV.index('-maxbd') + 1]
35
+ Iodine::Rack.max_body_size = ARGV[ARGV.index('-maxbd') + 1].to_i
36
+ end
37
+ if ARGV.index('-maxms') && ARGV[ARGV.index('-maxms') + 1]
38
+ Iodine::Rack.max_msg_size = ARGV[ARGV.index('-maxms') + 1].to_i
39
+ end
40
+ if ARGV.index('-ping') && ARGV[ARGV.index('-ping') + 1]
41
+ Iodine::Rack.ws_timeout = ARGV[ARGV.index('-ping') + 1].to_i
42
+ end
43
+ if ARGV.index('-www') && ARGV[ARGV.index('-www') + 1]
44
+ Iodine::Rack.public = ARGV[ARGV.index('-www') + 1]
45
+ end
46
+ Iodine::Rack.log = true if ARGV.index('-v')
47
+ Iodine::Rack.log = false if ARGV.index('-q')
48
+ Iodine.warmup if ARGV.index('-warmup')
49
+ Iodine::Rack.run(app, opt)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,45 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'canson/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "canson"
8
+ spec.version = Canson::VERSION
9
+ spec.authors = ["Jaigouk Kim"]
10
+ spec.email = ["ping@jaigouk.kim"]
11
+
12
+ spec.summary = %q{grpc}
13
+ spec.description = %q{grpc based webframework}
14
+ spec.homepage = "https://github.com/jaigouk/canson"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ # if spec.respond_to?(:metadata)
20
+ # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
21
+ # else
22
+ # raise "RubyGems 2.0 or newer is required to protect against " \
23
+ # "public gem pushes."
24
+ # end
25
+
26
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
+ f.match(%r{^(test|spec|features)/})
28
+ end
29
+ spec.bindir = "bin"
30
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_dependency 'dry-monads', '~> 0.2.1'
34
+ spec.add_dependency 'dry-matcher', '~> 0.5'
35
+ spec.add_dependency 'dry-transaction', '~> 0.8'
36
+ spec.add_dependency 'grpc', '~> 1.0.1'
37
+ spec.add_dependency 'http-2', '~> 0.8.2'
38
+ spec.add_dependency 'http_parser.rb', '~> 0.6'
39
+ spec.add_dependency 'dotenv'
40
+ spec.add_runtime_dependency 'iodine', '~> 0.2.3'
41
+ spec.add_runtime_dependency 'rack', '~> 2.0.1'
42
+ spec.add_runtime_dependency 'tilt', '~> 2.0.5'
43
+ spec.add_runtime_dependency 'mustermann', '~> 0.4'
44
+ spec.add_runtime_dependency 'mime-types', '~> 2.4'
45
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tilt'
4
+ require 'rack'
5
+ require 'mustermann'
6
+ require 'mime-types'
7
+ require 'canson/version'
8
+ require 'canson/web_socket'
9
+ require 'canson/route_map'
10
+ require 'canson/responder'
11
+ require 'canson/base'
12
+
13
+ module Canson
14
+ class RequestError
15
+ end
16
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/utils'
4
+ require 'rack/media_type'
5
+ require 'json'
6
+
7
+ module Canson
8
+ # used to create a new app
9
+ # App < CansonBase
10
+ # get '/foo' do
11
+ # {result: 'ok'}
12
+ # end
13
+ # end
14
+ class Base
15
+ extend Forwardable
16
+ include Mustermann
17
+
18
+ attr_reader :klass
19
+ attr_accessor :responder
20
+
21
+ def_delegators :@klass, :routes, :method_missing, :filename
22
+
23
+ class << self
24
+ # def inherited(subclass)
25
+ # # subclass.routes @routes
26
+ # subclass.filename @filename
27
+ # end
28
+
29
+ [:on_open, :on_close, :on_shutdown, :on_message].each do |m|
30
+ define_method m do |&block|
31
+ @responder = Responder.new :ws, &block
32
+ routes[m]['websocket_method'] = @responder
33
+ @responder.call
34
+ end
35
+ end
36
+
37
+ [:get, :post, :put, :delete, :options].each do |m|
38
+ define_method m do |path, opts = {}, &block|
39
+ path = ::Mustermann.new path, opts
40
+ @responder = Responder.new m, &block
41
+ routes[m][path.to_s] = @responder
42
+ @responder.call
43
+ end
44
+ end
45
+
46
+ def routes(r = nil)
47
+ return @routes = r if r
48
+ @routes ||= Hash.new { |h, k| h[k] = RouteMap.new }
49
+ end
50
+ end
51
+
52
+ def initialize(responder = Responder.new)
53
+ @responder = responder
54
+ @klass = self.class
55
+ @websocket = nil
56
+ @filename = File.expand_path('./index.html')
57
+ end
58
+
59
+ def filename
60
+ @filename
61
+ end
62
+
63
+ def call(env)
64
+ m = env['REQUEST_METHOD'].tr('/', '').downcase.to_sym
65
+ target = routes[m][env['PATH_INFO']]
66
+
67
+ if m == :get && env['PATH_INFO'] == '/' && File.file?(filename)
68
+ out = File.open(filename)
69
+ return [200, { 'X-Sendfile' => filename,
70
+ 'Content-Length' => out.size }, out]
71
+ end
72
+
73
+ return handle_socket(env) if env['HTTP_UPGRADE'] == 'websocket'
74
+ return [404, {}, ['no route']] unless target
75
+ return call_result(env, target)
76
+ rescue => e
77
+ puts e.message
78
+ [500, {}, ["server error #{e.message}".to_json]]
79
+ end
80
+
81
+ def handle_socket(env)
82
+ nickname = env['PATH_INFO'][1..-1].force_encoding 'UTF-8'
83
+ if env['HTTP_UPGRADE'.freeze] =~ /websocket/i
84
+ routes[:on_message][nickname] ||= Canson::Websocket.new(env)
85
+ routes[:on_message][nickname].on_open = routes[:on_open]['websocket_method']
86
+ routes[:on_message][nickname].on_close = routes[:on_close]['websocket_method']
87
+ routes[:on_message][nickname].on_shutdown = routes[:on_shutdown]['websocket_method']
88
+ routes[:on_message][nickname].on_message = routes[:on_message]['websocket_method']
89
+ env['upgrade.websocket'.freeze] = routes[:on_message][nickname]
90
+ return [0, {}, []]
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def call_result(env, target)
97
+ param = get_param(env)
98
+ result = if param.nil?
99
+ self.class.instance_exec(&(target.call))
100
+ else
101
+ self.class.instance_exec(param, &(target.call))
102
+ end
103
+ return [404, {}, ['not found']] if result.values.include? nil
104
+ [200, { 'Content-Type' => 'application/json' }, [result.to_json]]
105
+ end
106
+
107
+ def get_param(env)
108
+ req = Rack::Request.new(env)
109
+ param = req.params.empty? ? req.body.read : req.params
110
+ return if param.empty?
111
+ param_to_hash(param)
112
+ end
113
+
114
+ def param_to_hash(param)
115
+ if param.class == Hash
116
+ if param.keys.first.include?(':')
117
+ return to_hash(JSON.parse(param.keys.first))
118
+ end
119
+ to_hash(param)
120
+ else
121
+ to_hash(JSON.parse(param))
122
+ end
123
+ end
124
+
125
+ def to_hash(ele)
126
+ ele.map { |k, v| [k.to_sym, v] }.to_h
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canson
4
+ # responsible to return the block
5
+ class Responder
6
+ attr_reader :method, :response_handler
7
+ attr_writer :base
8
+
9
+ def initialize(method = nil, &block)
10
+ @method = method
11
+ @response_handler = block
12
+ end
13
+
14
+ def call
15
+ @response_handler
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canson
4
+ # keeps routes and responders
5
+ class RouteMap
6
+ def initialize
7
+ @hash = {}
8
+ end
9
+
10
+ def []=(route, responder)
11
+ @hash[route] = responder
12
+ end
13
+
14
+ def [](route)
15
+ _path, responder = @hash.find { |k, _v| k.match(route) }
16
+ responder
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canson
4
+ VERSION = '0.1.0'.freeze
5
+ end
@@ -0,0 +1,44 @@
1
+ module Canson
2
+ class Websocket
3
+ def initialize(env)
4
+ @nickname = env['PATH_INFO'][1..-1].force_encoding 'UTF-8'
5
+ @on_open = nil
6
+ @on_close = nil
7
+ @on_shutdown = nil
8
+ @on_message = nil
9
+ end
10
+
11
+ def on_open=(responder)
12
+ @on_open = responder
13
+ end
14
+
15
+ def on_close=(responder)
16
+ @on_close = responder
17
+ end
18
+
19
+ def on_shutdown=(responder)
20
+ @on_shutdown = responder
21
+ end
22
+
23
+ def on_message=(responder)
24
+ @on_message = responder
25
+ end
26
+
27
+ def on_open
28
+ @on_open.class.instance_exec({count: self.count}, &(@on_open.call))
29
+ end
30
+
31
+ def on_close
32
+ @on_close.class.instance_exec({count: self.count}, &(@on_close.call))
33
+ end
34
+
35
+ def on_shutdown
36
+ @on_shutdown.class.instance_exec(&(@on_shutdown.call))
37
+ end
38
+
39
+ def on_message(data)
40
+ @on_message.class.instance_exec({data: data, ws: self, nickname: @nickname},
41
+ &(@on_message.call))
42
+ end
43
+ end
44
+ end
data/refs.md ADDED
@@ -0,0 +1,113 @@
1
+
2
+ ## Parsing
3
+
4
+ https://github.com/sinatra/mustermann
5
+ https://github.com/tmm1/http_parser.rb
6
+ https://github.com/ngauthier/tubesock
7
+
8
+ ## Streaming
9
+
10
+ https://rosenfeld.herokuapp.com/en/articles/ruby-rails/2016-07-02-the-sad-state-of-streaming-in-ruby-web-applications
11
+ Each request thread (or process) has access to a "response" object that accepts a "write" call that goes directly to the socket's output (or after a "flush" call).
12
+ It would be awesome if Ruby web applications had the option to use a more flexible API, more friendly to streamed responses, including SSE and websockets. Hijacking currently seems to be considered a second-class citizen since they are usually ignored by major web frameworks like Rails itself.
13
+
14
+ https://bowild.wordpress.com/2016/07/31/the-dark-side-of-the-rack/
15
+ what if everything we had to do to upgrade from HTTP to Websockets was something like this env[upgrade.websockets] = MyCallbacks.new(env)…?
16
+
17
+ We can unify the IO polling for all IO objects (HTTP, Websocket, no need for hijack). This is bigger then you think.
18
+ We can parse websocket data before entering the GIL, winning some of our concurrency back. This also means we can better utilize multi-core CPUs.
19
+
20
+ We don’t need to know anything about network programming – let the people programming servers do what they do best and let the people who write web applications focus on their application logic.
21
+ https://gist.github.com/boazsegev/1466442c913a8dd4271178cab9d98a27
22
+ https://github.com/boazsegev/iodine
23
+
24
+ http://rubykaigi.org/2015/presentations/tenderlove
25
+
26
+ https://github.com/SamSaffron/message_bus
27
+
28
+ rack hijack
29
+ https://github.com/ngauthier/tubesock
30
+
31
+ https://bowild.wordpress.com/2016/07/31/the-dark-side-of-the-rack/
32
+
33
+ https://blog.heroku.com/real_time_rails_implementing_websockets_in_rails_5_with_action_cable
34
+
35
+
36
+ https://blog.heroku.com/real_time_rails_implementing_websockets_in_rails_5_with_action_cable
37
+
38
+
39
+ https://klibert.pl/statics/python-and-elixir/#/
40
+
41
+ https://www.reddit.com/r/ruby/comments/4r9alo/the_sad_state_of_streaming_support_in_ruby_web/
42
+
43
+ If your servers were implementing HTTP/2, then hijacking the IO would break the protocol and (hopefully) terminate the connection with no data being received.
44
+ HTTP/2 is a binary protocol with packet headers and footer. If the hijacked IO doesn't write the data as HTTP/2 packets and sends the end of stream flag upon completion, then the receiving party will simply assume the data is garbage and (if implementing the standard correctly) will disconnect.
45
+ This is the reason I though hijacking is limited and approaching an end-of-life cycle for HTTP requests (will soon be impossible). It's only possible long term implementation is HTTP upgrade requests which usually means a long-lived connection is being established with either Websockets or HTTP/2 (although other protocols can also be used with the technique).
46
+
47
+ https://gist.github.com/TiagoCardoso1983/591d48cfde04219f801fa7fd7966571c
48
+
49
+ For http2, the same above stands, with a few add-ons:
50
+ Ruby's openssl is (at least, it was) missing some bugfixes for ALPN negotiation (is it still valid?).
51
+
52
+ Ruby is currently missing an optimized parser for http2 requests (not a big hinderance, as there's an http2 pure ruby gem around).
53
+ http2-to-http1 on the web server is seemingly "good enough".
54
+ http2 protocol is only a big gain if one is using an evented application server, as the big thing over there is the frame multiplexing.
55
+
56
+ So, we have thin and reel. Both of them use C-written event loops (reel uses libev), which means we're out of ruby core, and are not widely used (correct me if I'm wrong, but is reel compatible with most available middlewares? Is it running rails for anyone?).
57
+
58
+ rack needs to die and be born again. event-based middlewares, #call(request, response) instead of #call(env), a proper stream object with less socket-y semantics, the rack hijack hack won't stand.
59
+
60
+ nio4r (celluloid-io/reel) looks much better, but ships with a patched version of libev because no-ruby-core-GIL-API). It brings with it the libev's "problems", which are no Windows support or no support for not-network file descriptors (file, pipes) (not really a problem when there is no competition though).
61
+
62
+ ### grpc
63
+
64
+ * stackoverflow
65
+
66
+ http://stackoverflow.com/questions/35065875/how-to-bring-a-grpc-defined-api-to-the-web-browser
67
+
68
+ We want to build a Javascript/HTML gui for our gRPC-microservices. Since gRPC is not supported on the browser side, we thought of using web-sockets to connect to a node.js server, which calls the target service via grpc. We struggle to find an elegant solution to do this. Especially, since we use gRPC streams to push events between our micro-services. It seems that we need a second RPC system, just to communicate between the front end and the node.js server. This seems to be a lot of overhead and additional code that must be maintained.
69
+
70
+ Does anyone have experience doing something like this or has an idea how this could be solved?
71
+
72
+ https://github.com/tmc/grpc-websocket-proxy sounds like it may meet your needs. This translates json over web sockets to grpc (layer on top of grpc-gateway).
73
+
74
+
75
+ https://coreos.com/blog/gRPC-protobufs-swagger.html
76
+
77
+ One of the key reasons we chose gRPC is because it uses HTTP/2, enabling applications to present both a HTTP 1.1 REST+JSON API and an efficient gRPC interface on a single TCP port. This gives developers compatibility with the REST web ecosystem while advancing a new, high-efficiency RPC protocol. With Go 1.6 recently released, Go ships with a stable net/http2 package by default.
78
+
79
+
80
+ https://github.com/grpc-ecosystem/grpc-gateway
81
+
82
+ https://github.com/ruby-concurrency/concurrent-ruby/blob/master/doc/channel.md
83
+
84
+ https://github.com/paralin/grpc-bus
85
+
86
+ # RubyConf 2016
87
+
88
+ RubyConf 2016 - To Clojure and back: writing and rewriting in Ruby by Phill MV
89
+ https://www.youtube.com/watch?v=doZ0XAc9Wtc
90
+
91
+ 1. "value" objects
92
+ tcrayford/values
93
+
94
+ 2. "manager" object
95
+ controller: handles input
96
+ model: value object. handles persistence(db query, save)
97
+ manager: handles state
98
+ ```
99
+ class PackageManager
100
+ attr_reader :platform, :release
101
+ def initialize platform, release
102
+ @platform = platform
103
+ @release = release
104
+ end
105
+ # methods never modify inputs.
106
+ # never set internal state
107
+ def find_existing_packages package_list
108
+ return Package.none if package_list.empty?
109
+ query = Package.where platform: self.platform,
110
+ release: self.release
111
+ query.search_qunique_fields(package_list.map(&:uniq_values))
112
+ end
113
+ ```
metadata ADDED
@@ -0,0 +1,235 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: canson
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jaigouk Kim
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-12-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-monads
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-matcher
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.5'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-transaction
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.8'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: grpc
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.0.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: http-2
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.8.2
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.8.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: http_parser.rb
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.6'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.6'
97
+ - !ruby/object:Gem::Dependency
98
+ name: dotenv
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: iodine
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.2.3
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.2.3
125
+ - !ruby/object:Gem::Dependency
126
+ name: rack
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 2.0.1
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 2.0.1
139
+ - !ruby/object:Gem::Dependency
140
+ name: tilt
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 2.0.5
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 2.0.5
153
+ - !ruby/object:Gem::Dependency
154
+ name: mustermann
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.4'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.4'
167
+ - !ruby/object:Gem::Dependency
168
+ name: mime-types
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '2.4'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '2.4'
181
+ description: grpc based webframework
182
+ email:
183
+ - ping@jaigouk.kim
184
+ executables:
185
+ - console
186
+ - iodine
187
+ - setup
188
+ extensions: []
189
+ extra_rdoc_files: []
190
+ files:
191
+ - ".env"
192
+ - ".gitignore"
193
+ - ".rubocop.yml"
194
+ - ".travis.yml"
195
+ - Gemfile
196
+ - Guardfile
197
+ - LICENSE.txt
198
+ - README.md
199
+ - Rakefile
200
+ - bin/console
201
+ - bin/iodine
202
+ - bin/setup
203
+ - canson.gemspec
204
+ - lib/canson.rb
205
+ - lib/canson/base.rb
206
+ - lib/canson/responder.rb
207
+ - lib/canson/route_map.rb
208
+ - lib/canson/version.rb
209
+ - lib/canson/web_socket.rb
210
+ - refs.md
211
+ homepage: https://github.com/jaigouk/canson
212
+ licenses:
213
+ - MIT
214
+ metadata: {}
215
+ post_install_message:
216
+ rdoc_options: []
217
+ require_paths:
218
+ - lib
219
+ required_ruby_version: !ruby/object:Gem::Requirement
220
+ requirements:
221
+ - - ">="
222
+ - !ruby/object:Gem::Version
223
+ version: '0'
224
+ required_rubygems_version: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - ">="
227
+ - !ruby/object:Gem::Version
228
+ version: '0'
229
+ requirements: []
230
+ rubyforge_project:
231
+ rubygems_version: 2.6.7
232
+ signing_key:
233
+ specification_version: 4
234
+ summary: grpc
235
+ test_files: []