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