Module: Itsi::Server::RackInterface

Included in:
Itsi::Server
Defined in:
lib/itsi/server/rack_interface.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.for(app) ⇒ Object

Builds a handler proc that is compatible with Rack applications.



5
6
7
8
9
10
11
12
13
14
15
16
17
# File 'lib/itsi/server/rack_interface.rb', line 5

def self.for(app)
  require "rack"
  if app.is_a?(String)
    dir = File.expand_path(File.dirname(app))
    Dir.chdir(dir) do
      loaded_app = ::Rack::Builder.parse_file(File.basename(app))
      app = loaded_app.is_a?(Array) ? loaded_app.first : loaded_app
    end
  end
  lambda do |request|
    Server.respond(request, app.call(request.to_rack_env))
  end
end

Instance Method Details

#call(app, request) ⇒ Object

Interface to Rack applications. Here we build the env, and invoke the Rack app’s call method. We then turn the Rack response into something Itsi server understands.



22
23
24
# File 'lib/itsi/server/rack_interface.rb', line 22

def call(app, request)
  respond request, app.call(request.to_rack_env)
end

#respond(request, status, headers, body) ⇒ Object

Itsi responses are asynchronous and can be streamed. Response chunks are sent using response.send_frame and the response is finished using response.close_write. If only a single chunk is written, you can use the #send_and_close method.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/itsi/server/rack_interface.rb', line 30

def respond(request, (status, headers, body))
  response = request.response

  # Don't try and respond if we've been hijacked.
  # The hijacker is now responsible for this.
  return if request.hijacked

  # 1. Set Status
  response.status = status

  # 2. Set Headers
  body_streamer = streaming_body?(body) ? body : headers.delete("rack.hijack")
  headers.each do |key, value|
    if value.is_a?(Array)
      value.each do |v|
        response[key] = v
      end
    elsif value.is_a?(String)
      response[key] = value
    end
  end

  # 3. Set Body
  # As soon as we start setting the response
  # the server will begin to stream it to the client.

  # If we're partially hijacked or returned a streaming body,
  # stream this response.

  if body_streamer
    body_streamer.call(response)

  # If we're enumerable with more than one chunk
  # also stream, otherwise write in a single chunk
  elsif body.respond_to?(:each) || body.respond_to?(:to_ary)
    unless body.respond_to?(:each)
      body = body.to_ary
      raise "Body #to_ary didn't return an array" unless body.is_a?(Array)
    end
    # We offset this iteration intentionally,
    # to optimize for the case where there's only one chunk.
    buffer = nil
    body.each do |part|
      response << buffer.to_s if buffer
      buffer = part
    end

    response.send_and_close(buffer.to_s)
  else
    response.send_and_close(body.to_s)
  end
ensure
  response.close_write
  body.close if body.respond_to?(:close)
end

#streaming_body?(body) ⇒ Boolean

A streaming body is one that responds to #call and not #each.

Returns:

  • (Boolean)


87
88
89
# File 'lib/itsi/server/rack_interface.rb', line 87

def streaming_body?(body)
  body.respond_to?(:call) && !body.respond_to?(:each)
end