-
Notifications
You must be signed in to change notification settings - Fork 0
/
thin_connection2.rb
176 lines (150 loc) · 4.94 KB
/
thin_connection2.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
Thin::Connection.new(:foo)
Thin.send(:remove_const, :Connection)
require 'socket'
module Thin
class Connection < EventMachine::Connection
include Logging
# Rack application (adapter) served by this connection.
attr_accessor :app
# Backend to the server
attr_accessor :backend
# Current request served by the connection
attr_accessor :request
# Next response sent through the connection
attr_accessor :response
# Calling the application in a threaded allowing
# concurrent processing of requests.
attr_writer :threaded
# Get the connection ready to process a request.
def post_init
@request = Request.new
@response = Response.new
end
# Called when data is received from the client.
def receive_data(data)
trace { data }
process if @request.parse(data)
rescue InvalidRequest => e
log "!! Invalid request"
log_error e
close_connection
end
# Called when all data was received and the request
# is ready to be processed.
def process
# Add client info to the request env
@request.remote_address = remote_address
@request.env["rack.header"] = method(:send_header)
@request.env["rack.body"] = method(:send_body)
@request.env["rack.finish"] = method(:finish)
@app.call(@request.env)
rescue Exception
handle_error
terminate_request
nil
end
def send_header(status, headers)
@header_sent = true
@response.status = status
@response.headers = headers
send_data @response.head
end
def send_body(chunk)
raise "Header not sent" unless @header_sent
trace { chunk }
send_data chunk
end
def finish
terminate_request
end
# Logs catched exception and closes the connection.
def handle_error
log "!! Unexpected error while processing request: #{$!.message}"
log "!! #{$!.backtrace.join("\n")}"
log_error
close_connection rescue nil
end
def close_request_response
@request.async_close.succeed if @request.async_close
@request.close rescue nil
@response.close rescue nil
end
# Does request and response cleanup (closes open IO streams and
# deletes created temporary files).
# Re-initializes response and request if client supports persistent
# connection.
def terminate_request
unless persistent?
close_connection_after_writing rescue nil
close_request_response
else
close_request_response
# Prepare the connection for another request if the client
# supports HTTP pipelining (persistent connection).
post_init
end
end
# Called when the connection is unbinded from the socket
# and can no longer be used to process requests.
def unbind
@request.async_close.succeed if @request.async_close
@response.body.fail if @response.body.respond_to?(:fail)
@backend.connection_finished(self)
end
# Allows this connection to be persistent.
def can_persist!
@can_persist = true
end
# Return +true+ if this connection is allowed to stay open and be persistent.
def can_persist?
@can_persist
end
# Return +true+ if the connection must be left open
# and ready to be reused for another request.
def persistent?
@can_persist && @response.persistent?
end
# +true+ if <tt>app.call</tt> will be called inside a thread.
# You can set all requests as threaded setting <tt>Connection#threaded=true</tt>
# or on a per-request case returning +true+ in <tt>app.deferred?</tt>.
def threaded?
@threaded || (@app.respond_to?(:deferred?) && @app.deferred?(@request.env))
end
# IP Address of the remote client.
def remote_address
socket_address
rescue Exception
log_error
nil
end
protected
# Returns IP address of peer as a string.
def socket_address
Socket.unpack_sockaddr_in(get_peername)[1]
end
private
def need_content_length?(result)
status, headers, body = result
return false if status == -1
return false if headers.has_key?(CONTENT_LENGTH)
return false if (100..199).include?(status) || status == 204 || status == 304
return false if headers.has_key?(TRANSFER_ENCODING) && headers[TRANSFER_ENCODING] =~ CHUNKED_REGEXP
return false unless body.kind_of?(String) || body.kind_of?(Array)
true
end
def set_content_length(result)
headers, body = result[1..2]
case body
when String
# See http://redmine.ruby-lang.org/issues/show/203
headers[CONTENT_LENGTH] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
when Array
bytes = 0
body.each do |p|
bytes += p.respond_to?(:bytesize) ? p.bytesize : p.size
end
headers[CONTENT_LENGTH] = bytes.to_s
end
end
end
end