michaelyta-thin 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. data/CHANGELOG +257 -0
  2. data/COPYING +18 -0
  3. data/README +69 -0
  4. data/Rakefile +13 -0
  5. data/benchmark/abc +51 -0
  6. data/benchmark/benchmarker.rb +80 -0
  7. data/benchmark/runner +79 -0
  8. data/bin/thin +6 -0
  9. data/example/adapter.rb +32 -0
  10. data/example/async_app.ru +126 -0
  11. data/example/async_chat.ru +247 -0
  12. data/example/async_tailer.ru +100 -0
  13. data/example/config.ru +23 -0
  14. data/example/monit_sockets +20 -0
  15. data/example/monit_unixsock +20 -0
  16. data/example/myapp.rb +1 -0
  17. data/example/ramaze.ru +12 -0
  18. data/example/thin.god +80 -0
  19. data/example/thin_solaris_smf.erb +36 -0
  20. data/example/thin_solaris_smf.readme.txt +150 -0
  21. data/example/vlad.rake +64 -0
  22. data/ext/thin_parser/common.rl +55 -0
  23. data/ext/thin_parser/ext_help.h +14 -0
  24. data/ext/thin_parser/extconf.rb +6 -0
  25. data/ext/thin_parser/parser.c +452 -0
  26. data/ext/thin_parser/parser.h +49 -0
  27. data/ext/thin_parser/parser.rl +157 -0
  28. data/ext/thin_parser/thin.c +433 -0
  29. data/lib/rack/adapter/loader.rb +79 -0
  30. data/lib/rack/adapter/rails.rb +175 -0
  31. data/lib/thin.rb +49 -0
  32. data/lib/thin/backends/base.rb +141 -0
  33. data/lib/thin/backends/swiftiply_client.rb +56 -0
  34. data/lib/thin/backends/tcp_server.rb +68 -0
  35. data/lib/thin/backends/unix_server.rb +51 -0
  36. data/lib/thin/command.rb +53 -0
  37. data/lib/thin/connection.rb +214 -0
  38. data/lib/thin/controllers/cluster.rb +127 -0
  39. data/lib/thin/controllers/controller.rb +183 -0
  40. data/lib/thin/controllers/service.rb +75 -0
  41. data/lib/thin/controllers/service.sh.erb +39 -0
  42. data/lib/thin/daemonizing.rb +174 -0
  43. data/lib/thin/headers.rb +39 -0
  44. data/lib/thin/logging.rb +54 -0
  45. data/lib/thin/request.rb +158 -0
  46. data/lib/thin/response.rb +101 -0
  47. data/lib/thin/runner.rb +209 -0
  48. data/lib/thin/server.rb +247 -0
  49. data/lib/thin/stats.html.erb +216 -0
  50. data/lib/thin/stats.rb +52 -0
  51. data/lib/thin/statuses.rb +43 -0
  52. data/lib/thin/version.rb +32 -0
  53. data/spec/backends/swiftiply_client_spec.rb +66 -0
  54. data/spec/backends/tcp_server_spec.rb +33 -0
  55. data/spec/backends/unix_server_spec.rb +37 -0
  56. data/spec/command_spec.rb +25 -0
  57. data/spec/configs/cluster.yml +9 -0
  58. data/spec/configs/single.yml +9 -0
  59. data/spec/connection_spec.rb +105 -0
  60. data/spec/controllers/cluster_spec.rb +235 -0
  61. data/spec/controllers/controller_spec.rb +129 -0
  62. data/spec/controllers/service_spec.rb +50 -0
  63. data/spec/daemonizing_spec.rb +192 -0
  64. data/spec/headers_spec.rb +40 -0
  65. data/spec/logging_spec.rb +46 -0
  66. data/spec/perf/request_perf_spec.rb +50 -0
  67. data/spec/perf/response_perf_spec.rb +19 -0
  68. data/spec/perf/server_perf_spec.rb +39 -0
  69. data/spec/rack/loader_spec.rb +29 -0
  70. data/spec/rack/rails_adapter_spec.rb +106 -0
  71. data/spec/rails_app/app/controllers/application.rb +10 -0
  72. data/spec/rails_app/app/controllers/simple_controller.rb +19 -0
  73. data/spec/rails_app/app/helpers/application_helper.rb +3 -0
  74. data/spec/rails_app/app/views/simple/index.html.erb +15 -0
  75. data/spec/rails_app/config/boot.rb +109 -0
  76. data/spec/rails_app/config/environment.rb +64 -0
  77. data/spec/rails_app/config/environments/development.rb +18 -0
  78. data/spec/rails_app/config/environments/production.rb +19 -0
  79. data/spec/rails_app/config/environments/test.rb +22 -0
  80. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  81. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  82. data/spec/rails_app/config/routes.rb +35 -0
  83. data/spec/rails_app/public/404.html +30 -0
  84. data/spec/rails_app/public/422.html +30 -0
  85. data/spec/rails_app/public/500.html +30 -0
  86. data/spec/rails_app/public/dispatch.cgi +10 -0
  87. data/spec/rails_app/public/dispatch.fcgi +24 -0
  88. data/spec/rails_app/public/dispatch.rb +10 -0
  89. data/spec/rails_app/public/favicon.ico +0 -0
  90. data/spec/rails_app/public/images/rails.png +0 -0
  91. data/spec/rails_app/public/index.html +277 -0
  92. data/spec/rails_app/public/javascripts/application.js +2 -0
  93. data/spec/rails_app/public/javascripts/controls.js +963 -0
  94. data/spec/rails_app/public/javascripts/dragdrop.js +972 -0
  95. data/spec/rails_app/public/javascripts/effects.js +1120 -0
  96. data/spec/rails_app/public/javascripts/prototype.js +4225 -0
  97. data/spec/rails_app/public/robots.txt +5 -0
  98. data/spec/rails_app/script/about +3 -0
  99. data/spec/rails_app/script/console +3 -0
  100. data/spec/rails_app/script/destroy +3 -0
  101. data/spec/rails_app/script/generate +3 -0
  102. data/spec/rails_app/script/performance/benchmarker +3 -0
  103. data/spec/rails_app/script/performance/profiler +3 -0
  104. data/spec/rails_app/script/performance/request +3 -0
  105. data/spec/rails_app/script/plugin +3 -0
  106. data/spec/rails_app/script/process/inspector +3 -0
  107. data/spec/rails_app/script/process/reaper +3 -0
  108. data/spec/rails_app/script/process/spawner +3 -0
  109. data/spec/rails_app/script/runner +3 -0
  110. data/spec/rails_app/script/server +3 -0
  111. data/spec/request/mongrel_spec.rb +39 -0
  112. data/spec/request/parser_spec.rb +215 -0
  113. data/spec/request/persistent_spec.rb +35 -0
  114. data/spec/request/processing_spec.rb +45 -0
  115. data/spec/response_spec.rb +91 -0
  116. data/spec/runner_spec.rb +168 -0
  117. data/spec/server/builder_spec.rb +44 -0
  118. data/spec/server/pipelining_spec.rb +110 -0
  119. data/spec/server/robustness_spec.rb +34 -0
  120. data/spec/server/stopping_spec.rb +55 -0
  121. data/spec/server/swiftiply.yml +6 -0
  122. data/spec/server/swiftiply_spec.rb +32 -0
  123. data/spec/server/tcp_spec.rb +57 -0
  124. data/spec/server/threaded_spec.rb +27 -0
  125. data/spec/server/unix_socket_spec.rb +26 -0
  126. data/spec/server_spec.rb +96 -0
  127. data/spec/spec_helper.rb +219 -0
  128. data/tasks/announce.rake +22 -0
  129. data/tasks/deploy.rake +16 -0
  130. data/tasks/email.erb +30 -0
  131. data/tasks/ext.rake +42 -0
  132. data/tasks/gem.rake +108 -0
  133. data/tasks/rdoc.rake +25 -0
  134. data/tasks/site.rake +15 -0
  135. data/tasks/spec.rake +48 -0
  136. data/tasks/stats.rake +28 -0
  137. metadata +254 -0
