Use rb_thread_select instead of rb_thread_schedule
[ebb.git] / ruby_lib / ebb.rb
blob36578fc3bbbdbb64855ff5ea547842fb043b48fa
1 # Ruby Binding to the Ebb Web Server
2 # Copyright (c) 2008 Ry Dahl. This software is released under the MIT License.
3 # See README file for details.
4 require 'stringio'
5 module Ebb
6   LIBDIR = File.dirname(__FILE__)
7   require Ebb::LIBDIR + '/../src/ebb_ext'
8   autoload :Runner, LIBDIR + '/ebb/runner'
9   
10   def self.start_server(app, options={})
11     port = (options[:port] || 4001).to_i
12     if options.has_key?(:threaded_processing)
13       threaded_processing = options[:threaded_processing] ? true : false
14     else
15       threaded_processing = true
16     end
17     
18     Client::BASE_ENV['rack.multithread'] = threaded_processing
19     
20     FFI::server_listen_on_port(port)
21     @running = true
22     #trap('INT')  { stop_server }
23     
24     puts "Ebb listening at http://0.0.0.0:#{port}/ (#{threaded_processing ? 'threaded' : 'sequential'} processing, PID #{Process.pid})"
25     
26     while @running
27       FFI::server_process_connections()
28       while client = FFI::waiting_clients.shift
29         if threaded_processing
30           Thread.new(client) { |c| process(app, c) }
31         else
32           process(app, client)
33         end
34       end
35     end
36     FFI::server_unlisten()
37   end
38   
39   def self.running?
40     FFI::server_open?
41   end
42   
43   def self.stop_server()
44     @running = false
45   end
46   
47   def self.process(app, client)
48     begin
49       status, headers, body = app.call(client.env)
50     rescue
51       raise if $DEBUG
52       status = 500
53       headers = {'Content-Type' => 'text/plain'}
54       body = "Internal Server Error\n"
55     end
56     
57     client.write_status(status)
58     
59     if headers.respond_to?(:[]=) and body.respond_to?(:length) and status != 304
60       headers['Connection'] = 'close'
61       headers['Content-Length'] = body.length.to_s
62     end
63     
64     headers.each { |field, value| client.write_header(field, value) }
65     client.write("\r\n")
66     
67     if body.kind_of?(String)
68       client.write(body)
69       client.body_written()
70       client.begin_transmission()
71     else
72       client.begin_transmission()
73       client.body.each { |p| write(p) }
74       client.body_written()
75     end
76   rescue => e
77     puts "Ebb Error! #{e.class}  #{e.message}"
78     puts e.backtrace.join("\n")
79   ensure
80     client.release
81   end
82   
83   # This array is created and manipulated in the C extension.
84   def FFI.waiting_clients
85     @waiting_clients
86   end
87   
88   class Client
89     BASE_ENV = {
90       'SERVER_NAME' => '0.0.0.0',
91       'SCRIPT_NAME' => '',
92       'SERVER_SOFTWARE' => "Ebb #{Ebb::VERSION}",
93       'SERVER_PROTOCOL' => 'HTTP/1.1',
94       'rack.version' => [0, 1],
95       'rack.errors' => STDERR,
96       'rack.url_scheme' => 'http',
97       'rack.multiprocess' => false,
98       'rack.run_once' => false
99     }
100     
101     def env
102       env = FFI::client_env(self).update(BASE_ENV)
103       env['rack.input'] = RequestBody.new(self)
104       env
105     end
106     
107     def write_status(status)
108       s = status.to_i
109       FFI::client_write_status(self, s, HTTP_STATUS_CODES[s])
110     end
111     
112     def write(data)
113       FFI::client_write(self, data)
114     end
115     
116     def write_header(field, value)
117       value.send(value.is_a?(String) ? :each_line : :each) do |v| 
118         FFI::client_write_header(self, field, v.chomp)
119       end
120     end
121     
122     def body_written
123       FFI::client_set_body_written(self, true)
124     end
125     
126     def begin_transmission
127       FFI::client_begin_transmission(self)
128     end
129     
130     def release
131       FFI::client_release(self)
132     end
133   end
134   
135   class RequestBody
136     def initialize(client)
137       @client = client
138     end
139     
140     def read(len = nil)
141       if @io
142         @io.read(len)
143       else
144         if len.nil?
145           s = ''
146           while(chunk = read(10*1024)) do
147             s << chunk
148           end
149           s
150         else
151           FFI::client_read_input(@client, len)
152         end
153       end
154     end
155     
156     def gets
157       io.gets
158     end
159     
160     def each(&block)
161       io.each(&block)
162     end
163     
164     def io
165       @io ||= StringIO.new(read)
166     end
167   end
168   
169   
170   HTTP_STATUS_CODES = {  
171     100  => 'Continue', 
172     101  => 'Switching Protocols', 
173     200  => 'OK', 
174     201  => 'Created', 
175     202  => 'Accepted', 
176     203  => 'Non-Authoritative Information', 
177     204  => 'No Content', 
178     205  => 'Reset Content', 
179     206  => 'Partial Content', 
180     300  => 'Multiple Choices', 
181     301  => 'Moved Permanently', 
182     302  => 'Moved Temporarily', 
183     303  => 'See Other', 
184     304  => 'Not Modified', 
185     305  => 'Use Proxy', 
186     400  => 'Bad Request', 
187     401  => 'Unauthorized', 
188     402  => 'Payment Required', 
189     403  => 'Forbidden', 
190     404  => 'Not Found', 
191     405  => 'Method Not Allowed', 
192     406  => 'Not Acceptable', 
193     407  => 'Proxy Authentication Required', 
194     408  => 'Request Time-out', 
195     409  => 'Conflict', 
196     410  => 'Gone', 
197     411  => 'Length Required', 
198     412  => 'Precondition Failed', 
199     413  => 'Request Entity Too Large', 
200     414  => 'Request-URI Too Large', 
201     415  => 'Unsupported Media Type', 
202     500  => 'Internal Server Error', 
203     501  => 'Not Implemented', 
204     502  => 'Bad Gateway', 
205     503  => 'Service Unavailable', 
206     504  => 'Gateway Time-out', 
207     505  => 'HTTP Version not supported'
208   }.freeze