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(env = request.to_rack_env), 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(env = request.to_rack_env), env
end

#respond(request, status, headers, body, env) ⇒ 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
85
86
87
88
89
# File 'lib/itsi/server/rack_interface.rb', line 30

def respond(request, (status, headers, body), env)
  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")

  response.reserve_headers(headers.size)

  for key, value in headers
    case value
    when String then response[key] = value
    when Array
      value.each do |v|
        response[key] = v
      end
    end
  end

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


  if body_streamer
    # If we're partially hijacked or returned a streaming body,
    # stream this response.
    body_streamer.call(response)

  elsif body.respond_to?(:each) || body.respond_to?(:to_ary)
    # If we're enumerable with more than one chunk
    # also stream, otherwise write in a single chunk
    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
rescue EOFError
  response.close
ensure
  RackEnvPool.checkin(env)
  body.close if body.respond_to?(:close)
end

#streaming_body?(body) ⇒ Boolean

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



92
93
94
# File 'lib/itsi/server/rack_interface.rb', line 92

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