@@ -0,0 +1,79 @@
1
+ module Rack
2
+ class AdapterNotFound < RuntimeError; end
3
+
4
+ # Mapping used to guess which adapter to use in <tt>Adapter.for</tt>.
5
+ # Framework <name> => <file unique to this framework> in order they will
6
+ # be tested.
7
+ # +nil+ for value to never guess.
8
+ # NOTE: If a framework has a file that is not unique, make sure to place
9
+ # it at the end.
10
+ ADAPTERS = [
11
+ [:rails, 'config/environment.rb'],
12
+ [:ramaze, 'start.rb'],
13
+ [:halcyon, 'runner.ru'],
14
+ [:merb, 'config/init.rb'],
15
+ [:mack, 'config/app_config/default.yml'],
16
+ [:mack, 'config/configatron/default.rb'],
17
+ [:file, nil]
18
+ ]
19
+
20
+ module Adapter
21
+ # Guess which adapter to use based on the directory structure
22
+ # or file content.
23
+ # Returns a symbol representing the name of the adapter to use
24
+ # to load the application under <tt>dir/</tt>.
25
+ def self.guess(dir)
26
+ ADAPTERS.each do |adapter, file|
27
+ return adapter if file && ::File.exist?(::File.join(dir, file))
28
+ end
29
+ raise AdapterNotFound, "No adapter found for #{dir}"
30
+ end
31
+
32
+ # Loads an adapter identified by +name+ using +options+ hash.
33
+ def self.for(name, options={})
34
+ case name.to_sym
35
+ when :rails
36
+ return Rails.new(options.merge(:root => options[:chdir]))
37
+
38
+ when :ramaze
39
+ require "#{options[:chdir]}/start"
40
+
41
+ Ramaze.trait[:essentials].delete Ramaze::Adapter
42
+ Ramaze.start :force => true
43
+
44
+ return Ramaze::Adapter::Base
45
+
46
+ when :merb
47
+ require 'merb-core'
48
+
49
+ Merb::Config.setup(:merb_root => options[:chdir],
50
+ :environment => options[:environment])
51
+ Merb.environment = Merb::Config[:environment]
52
+ Merb.root = Merb::Config[:merb_root]
53
+ Merb::BootLoader.run
54
+
55
+ return Merb::Rack::Application.new
56
+
57
+ when :halcyon
58
+ require 'halcyon'
59
+
60
+ $:.unshift(Halcyon.root/'lib')
61
+
62
+ return Halcyon::Runner.new
63
+
64
+ when :mack
65
+ ENV["MACK_ENV"] = options[:environment]
66
+ load(::File.join(options[:chdir], "Rakefile"))
67
+ require 'mack'
68
+ return Mack::Utils::Server.build_app
69
+
70
+ when :file
71
+ return Rack::File.new(options[:chdir])
72
+
73
+ else
74
+ raise AdapterNotFound, "Adapter not found: #{name}"
75
+
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,175 @@
1
+ require 'cgi'
2
+
3
+ # Adapter to run a Rails app with any supported Rack handler.
4
+ # By default it will try to load the Rails application in the
5
+ # current directory in the development environment.
6
+ #
7
+ # Options:
8
+ # root: Root directory of the Rails app
9
+ # environment: Rails environment to run in (development [default], production or test)
10
+ # prefix: Set the relative URL root.
11
+ #
12
+ # Based on http://fuzed.rubyforge.org/ Rails adapter
13
+ module Rack
14
+ module Adapter
15
+ class Rails
16
+ FILE_METHODS = %w(GET HEAD).freeze
17
+
18
+ def initialize(options={})
19
+ @root = options[:root] || Dir.pwd
20
+ @env = options[:environment] || 'development'
21
+ @prefix = options[:prefix]
22
+
23
+ load_application
24
+
25
+ @rails_app = if ActionController::Dispatcher.instance_methods.include?(:call)
26
+ ActionController::Dispatcher.new
27
+ else
28
+ CgiApp.new
29
+ end
30
+
31
+ @file_app = Rack::File.new(::File.join(RAILS_ROOT, "public"))
32
+ end
33
+
34
+ def load_application
35
+ ENV['RAILS_ENV'] = @env
36
+
37
+ require "#{@root}/config/environment"
38
+ require 'dispatcher'
39
+
40
+ if @prefix
41
+ if ActionController::Base.respond_to?(:relative_url_root=)
42
+ ActionController::Base.relative_url_root = @prefix # Rails 2.1.1
43
+ else
44
+ ActionController::AbstractRequest.relative_url_root = @prefix
45
+ end
46
+ end
47
+ end
48
+
49
+ def file_exist?(path)
50
+ full_path = ::File.join(@file_app.root, Utils.unescape(path))
51
+ ::File.file?(full_path) && ::File.readable_real?(full_path)
52
+ end
53
+
54
+ def call(env)
55
+ path = env['PATH_INFO'].chomp('/')
56
+ method = env['REQUEST_METHOD']
57
+ cached_path = (path.empty? ? 'index' : path) + ActionController::Base.page_cache_extension
58
+
59
+ if FILE_METHODS.include?(method)
60
+ if file_exist?(path) # Serve the file if it's there
61
+ return @file_app.call(env)
62
+ elsif file_exist?(cached_path) # Serve the page cache if it's there
63
+ env['PATH_INFO'] = cached_path
64
+ return @file_app.call(env)
65
+ end
66
+ end
67
+
68
+ # No static file, let Rails handle it
69
+ @rails_app.call(env)
70
+ end
71
+
72
+ protected
73
+ # For Rails pre Rack (2.3)
74
+ class CgiApp
75
+ def call(env)
76
+ request = Request.new(env)
77
+ response = Response.new
78
+ session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
79
+ cgi = CGIWrapper.new(request, response)
80
+
81
+ Dispatcher.dispatch(cgi, session_options, response)
82
+
83
+ response.finish
84
+ end
85
+ end
86
+
87
+ class CGIWrapper < ::CGI
88
+ def initialize(request, response, *args)
89
+ @request = request
90
+ @response = response
91
+ @args = *args
92
+ @input = request.body
93
+
94
+ super *args
95
+ end
96
+
97
+ def header(options = "text/html")
98
+ if options.is_a?(String)
99
+ @response['Content-Type'] = options unless @response['Content-Type']
100
+ else
101
+ @response['Content-Length'] = options.delete('Content-Length').to_s if options['Content-Length']
102
+
103
+ @response['Content-Type'] = options.delete('type') || "text/html"
104
+ @response['Content-Type'] += "; charset=" + options.delete('charset') if options['charset']
105
+
106
+ @response['Content-Language'] = options.delete('language') if options['language']
107
+ @response['Expires'] = options.delete('expires') if options['expires']
108
+
109
+ @response.status = options.delete('Status') if options['Status']
110
+
111
+ # Convert 'cookie' header to 'Set-Cookie' headers.
112
+ # Because Set-Cookie header can appear more the once in the response body,
113
+ # we store it in a line break seperated string that will be translated to
114
+ # multiple Set-Cookie header by the handler.
115
+ if cookie = options.delete('cookie')
116
+ cookies = []
117
+
118
+ case cookie
119
+ when Array then cookie.each { |c| cookies << c.to_s }
120
+ when Hash then cookie.each { |_, c| cookies << c.to_s }
121
+ else cookies << cookie.to_s
122
+ end
123
+
124
+ @output_cookies.each { |c| cookies << c.to_s } if @output_cookies
125
+
126
+ @response['Set-Cookie'] = [@response['Set-Cookie'], cookies].compact
127
+ # See http://groups.google.com/group/rack-devel/browse_thread/thread/e8759b91a82c5a10/a8dbd4574fe97d69?#a8dbd4574fe97d69
128
+ if Thin.ruby_18?
129
+ @response['Set-Cookie'].flatten!
130
+ else
131
+ @response['Set-Cookie'] = @response['Set-Cookie'].join("\n")
132
+ end
133
+ end
134
+
135
+ options.each { |k,v| @response[k] = v }
136
+ end
137
+
138
+ ""
139
+ end
140
+
141
+ def params
142
+ @params ||= @request.params
143
+ end
144
+
145
+ def cookies
146
+ @request.cookies
147
+ end
148
+
149
+ def query_string
150
+ @request.query_string
151
+ end
152
+
153
+ # Used to wrap the normal args variable used inside CGI.
154
+ def args
155
+ @args
156
+ end
157
+
158
+ # Used to wrap the normal env_table variable used inside CGI.
159
+ def env_table
160
+ @request.env
161
+ end
162
+
163
+ # Used to wrap the normal stdinput variable used inside CGI.
164
+ def stdinput
165
+ @input
166
+ end
167
+
168
+ def stdoutput
169
+ STDERR.puts "stdoutput should not be used."
170
+ @response.body
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
data/lib/thin.rb ADDED
@@ -0,0 +1,49 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'fileutils'
4
+ require 'timeout'
5
+ require 'stringio'
6
+ require 'time'
7
+ require 'forwardable'
8
+
9
+ require 'rubygems'
10
+ require 'openssl'
11
+ require 'eventmachine'
12
+
13
+ require 'thin/version'
14
+ require 'thin/statuses'
15
+
16
+ module Thin
17
+ autoload :Command, 'thin/command'
18
+ autoload :Connection, 'thin/connection'
19
+ autoload :Daemonizable, 'thin/daemonizing'
20
+ autoload :Logging, 'thin/logging'
21
+ autoload :Headers, 'thin/headers'
22
+ autoload :Request, 'thin/request'
23
+ autoload :Response, 'thin/response'
24
+ autoload :Runner, 'thin/runner'
25
+ autoload :Server, 'thin/server'
26
+ autoload :Stats, 'thin/stats'
27
+
28
+ module Backends
29
+ autoload :Base, 'thin/backends/base'
30
+ autoload :SwiftiplyClient, 'thin/backends/swiftiply_client'
31
+ autoload :TcpServer, 'thin/backends/tcp_server'
32
+ autoload :UnixServer, 'thin/backends/unix_server'
33
+ end
34
+
35
+ module Controllers
36
+ autoload :Cluster, 'thin/controllers/cluster'
37
+ autoload :Controller, 'thin/controllers/controller'
38
+ autoload :Service, 'thin/controllers/service'
39
+ end
40
+ end
41
+
42
+ require 'rack'
43
+ require 'rack/adapter/loader'
44
+
45
+ module Rack
46
+ module Adapter
47
+ autoload :Rails, 'rack/adapter/rails'
48
+ end
49
+ end
@@ -0,0 +1,141 @@
1
+ module Thin
2
+ module Backends
3
+ # A Backend connects the server to the client. It handles:
4
+ # * connection/disconnection to the server
5
+ # * initialization of the connections
6
+ # * manitoring of the active connections.
7
+ #
8
+ # == Implementing your own backend
9
+ # You can create your own minimal backend by inheriting this class and
10
+ # defining the +connect+ and +disconnect+ method.
11
+ # If your backend is not based on EventMachine you also need to redefine
12
+ # the +start+, +stop+, <tt>stop!</tt> and +config+ methods.
13
+ class Base
14
+ # Server serving the connections throught the backend
15
+ attr_accessor :server
16
+
17
+ # Maximum time for incoming data to arrive
18
+ attr_accessor :timeout
19
+
20
+ # Maximum number of file or socket descriptors that the server may open.
21
+ attr_accessor :maximum_connections
22
+
23
+ # Maximum number of connections that can be persistent
24
+ attr_accessor :maximum_persistent_connections
25
+
26
+ # Allow using threads in the backend.
27
+ attr_writer :threaded
28
+ def threaded?; @threaded end
29
+
30
+ # Number of persistent connections currently opened
31
+ attr_accessor :persistent_connection_count
32
+
33
+ # Disable the use of epoll under Linux
34
+ attr_accessor :no_epoll
35
+
36
+ def initialize
37
+ @connections = []
38
+ @timeout = Server::DEFAULT_TIMEOUT
39
+ @persistent_connection_count = 0
40
+ @maximum_connections = Server::DEFAULT_MAXIMUM_CONNECTIONS
41
+ @maximum_persistent_connections = Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
42
+ @no_epoll = false
43
+ end
44
+
45
+ # Start the backend and connect it.
46
+ def start
47
+ @stopping = false
48
+ starter = proc do
49
+ connect
50
+ @running = true
51
+ end
52
+
53
+ # Allow for early run up of eventmachine.
54
+ if EventMachine.reactor_running?
55
+ starter.call
56
+ else
57
+ EventMachine.run(&starter)
58
+ end
59
+ end
60
+
61
+ # Stop of the backend from accepting new connections.
62
+ def stop
63
+ @running = false
64
+ @stopping = true
65
+
66
+ # Do not accept anymore connection
67
+ disconnect
68
+ stop! if @connections.empty?
69
+ end
70
+
71
+ # Force stop of the backend NOW, too bad for the current connections.
72
+ def stop!
73
+ @running = false
74
+ @stopping = false
75
+
76
+ EventMachine.stop if EventMachine.reactor_running?
77
+ @connections.each { |connection| connection.close_connection }
78
+ close
79
+ end
80
+
81
+ # Configure the backend. This method will be called before droping superuser privileges,
82
+ # so you can do crazy stuff that require godlike powers here.
83
+ def config
84
+ # See http://rubyeventmachine.com/pub/rdoc/files/EPOLL.html
85
+ EventMachine.epoll unless @no_epoll
86
+
87
+ # Set the maximum number of socket descriptors that the server may open.
88
+ # The process needs to have required privilege to set it higher the 1024 on
89
+ # some systems.
90
+ @maximum_connections = EventMachine.set_descriptor_table_size(@maximum_connections) unless Thin.win?
91
+ end
92
+
93
+ # Free up resources used by the backend.
94
+ def close
95
+ end
96
+
97
+ # Returns +true+ if the backend is connected and running.
98
+ def running?
99
+ @running
100
+ end
101
+
102
+ # Called by a connection when it's unbinded.
103
+ def connection_finished(connection)
104
+ @persistent_connection_count -= 1 if connection.can_persist?
105
+ @connections.delete(connection)
106
+
107
+ # Finalize gracefull stop if there's no more active connection.
108
+ stop! if @stopping && @connections.empty?
109
+ end
110
+
111
+ # Returns +true+ if no active connection.
112
+ def empty?
113
+ @connections.empty?
114
+ end
115
+
116
+ # Number of active connections.
117
+ def size
118
+ @connections.size
119
+ end
120
+
121
+ protected
122
+ # Initialize a new connection to a client.
123
+ def initialize_connection(connection)
124
+ connection.backend = self
125
+ connection.app = @server.app
126
+ connection.comm_inactivity_timeout = @timeout
127
+ connection.threaded = @threaded
128
+
129
+ # We control the number of persistent connections by keeping
130
+ # a count of the total one allowed yet.
131
+ if @persistent_connection_count < @maximum_persistent_connections
132
+ connection.can_persist!
133
+ @persistent_connection_count += 1
134
+ end
135
+
136
+ @connections << connection
137
+ end
138
+
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,56 @@
1
+ module Thin
2
+ module Backends
3
+ # Backend to act as a Swiftiply client (http://swiftiply.swiftcore.org).
4
+ class SwiftiplyClient < Base
5
+ attr_accessor :key
6
+
7
+ attr_accessor :host, :port
8
+
9
+ def initialize(host, port, options={})
10
+ @host = host
11
+ @port = port.to_i
12
+ @key = options[:swiftiply].to_s
13
+ super()
14
+ end
15
+
16
+ # Connect the server
17
+ def connect
18
+ EventMachine.connect(@host, @port, SwiftiplyConnection, &method(:initialize_connection))
19
+ end
20
+
21
+ # Stops the server
22
+ def disconnect
23
+ EventMachine.stop
24
+ end
25
+
26
+ def to_s
27
+ "#{@host}:#{@port} swiftiply"
28
+ end
29
+ end
30
+ end
31
+
32
+ class SwiftiplyConnection < Connection
33
+ def connection_completed
34
+ send_data swiftiply_handshake(@backend.key)
35
+ end
36
+
37
+ def persistent?
38
+ true
39
+ end
40
+
41
+ def unbind
42
+ super
43
+ EventMachine.add_timer(rand(2)) { reconnect(@backend.host, @backend.port) } if @backend.running?
44
+ end
45
+
46
+ protected
47
+ def swiftiply_handshake(key)
48
+ 'swiftclient' << host_ip.collect { |x| sprintf('%02x', x.to_i)}.join << sprintf('%04x', @backend.port) << sprintf('%02x', key.length) << key
49
+ end
50
+
51
+ # For some reason Swiftiply request the current host
52
+ def host_ip
53
+ Socket.gethostbyname(@backend.host)[3].unpack('CCCC') rescue [0,0,0,0]
54
+ end
55
+ end
56
+ end