Module: Itsi::Server::RackInterface

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

Defined Under Namespace

Classes: PartialHijackStream

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.for(app) ⇒ Object

Builds a handler proc that is compatible with Rack applications.



44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/itsi/server/rack_interface.rb', line 44

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.



61
62
63
# File 'lib/itsi/server/rack_interface.rb', line 61

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.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/itsi/server/rack_interface.rb', line 69

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
  hijack_callback = headers.delete("rack.hijack")
  body_streamer = streaming_body?(body) ? body : hijack_callback

  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 hijack_callback
    stream = status == 101 ? request.partial_hijack : PartialHijackStream.new(response)
    body_streamer.call(stream)
  elsif body_streamer
    # If we're partially hijacked or returned a streaming body,
    # stream this response.
    body_streamer.call(response)

  elsif body.is_a?(Array)
    if body.length == 1
      response.send_and_close(body[0].to_s)
    else
      buffer = nil
      body.each do |part|
        response << buffer.to_s if buffer
        buffer = part
      end

      response.send_and_close(buffer.to_s)
    end
  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.

Returns:

  • (Boolean)


147
148
149
# File 'lib/itsi/server/rack_interface.rb', line 147

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