thin-fun_embed 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in thin-fun_embed.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2012 Sokolov Yura 'funny-falcon'
2
+ Copyright (c) 2011 Nils Franzén
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,118 @@
1
+ = Thin::FunEmbed
2
+
3
+ This is minimalistic web server for embedding into EventMachine-enabled application
4
+ based on {idea of Nils Franzén}[http://www.franzens.org/2011/10/writing-minimalistic-web-server-using.html],
5
+ but it uses origin Thin::Request instead of custom wrapper to thin parser.
6
+
7
+ It is intentionally not full fledge rack server, but you could use +send_status_headers_body+ to return
8
+ rack application's response to client.
9
+
10
+ It is capable to serve keep-alive requests (but not pipelined).
11
+
12
+ It is faster than Thin itself cause it allows you to send very custom response, and even for Rack like
13
+ response it doesn't do many checks on your output. But it meens, that you responsible for you response :)
14
+
15
+ Rack hello world, tested with +ab -k -c 500 -n 50000 127.0.0.1:8080/+ on Core i3@1600Mhz
16
+
17
+ * with Thin::FunEmbed (see examples/rack_like.rb) ~ 14000 req/sec
18
+ * with `thin -p 8080 -e production start` ~ 6500 req/sec
19
+
20
+ (But consider, that this is for very dump response. Do not expect big speedup for heavy weight ones')
21
+
22
+ Also, it doesn't bother with keep-alive timeouts, limits and many other things that Thin do. But it is not
23
+ too hard too look for them in the Thin's sources. And, in any way, it is better to place this "server" behind
24
+ "something like" Nginx, which could use keep-alive connections to upstream since version 1.1 .
25
+
26
+ == Installation
27
+
28
+ Add this line to your application's Gemfile:
29
+
30
+ gem 'thin-fun_embed'
31
+
32
+ And then execute:
33
+
34
+ $ bundle
35
+
36
+ Or install it yourself as:
37
+
38
+ $ gem install thin-fun_embed
39
+
40
+ == Usage
41
+
42
+ You should subclass Thin::FunEmbed and override #handle_http_request method. Then you should start it with
43
+ EM.start_server as any other EM::Connection .
44
+
45
+ Abstract example:
46
+ require 'thin/fun_embed.rb'
47
+ class MyServer < Thin::FunEmbed
48
+ def handle_http_request(env)
49
+ if rack_like_response?
50
+ send_status_headers_body(status, headers, body)
51
+ elsif is_200_ok?
52
+ send_200_ok(body)
53
+ elsif is_status_body?
54
+ send_status_body(status, body)
55
+ elsif is_raw_string?
56
+ send_raw_string(full_http_response_string, try_to_keep_alive)
57
+ end
58
+ end
59
+ end
60
+
61
+ host, port = '0.0.0.0', 8080
62
+ EM.run do
63
+ EM.start_server host, port, MyServer
64
+ end
65
+
66
+ 200 ok example:
67
+ require 'thin/fun_embed.rb'
68
+
69
+ class Simple200ok < Thin::FunEmbed
70
+ def handle_http_request(env)
71
+ if rand(2) == 1
72
+ send_200_ok('{"hello":"world"}', 'application/javascript')
73
+ else
74
+ send_200_ok('hello world')
75
+ end
76
+ end
77
+ end
78
+
79
+ EM.run do
80
+ EM.start_server '0.0.0.0', 8080, Simple200ok
81
+ end
82
+
83
+ Rack like example with correct socket closing:
84
+ require 'thin/fun_embed.rb'
85
+
86
+ class RackLikeServer < Thin::FunEmbed
87
+ attr_accessor :app
88
+ def handle_http_request(env)
89
+ send_rack_response(*app.call(env))
90
+ end
91
+ end
92
+
93
+ app = proc{|env| [200, {'Content-Length'=>6}, ['hello', "\n"]]}
94
+
95
+ host, port = '0.0.0.0', 8080
96
+ all_conns = {}
97
+ trap(:INT) do
98
+ EM.schedule{
99
+ all_conns.each{|conn, _| conn.close_after_writting}
100
+ EM.next_tick{ EM.stop }
101
+ }
102
+ end
103
+
104
+ EM.run do
105
+ EM.start_server host, port, RackLikeServer do |conn|
106
+ conn.app = app
107
+ all_conns[conn] = true
108
+ conn.unbind_callback = all_conns.method(:delete)
109
+ end
110
+ end
111
+
112
+ == Contributing
113
+
114
+ 1. Fork it
115
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
116
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
117
+ 4. Push to the branch (`git push origin my-new-feature`)
118
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,2 @@
1
+ app = proc{|env| [200, {'Content-Length'=>'6'}, ['hello', "\n"]]}
2
+ run app
@@ -0,0 +1,28 @@
1
+ # :stopdoc:
2
+ require 'thin/fun_embed.rb'
3
+
4
+ class RackLikeServer < Thin::FunEmbed
5
+ attr_accessor :app
6
+ def handle_http_request(env)
7
+ send_rack_response(*app.call(env))
8
+ end
9
+ end
10
+
11
+ app = proc{|env| [200, {'Content-Length'=>6}, ['hello', "\n"]]}
12
+
13
+ host, port = '0.0.0.0', 8080
14
+ all_conns = {}
15
+ trap(:INT) do
16
+ EM.schedule{
17
+ all_conns.each{|conn, _| conn.close_after_writting}
18
+ EM.next_tick{ EM.stop }
19
+ }
20
+ end
21
+
22
+ EM.run do
23
+ EM.start_server host, port, RackLikeServer do |conn|
24
+ conn.app = app
25
+ all_conns[conn] = true
26
+ conn.unbind_callback = all_conns.method(:delete)
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ # :stopdoc:
2
+ require 'thin/fun_embed.rb'
3
+
4
+ class Simple200ok < Thin::FunEmbed
5
+ def handle_http_request(env)
6
+ if false && rand(2) == 1
7
+ send_200_ok('{"hello":"world"}', 'application/javascript')
8
+ else
9
+ send_200_ok('hello world')
10
+ end
11
+ end
12
+ end
13
+
14
+ EM.run do
15
+ EM.start_server '0.0.0.0', 8080, Simple200ok
16
+ end
@@ -0,0 +1,16 @@
1
+ # :stopdoc:
2
+ require 'thin/fun_embed.rb'
3
+
4
+ class StatusBody < Thin::FunEmbed
5
+ def handle_http_request(env)
6
+ if env[PATH_INFO] =~ /\.js$/
7
+ send_status_body(200, '{"hello":"world"}', 'application/javascript')
8
+ else
9
+ send_status_body(200, 'hello world')
10
+ end
11
+ end
12
+ end
13
+
14
+ EM.run do
15
+ EM.start_server '0.0.0.0', 8080, StatusBody
16
+ end
@@ -0,0 +1,18 @@
1
+ module Thin # :nodoc:
2
+ class FunEmbed
3
+ # Usefull constants for analysing Rack environmentc
4
+ module Constants
5
+ PATH_INFO = 'PATH_INFO'.freeze
6
+ REQUEST_METHOD = 'REQUEST_METHOD'.freeze
7
+ SCRIPT_NAME = 'SCRIPT_NAME'.freeze
8
+ QUERY_STRING = 'QUERY_STRING'.freeze
9
+ SERVER_NAME = 'SERVER_NAME'.freeze
10
+ SERVER_PORT = 'SERVER_PORT'.freeze
11
+
12
+ GET = 'GET'.freeze
13
+ POST = 'POST'.freeze
14
+ DELETE = 'DELETE'.freeze
15
+ PUT = 'PUT'.freeze
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ require 'eventmachine'
2
+ module Thin # :nodoc:
3
+ class FunEmbed < EM::Connection
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
@@ -0,0 +1,179 @@
1
+ require "eventmachine"
2
+ require "thin_parser"
3
+ require "thin/request"
4
+ require "thin/statuses"
5
+ require "thin/fun_embed/version"
6
+ require "thin/fun_embed/constants"
7
+
8
+ module Thin # :nodoc:
9
+ SERVER = "FunEmbed #{FunEmbed::VERSION}".freeze
10
+ module VERSION # :nodoc:
11
+ RACK = [1,1].freeze
12
+ end
13
+
14
+ # minimalistic HTTP server for embedding into EventMachine'd applications
15
+ #
16
+ # Method #handle_http_request accept fully formed Rack environment and should
17
+ # call send http status+headers+body using one of #send_200_ok, #send_status_body,
18
+ # #send_raw_string and #send_rack_response methods or manually. If you send
19
+ # response manually, then you should call #consider_keep_alive method with your wish:
20
+ # whether you want to try keep-alive connection or not.
21
+ #
22
+ # require 'thin/fun_embed.rb'
23
+ #
24
+ # class Simple200ok < Thin::FunEmbed
25
+ # def handle_http_request(env)
26
+ # if rand(2) == 1
27
+ # send_200_ok('{"hello":"world"}', 'application/javascript')
28
+ # else
29
+ # send_200_ok('hello world')
30
+ # end
31
+ # end
32
+ # end
33
+ #
34
+ # EM.run do
35
+ # EM.start_server '0.0.0.0', 8080, Simple200ok
36
+ # end
37
+ #
38
+ class FunEmbed < EM::Connection
39
+ # :stopdoc:
40
+ include Constants
41
+ FULL_KEEP_ALIVE = "Connection: keep-alive\r\n".freeze
42
+ RN = "\r\n".freeze
43
+ RNRN = "\r\n\r\n".freeze
44
+ NL = "\n".freeze
45
+ CONTENT_LENGTH = "Content-Length".freeze
46
+ CONTENT_LENGTH_DT = "\r\nContent-Length: ".freeze
47
+ CONNECTION = 'Connection'.freeze
48
+ KEEP_ALIVE = 'keep-alive'.freeze
49
+ CLOSE = 'close'.freeze
50
+ TEXT_PLAIN = 'text/plain'.freeze
51
+ HTTP_STATUS_CODES = Thin::HTTP_STATUS_CODES
52
+ # :startdoc:
53
+
54
+ # callback, which called from +#unbind+.
55
+ # Could be used for monitoring connections.
56
+ attr_accessor :unbind_callback
57
+
58
+ # signals if client may accept keep-alive connection
59
+ attr :keep_alive
60
+
61
+ def post_init
62
+ @keep_alive = nil
63
+ @parser = Thin::Request.new
64
+ end
65
+
66
+ def receive_data(data)
67
+ if @parser.parse(data)
68
+ @keep_alive = @parser.persistent?
69
+ handle_http_request(@parser.env)
70
+ end
71
+ end
72
+
73
+ # main method for override
74
+ # you ought to put your application logic here
75
+ def handle_http_request(env)
76
+ send_200_ok "you should override #handle_http_request"
77
+ end
78
+
79
+ # send simple '200 OK' response with a body
80
+ def send_200_ok(body = '', type = TEXT_PLAIN, try_keep_alive = true)
81
+ send_data("HTTP/1.1 200 OK\r\nContent-Type: #{type}\r\n"\
82
+ "Content-Length: #{body.bytesize}\r\n"\
83
+ "#{keep_alive ? FULL_KEEP_ALIVE : nil}\r\n")
84
+ send_data(body)
85
+ consider_keep_alive(try_keep_alive)
86
+ end
87
+
88
+ # send simple response with status, body and type
89
+ def send_status_body(status, body = '', type = TEXT_PLAIN, try_keep_alive = true)
90
+ status = status.to_i
91
+ if status < 200 || status == 204 || status == 304
92
+ send_data("HTTP/1.1 #{status} #{HTTP_STATUS_CODES[status]}\r\n"\
93
+ "#{keep_alive ? FULL_KEEP_ALIVE : nil}\r\n")
94
+ else
95
+ send_data("HTTP/1.1 #{status} #{HTTP_STATUS_CODES[status]}\r\n"\
96
+ "Content-Type: #{type}\r\n"\
97
+ "Content-Length: #{body.bytesize}\r\n"\
98
+ "#{keep_alive ? FULL_KEEP_ALIVE : nil}\r\n")
99
+ send_data(body)
100
+ end
101
+ consider_keep_alive(try_keep_alive)
102
+ end
103
+
104
+ # send fully formatted HTTP response
105
+ def send_raw_string(string, try_keep_alive = true)
106
+ send_data(string)
107
+ try_keep_alive &&= @keep_alive &&
108
+ (cl = string.index(CONTENT_LENGTH_DT)) &&
109
+ (kai = string.index(FULL_KEEP_ALIVE)) &&
110
+ kai < (rn = string.index(RNRN)) &&
111
+ cl < rn
112
+ consider_keep_alive(try_keep_alive)
113
+ end
114
+
115
+ # send Rack like response (status, headers, body)
116
+ #
117
+ # This method tries to implement as much of Rack as it progmatically needed, but not more.
118
+ # Test it before use
119
+ def send_rack_response(status, headers, body, try_keep_alive = true)
120
+ status = status.to_i
121
+ out = "HTTP/1.1 #{status} #{HTTP_STATUS_CODES[status]}\r\n"
122
+
123
+ if String === headers
124
+ try_keep_alive &&= headers.index(CONTENT_LENGTH_DT) && headers.index(FULL_KEEP_ALIVE)
125
+ out << headers
126
+ else
127
+ content_length = connection = nil
128
+ headers.each do |k, v|
129
+ if k == CONTENT_LENGTH
130
+ content_length = v
131
+ elsif k == CONNECTION
132
+ connection = v
133
+ end
134
+ if String === v
135
+ unless v.index(NL)
136
+ out << "#{k}: #{v}\r\n"
137
+ else
138
+ v.each_line{|l| out << "#{k}: #{l}\r\n"}
139
+ end
140
+ elsif v.respond_to?(:each)
141
+ v.each{|l| out << "#{k}: #{l}\r\n"}
142
+ else
143
+ unless (v=v.to_s).index(NL)
144
+ out << "#{k}: #{v}\r\n"
145
+ else
146
+ v.each_line{|l| out << "#{k}: #{l}\r\n"}
147
+ end
148
+ end
149
+ end
150
+
151
+ try_keep_alive &&= @keep_alive && content_length && (!connection || connection == KEEP_ALIVE)
152
+ out << FULL_KEEP_ALIVE if try_keep_alive && !connection
153
+ end
154
+ out << RN
155
+ send_data out
156
+
157
+ if String === body
158
+ send_data(body)
159
+ else
160
+ body.each{|s| send_data(s)}
161
+ end
162
+
163
+ consider_keep_alive(try_keep_alive)
164
+ end
165
+
166
+ # call this when you fully send response
167
+ def consider_keep_alive(try_keep_alive = true)
168
+ if @keep_alive && try_keep_alive
169
+ post_init
170
+ else
171
+ close_connection_after_writing
172
+ end
173
+ end
174
+
175
+ def unbind # :nodoc:
176
+ @unbind_callback && @unbind_callback.call(self)
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/thin/fun_embed/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Sokolov Yura 'funny-falcon'"]
6
+ gem.email = ["funny.falcon@gmail.com"]
7
+ gem.description = %q{trim of Thin web server for embedding into eventmachined application}
8
+ gem.summary = "Subclass of EM::Connection which uses thin internals to do http request handling.\n"\
9
+ "It is intentionally not Rack server, but could be used to build some."
10
+
11
+ gem.homepage = "https://github.com/funny-falcon/thin-fun_embed"
12
+
13
+ gem.files = Dir["examples/*"] + Dir["lib/**/*.rb"] +
14
+ %w{Gemfile LICENSE Rakefile README.rdoc thin-fun_embed.gemspec}
15
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
+ gem.name = "thin-fun_embed"
17
+ gem.require_paths = ["lib"]
18
+ gem.version = Thin::FunEmbed::VERSION
19
+
20
+ gem.add_dependency 'thin', ['>= 1.4']
21
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: thin-fun_embed
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sokolov Yura 'funny-falcon'
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thin
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '1.4'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '1.4'
30
+ description: trim of Thin web server for embedding into eventmachined application
31
+ email:
32
+ - funny.falcon@gmail.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - examples/simple_200_ok.rb
38
+ - examples/rack_like.rb
39
+ - examples/status_body.rb
40
+ - examples/config.ru
41
+ - lib/thin/fun_embed/constants.rb
42
+ - lib/thin/fun_embed/version.rb
43
+ - lib/thin/fun_embed.rb
44
+ - Gemfile
45
+ - LICENSE
46
+ - Rakefile
47
+ - README.rdoc
48
+ - thin-fun_embed.gemspec
49
+ homepage: https://github.com/funny-falcon/thin-fun_embed
50
+ licenses: []
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 1.8.24
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: Subclass of EM::Connection which uses thin internals to do http request handling.
73
+ It is intentionally not Rack server, but could be used to build some.
74
+ test_files: []