Class: Jaws::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/jaws/server.rb

Constant Summary collapse

DefaultOptions =
{
  :Host => '0.0.0.0',
  :Port => 8080,
  :MaxClients => 20, 
  :SystemCores => nil,
  :ReadTimeout => 2,
}
DefaultRackEnv =

The default values for most of the rack environment variables

{
  "rack.version" => [1,1],
  "rack.url_scheme" => "http",
  "rack.input" => StringIO.new,
  "rack.errors" => $stderr,
  "rack.multithread" => true,
  "rack.multiprocess" => false,
  "rack.run_once" => false,
  "SCRIPT_NAME" => "",
  "PATH_INFO" => "",
  "QUERY_STRING" => "",
  "SERVER_SOFTWARE" => "Rack+Jaws",
}
StatusStrings =
Rack::Utils::HTTP_STATUS_CODES
CodesWithoutBody =
Rack::Utils::STATUS_WITH_NO_ENTITY_BODY

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = DefaultOptions) ⇒ Server

Initializes a new Jaws server object. Pass it a hash of options (:Host, :Port, :MaxClients, and :SystemCores valid)



66
67
68
69
70
71
72
# File 'lib/jaws/server.rb', line 66

def initialize(options = DefaultOptions)
  @options = DefaultOptions.merge(options)
  DefaultOptions.each do |k,v|
    send(:"#{Jaws.decapse_name(k.to_s)}=", @options[k])
  end
  self.extend Mutex_m
end

Instance Attribute Details

#hostObject

The host to listen on when run(app) is called. Also set with options



43
44
45
# File 'lib/jaws/server.rb', line 43

def host
  @host
end

#max_clientsObject

The maximum number of requests this server should handle concurrently. Also set with options Note that you should set this legitimately to the number of clients you can actually handle and not some arbitrary high number like with Mongrel. This server will simply not accept more connections than it can handle, which allows you to run other server instances on other machines to take up the slack. A really really good rule of thumb for a database driven site is to have it be less than the number of database connections your (hopefuly properly tuned) database server can handle. If you run more than one web server machine, the TOTAL max_clients from all those servers should be less than what the database can handle.



54
55
56
# File 'lib/jaws/server.rb', line 54

def max_clients
  @max_clients
end

#portObject

The port to listen on when run(app) is called. Also set with options



45
46
47
# File 'lib/jaws/server.rb', line 45

def port
  @port
end

#read_timeout=(value) ⇒ Object

The amount of time, in seconds, the server will wait without input before disconnecting the client. Also set with options



63
64
65
# File 'lib/jaws/server.rb', line 63

def read_timeout=(value)
  @read_timeout = value
end

#system_coresObject

The number of cores the system has. This may eventually be used to determine if the process should fork if it’s running on a ruby implementation that doesn’t support multiprocessing. If set to nil, it’ll auto-detect, and failing that just assume it shouldn’t fork at all. If you want it to never fork, you should set it to 1 (1 core means 1 process). Also set with options



60
61
62
# File 'lib/jaws/server.rb', line 60

def system_cores
  @system_cores
end

Instance Method Details

#chunked_read(io, timeout) ⇒ Object

Reads from a connection, yielding chunks of data as it goes, until the connection closes. Once the connection closes, it returns.



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/jaws/server.rb', line 116

def chunked_read(io, timeout)
  begin
    loop do
      list = IO.select([io], [], [], @read_timeout)
      if (list.nil? || list.empty?)
        # IO.select tells us we timed out by giving us nil,
        # disconnect the non-talkative client.
        return
      end
      data = io.recv(4096)
      if (data == "")
        # If recv returns an empty string, that means the other
        # end closed the connection (either in response to our
        # end closing the write pipe or because they just felt
        # like it) so we close the connection from our end too.
        return
      end
      yield data
    end
  ensure
    io.close if (!io.closed?)
  end
end

#make_interruptableObject

Sets the current thread as interruptable. This happens around the listen part of the thread. This means the thread is receptive to t.raise.



312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/jaws/server.rb', line 312

def make_interruptable
  begin
    @interruptable.synchronize do
      @interruptable << Thread.current
    end
    yield
  ensure
    @interruptable.synchronize do
      @interruptable.delete(Thread.current)
    end
  end
end

#run(app) ⇒ Object

Runs the application through the configured handler. Can only be run once at a time. If you try to run it more than once, the second run will block until the first finishes.



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/jaws/server.rb', line 328

def run(app)
  synchronize do
    @interruptable = []
    int_orig = trap "INT" do
      stop()
    end
    term_orig = trap "TERM" do
      stop()
    end
    begin
      @listener = create_listener(@options)
      @interruptable.extend Mutex_m
      if (@max_clients > 1)
        @master = Thread.current
        @workers = (0...@max_clients).collect do
          Thread.new do
            begin
              process_client(app)
            rescue GracefulExit, SystemExit => e
              # let it exit.
            rescue => e
              $stderr.puts("Handler thread unexpectedly died with #{e}:", e.backtrace)
            end
          end
        end
        @workers.each do |worker|
          worker.join
        end
      else
        begin
          @master = Thread.current
          @workers = [Thread.current]
          process_client(app)
        rescue GracefulExit, SystemExit => e
          # let it exit
        rescue => e
          $stderr.puts("Handler thread unexpectedly died with #{e}:", e.backtrace)
        end
      end      
    ensure
      trap "INT", int_orig
      trap "TERM", term_orig
      @listener.close if (@listener && !@listener.closed?)
      @interruptable = @listener = @master = @workers = nil
    end
  end
end

#running?Boolean

Returns:

  • (Boolean)


391
392
393
# File 'lib/jaws/server.rb', line 391

def running?
  !@workers.nil?
end

#stopObject



376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/jaws/server.rb', line 376

def stop()
  # close the connection, the handler threads will exit
  # the next time they try to load.
  # TODO: Make it force them to exit after a timeout.
  $stderr.puts("Terminating request threads. To force immediate exit, send sigkill.")
  @interruptable.synchronize do
    @listener.close if !@listener.closed?
    @workers.each do |worker|
      if (@interruptable.include?(worker))
        worker.raise GracefulExit, "Exiting"
      end
    end
  end
end

#stopped?Boolean

Returns:

  • (Boolean)


394
395
396
# File 'lib/jaws/server.rb', line 394

def stopped?
  @workers.nil?
end