Class: ControlTower::RackSocket

Inherits:
Object
  • Object
show all
Defined in:
lib/control_tower/rack_socket.rb

Constant Summary collapse

VERSION =
[1,0].freeze

Instance Method Summary collapse

Constructor Details

#initialize(host, port, server, concurrent) ⇒ RackSocket

Returns a new instance of RackSocket.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/control_tower/rack_socket.rb', line 14

def initialize(host, port, server, concurrent)
  @app = server.app
  @socket = TCPServer.new(host, port)
  @socket.listen(50)
  @status = :closed # Start closed and give the server time to start

  if concurrent
    @multithread = true
    @request_queue = Dispatch::Queue.concurrent
    puts "Caution! Wake turbulance from heavy aircraft landing on parallel runway.\n(Parallel Request Action ENABLED!)"
  else
    @multithread = false
    @request_queue = Dispatch::Queue.new('com.apple.ControlTower.rack_socket_queue')
  end
  @request_group = Dispatch::Group.new
end

Instance Method Details

#closeObject



121
122
123
124
125
126
127
128
129
130
131
# File 'lib/control_tower/rack_socket.rb', line 121

def close
  @status = :close

  # You get 30 seconds to empty the request queue and get outa here!
  Dispatch::Source.timer(30, 0, 1, Dispatch::Queue.concurrent) do
    $stderr.puts "Timed out waiting for connections to close"
    exit 1
  end
  @request_group.wait
  @socket.close
end

#openObject



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
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
# File 'lib/control_tower/rack_socket.rb', line 31

def open
  @status = :open
  while (@status == :open)
    connection = @socket.accept

    @request_queue.async(@request_group) do
      env = { 'rack.errors' => $stderr,
              'rack.multiprocess' => false,
              'rack.multithread' => @multithread,
              'rack.run_once' => false,
              'rack.version' => VERSION }
      resp = nil
      x_sendfile_header = 'X-Sendfile'
      x_sendfile = nil
      begin
        request_data = parse!(connection, env)
        if request_data
          request_data['REMOTE_ADDR'] = connection.addr[3]
          status, headers, body = @app.call(request_data)

          # If there's an X-Sendfile header, we'll use sendfile(2)
          if headers.has_key?(x_sendfile_header)
            x_sendfile = headers[x_sendfile_header]
            x_sendfile = ::File.open(x_sendfile, 'r') unless x_sendfile.kind_of? IO
            x_sendfile_size = x_sendfile.stat.size
            headers.delete(x_sendfile_header)
            headers['Content-Length'] = x_sendfile_size
          end

          # Unless somebody's already set it for us (or we don't need it), set the Content-Length
          unless (status == -1 ||
                  (status >= 100 and status <= 199) ||
                  status == 204 ||
                  status == 304 ||
                  headers.has_key?('Content-Length'))
            headers['Content-Length'] = if body.respond_to?(:each)
                                          size = 0
                                          body.each { |x| size += x.bytesize }
                                          size
                                        else
                                          body.bytesize
                                        end
          end

          # TODO -- We don't handle keep-alive connections yet
          headers['Connection'] = 'close'

          resp = "HTTP/1.1 #{status}\r\n"
          headers.each do |header, value|
            resp << "#{header}: #{value}\r\n"
          end
          resp << "\r\n"

          # Start writing the response
          connection.write resp

          # Write the body
          if x_sendfile
            connection.sendfile(x_sendfile, 0, x_sendfile_size)
          elsif body.respond_to?(:each)
            body.each do |chunk|
              connection.write chunk
            end
          else
            connection.write body
          end

        else
          $stderr.puts "Error: No request data received!"
        end
      rescue EOFError, Errno::ECONNRESET, Errno::EPIPE, Errno::EINVAL
        $stderr.puts "Error: Connection terminated!"
      rescue Object => e
        if resp.nil? && !connection.closed?
          connection.write "HTTP/1.1 400\r\n\r\n"
        else
          # We have a response, but there was trouble sending it:
          $stderr.puts "Error: Problem transmitting data -- #{e.inspect}"
          $stderr.puts e.backtrace.join("\n")
        end
      ensure
        # We should clean up after our tempfile, if we used one.
        input = env['rack.input']
        input.unlink if input.class == Tempfile
        connection.close rescue nil
      end
    end
  end
end