thin-fun_embed 0.0.1

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/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: []