macournoyer / thin
- Source
- Commits
- Network (36)
- Issues (5)
- Downloads (17)
- Wiki (1)
- Graphs
-
Tree:
2470ab0
commit 2470ab0206716b445c23a1c81eb5f01dff06ad3a
tree cd7d8ce9a4b975955a3b37f435890d06e4a9c822
parent cb6e21d6519c8da088f94864dec56c9122f2b753
tree cd7d8ce9a4b975955a3b37f435890d06e4a9c822
parent cb6e21d6519c8da088f94864dec56c9122f2b753
| f1e62946 » | macournoyer | 2007-12-20 | 1 | require 'socket' | |
| 2 | |||||
| f641f3f4 » | macournoyer | 2007-12-05 | 3 | module Thin | |
| 5a0965b3 » | macournoyer | 2008-02-04 | 4 | # Connection between the server and client. | |
| 8d3f1db0 » | macournoyer | 2008-02-10 | 5 | # This class is instanciated by EventMachine on each new connection | |
| 6 | # that is opened. | ||||
| f641f3f4 » | macournoyer | 2007-12-05 | 7 | class Connection < EventMachine::Connection | |
| affccc93 » | Dan Kubb | 2008-07-18 | 8 | CONTENT_LENGTH = 'Content-Length'.freeze | |
| 9 | TRANSFER_ENCODING = 'Transfer-Encoding'.freeze | ||||
| 10 | CHUNKED_REGEXP = /\bchunked\b/i.freeze | ||||
| 11 | |||||
| f641f3f4 » | macournoyer | 2007-12-05 | 12 | include Logging | |
| 13 | |||||
| 0fd4de53 » | macournoyer | 2008-03-06 | 14 | # Rack application (adapter) served by this connection. | |
| ef8bf4cb » | macournoyer | 2007-12-14 | 15 | attr_accessor :app | |
| f641f3f4 » | macournoyer | 2007-12-05 | 16 | ||
| b686394e » | macournoyer | 2008-02-28 | 17 | # Backend to the server | |
| 18 | attr_accessor :backend | ||||
| b6aa2a6e » | macournoyer | 2008-01-29 | 19 | ||
| 434ca28b » | macournoyer | 2008-02-19 | 20 | # Current request served by the connection | |
| 21 | attr_accessor :request | ||||
| 22 | |||||
| 0fd4de53 » | macournoyer | 2008-03-06 | 23 | # Next response sent through the connection | |
| 434ca28b » | macournoyer | 2008-02-19 | 24 | attr_accessor :response | |
| 7c1472b6 » | macournoyer | 2008-02-12 | 25 | ||
| 4d3709fa » | macournoyer | 2008-03-28 | 26 | # Calling the application in a threaded allowing | |
| 27 | # concurrent processing of requests. | ||||
| b02c717f » | macournoyer | 2008-04-08 | 28 | attr_writer :threaded | |
| 4d3709fa » | macournoyer | 2008-03-28 | 29 | ||
| 8d3f1db0 » | macournoyer | 2008-02-10 | 30 | # Get the connection ready to process a request. | |
| f641f3f4 » | macournoyer | 2007-12-05 | 31 | def post_init | |
| e10c7d54 » | macournoyer | 2007-12-17 | 32 | @request = Request.new | |
| f641f3f4 » | macournoyer | 2007-12-05 | 33 | @response = Response.new | |
| 34 | end | ||||
| 35 | |||||
| 8d3f1db0 » | macournoyer | 2008-02-10 | 36 | # Called when data is received from the client. | |
| f641f3f4 » | macournoyer | 2007-12-05 | 37 | def receive_data(data) | |
| edaaa848 » | macournoyer | 2008-01-04 | 38 | trace { data } | |
| b698fe8f » | macournoyer | 2008-01-20 | 39 | process if @request.parse(data) | |
| 4fde8628 » | macournoyer | 2007-12-10 | 40 | rescue InvalidRequest => e | |
| 0304be1f » | macournoyer | 2008-02-17 | 41 | log "!! Invalid request" | |
| 4fde8628 » | macournoyer | 2007-12-10 | 42 | log_error e | |
| 43 | close_connection | ||||
| 44 | end | ||||
| 45 | |||||
| 8d3f1db0 » | macournoyer | 2008-02-10 | 46 | # Called when all data was received and the request | |
| 0fd4de53 » | macournoyer | 2008-03-06 | 47 | # is ready to be processed. | |
| 4fde8628 » | macournoyer | 2007-12-10 | 48 | def process | |
| b02c717f » | macournoyer | 2008-04-08 | 49 | if threaded? | |
| 06439cf6 » | macournoyer | 2008-04-18 | 50 | @request.threaded = true | |
| 4d3709fa » | macournoyer | 2008-03-28 | 51 | EventMachine.defer(method(:pre_process), method(:post_process)) | |
| 52 | else | ||||
| 06439cf6 » | macournoyer | 2008-04-18 | 53 | @request.threaded = false | |
| 4d3709fa » | macournoyer | 2008-03-28 | 54 | post_process(pre_process) | |
| 55 | end | ||||
| 56 | end | ||||
| 57 | |||||
| 58 | def pre_process | ||||
| f641f3f4 » | macournoyer | 2007-12-05 | 59 | # Add client info to the request env | |
| 534a6479 » | macournoyer | 2008-01-24 | 60 | @request.remote_address = remote_address | |
| b698fe8f » | macournoyer | 2008-01-20 | 61 | ||
| 0fd4de53 » | macournoyer | 2008-03-06 | 62 | # Process the request calling the Rack adapter | |
| 4d3709fa » | macournoyer | 2008-03-28 | 63 | @app.call(@request.env) | |
| b3537eef » | michaelklishin | 2008-07-21 | 64 | rescue Exception | |
| 4d3709fa » | macournoyer | 2008-03-28 | 65 | handle_error | |
| 66 | terminate_request | ||||
| 67 | nil # Signal to post_process that the request could not be processed | ||||
| 68 | end | ||||
| 69 | |||||
| 70 | def post_process(result) | ||||
| 71 | return unless result | ||||
| 72 | |||||
| affccc93 » | Dan Kubb | 2008-07-18 | 73 | # Set the Content-Length header if possible | |
| 74 | set_content_length(result) if need_content_length?(result) | ||||
| 75 | |||||
| 4d3709fa » | macournoyer | 2008-03-28 | 76 | @response.status, @response.headers, @response.body = result | |
| 278a99ae » | michaelklishin | 2008-08-02 | 77 | ||
| 78 | log "!! Rack application returned nil body. Probably you wanted it to be an empty string?" if @response.body.nil? | ||||
| 8d3f1db0 » | macournoyer | 2008-02-10 | 79 | # Make the response persistent if requested by the client | |
| e442be1f » | macournoyer | 2008-01-24 | 80 | @response.persistent! if @request.persistent? | |
| 86f030f6 » | macournoyer | 2008-01-24 | 81 | ||
| f37bcad3 » | macournoyer | 2007-12-14 | 82 | # Send the response | |
| 782a71e8 » | macournoyer | 2008-01-18 | 83 | @response.each do |chunk| | |
| 84 | trace { chunk } | ||||
| 85 | send_data chunk | ||||
| 86 | end | ||||
| f641f3f4 » | macournoyer | 2007-12-05 | 87 | ||
| 86f030f6 » | macournoyer | 2008-01-24 | 88 | # If no more request on that same connection, we close it. | |
| 503d6cda » | macournoyer | 2008-02-07 | 89 | close_connection_after_writing unless persistent? | |
| f641f3f4 » | macournoyer | 2007-12-05 | 90 | ||
| a1257542 » | michaelklishin | 2008-07-21 | 91 | rescue Exception | |
| 4d3709fa » | macournoyer | 2008-03-28 | 92 | handle_error | |
| 93 | ensure | ||||
| 94 | terminate_request | ||||
| 95 | end | ||||
| 37153518 » | michaelklishin | 2008-08-02 | 96 | ||
| 97 | # Logs catched exception and closes the connection. | ||||
| 4d3709fa » | macournoyer | 2008-03-28 | 98 | def handle_error | |
| 0304be1f » | macournoyer | 2008-02-17 | 99 | log "!! Unexpected error while processing request: #{$!.message}" | |
| dd6b82a8 » | macournoyer | 2008-02-16 | 100 | log_error | |
| f641f3f4 » | macournoyer | 2007-12-05 | 101 | close_connection rescue nil | |
| 4d3709fa » | macournoyer | 2008-03-28 | 102 | end | |
| cb6e21d6 » | michaelklishin | 2008-08-02 | 103 | ||
| 2470ab02 » | michaelklishin | 2008-08-02 | 104 | # Does request and response cleanup (closes open IO streams and | |
| 105 | # deletes created temporary files). | ||||
| cb6e21d6 » | michaelklishin | 2008-08-02 | 106 | # Re-initializes response and request if client supports persistent | |
| 107 | # connection. | ||||
| 4d3709fa » | macournoyer | 2008-03-28 | 108 | def terminate_request | |
| 86f030f6 » | macournoyer | 2008-01-24 | 109 | @request.close rescue nil | |
| ef8bf4cb » | macournoyer | 2007-12-14 | 110 | @response.close rescue nil | |
| 86f030f6 » | macournoyer | 2008-01-24 | 111 | ||
| e442be1f » | macournoyer | 2008-01-24 | 112 | # Prepare the connection for another request if the client | |
| 113 | # supports HTTP pipelining (persistent connection). | ||||
| 503d6cda » | macournoyer | 2008-02-07 | 114 | post_init if persistent? | |
| f641f3f4 » | macournoyer | 2007-12-05 | 115 | end | |
| a68f46d6 » | macournoyer | 2008-01-23 | 116 | ||
| 8d3f1db0 » | macournoyer | 2008-02-10 | 117 | # Called when the connection is unbinded from the socket | |
| 118 | # and can no longer be used to process requests. | ||||
| b6aa2a6e » | macournoyer | 2008-01-29 | 119 | def unbind | |
| b686394e » | macournoyer | 2008-02-28 | 120 | @backend.connection_finished(self) | |
| b6aa2a6e » | macournoyer | 2008-01-29 | 121 | end | |
| 122 | |||||
| 2b7d72da » | macournoyer | 2008-02-20 | 123 | # Allows this connection to be persistent. | |
| 124 | def can_persist! | ||||
| 125 | @can_persist = true | ||||
| 126 | end | ||||
| 127 | |||||
| 128 | # Return +true+ if this connection is allowed to stay open and be persistent. | ||||
| 129 | def can_persist? | ||||
| 130 | @can_persist | ||||
| 131 | end | ||||
| 132 | |||||
| 8d3f1db0 » | macournoyer | 2008-02-10 | 133 | # Return +true+ if the connection must be left open | |
| 134 | # and ready to be reused for another request. | ||||
| 503d6cda » | macournoyer | 2008-02-07 | 135 | def persistent? | |
| 2b7d72da » | macournoyer | 2008-02-20 | 136 | @can_persist && @response.persistent? | |
| b02c717f » | macournoyer | 2008-04-08 | 137 | end | |
| 138 | |||||
| 139 | # +true+ if <tt>app.call</tt> will be called inside a thread. | ||||
| 140 | # You can set all requests as threaded setting <tt>Connection#threaded=true</tt> | ||||
| 141 | # or on a per-request case returning +true+ in <tt>app.deferred?</tt>. | ||||
| 142 | def threaded? | ||||
| 143 | @threaded || (@app.respond_to?(:deferred?) && @app.deferred?(@request.env)) | ||||
| 144 | end | ||||
| b6aa2a6e » | macournoyer | 2008-01-29 | 145 | ||
| dd6b82a8 » | macournoyer | 2008-02-16 | 146 | # IP Address of the remote client. | |
| 7c1472b6 » | macournoyer | 2008-02-12 | 147 | def remote_address | |
| dd6b82a8 » | macournoyer | 2008-02-16 | 148 | @request.forwarded_for || socket_address | |
| 044d58ed » | michaelklishin | 2008-07-21 | 149 | rescue Exception | |
| dd6b82a8 » | macournoyer | 2008-02-16 | 150 | log_error | |
| 7c1472b6 » | macournoyer | 2008-02-12 | 151 | nil | |
| 152 | end | ||||
| 4d24f67d » | macournoyer | 2008-02-13 | 153 | ||
| 154 | protected | ||||
| 553202ab » | michaelklishin | 2008-07-21 | 155 | ||
| 156 | # Returns IP address of peer as a string. | ||||
| 4d24f67d » | macournoyer | 2008-02-13 | 157 | def socket_address | |
| 158 | Socket.unpack_sockaddr_in(get_peername)[1] | ||||
| 159 | end | ||||
| affccc93 » | Dan Kubb | 2008-07-18 | 160 | ||
| 161 | private | ||||
| 162 | def need_content_length?(result) | ||||
| 163 | status, headers, body = result | ||||
| 164 | return false if headers.has_key?(CONTENT_LENGTH) | ||||
| 165 | return false if (100..199).include?(status) || status == 204 || status == 304 | ||||
| 166 | return false if headers.has_key?(TRANSFER_ENCODING) && headers[TRANSFER_ENCODING] =~ CHUNKED_REGEXP | ||||
| 167 | return false unless body.kind_of?(String) || body.kind_of?(Array) | ||||
| 168 | true | ||||
| 169 | end | ||||
| 170 | |||||
| 171 | def set_content_length(result) | ||||
| 172 | headers, body = result[1..2] | ||||
| 173 | case body | ||||
| 174 | when String | ||||
| c9634028 » | macournoyer | 2008-07-19 | 175 | # See http://redmine.ruby-lang.org/issues/show/203 | |
| affccc93 » | Dan Kubb | 2008-07-18 | 176 | headers[CONTENT_LENGTH] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s | |
| 177 | when Array | ||||
| 178 | bytes = 0 | ||||
| 179 | body.each do |p| | ||||
| 180 | bytes += p.respond_to?(:bytesize) ? p.bytesize : p.size | ||||
| 181 | end | ||||
| 182 | headers[CONTENT_LENGTH] = bytes.to_s | ||||
| 183 | end | ||||
| 184 | end | ||||
| f641f3f4 » | macournoyer | 2007-12-05 | 185 | end | |
| a1257542 » | michaelklishin | 2008-07-21 | 186 | end | |

