Class: EventMachine::HttpResponse

Inherits:
Object
  • Object
show all
Defined in:
lib/evma_httpserver/response.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeHttpResponse

Returns a new instance of HttpResponse.



40
41
42
# File 'lib/evma_httpserver/response.rb', line 40

def initialize
	@headers = {}
end

Instance Attribute Details

#chunksObject

Returns the value of attribute chunks.



38
39
40
# File 'lib/evma_httpserver/response.rb', line 38

def chunks
  @chunks
end

#contentObject

Returns the value of attribute content.



38
39
40
# File 'lib/evma_httpserver/response.rb', line 38

def content
  @content
end

#headersObject

Returns the value of attribute headers.



38
39
40
# File 'lib/evma_httpserver/response.rb', line 38

def headers
  @headers
end

#multipartsObject

Returns the value of attribute multiparts.



38
39
40
# File 'lib/evma_httpserver/response.rb', line 38

def multiparts
  @multiparts
end

#statusObject

Returns the value of attribute status.



38
39
40
# File 'lib/evma_httpserver/response.rb', line 38

def status
  @status
end

Class Method Details

.concoct_multipart_boundaryObject



262
263
264
265
266
267
268
269
270
271
# File 'lib/evma_httpserver/response.rb', line 262

def self.concoct_multipart_boundary
	@multipart_index ||= 0
	@multipart_index += 1
	if @multipart_index >= 1000
		@multipart_index = 0
		@multipart_guid = nil
	end
	@multipart_guid ||= `uuidgen -r`.chomp.gsub(/\-/,"")
	"#{@multipart_guid}#{@multipart_index}"
end

Instance Method Details

Sugaring for Set-cookie headers. These are a pain because there can easily and legitimately be more than one. So we use an ugly verb to signify that. #add_set_cookies does NOT disturb the set-cookie headers which may have been added on a prior call. #set_cookie clears them out first.



61
62
63
64
65
66
# File 'lib/evma_httpserver/response.rb', line 61

def add_set_cookie *ck
	if ck.length > 0
		h = (@headers ["Set-cookie"] ||= [])
		ck.each {|c| h << c}
	end
end

#chunk(text) ⇒ Object

add a chunk to go to the output. Will cause the headers to pick up “content-transfer-encoding” Add the chunk to a list. Calling #send_chunks will send out the available chunks and clear the chunk list WITHOUT closing the connection, so it can be called any number of times. TODO!!! Per RFC2616, we may not send chunks to an HTTP/1.0 client. Raise an exception here if our user tries to do so. Chunked transfer coding is defined in RFC2616 pgh 3.6.1. The argument can be a string or a hash. The latter allows for sending chunks with extensions (someday).



202
203
204
205
# File 'lib/evma_httpserver/response.rb', line 202

def chunk text
	@chunks ||= []
	@chunks << text
end

#content_type(*mime) ⇒ Object

sugarings for headers



49
50
51
52
53
54
55
# File 'lib/evma_httpserver/response.rb', line 49

def content_type *mime
	if mime.length > 0
		@headers ["Content-type"] = mime.first.to_s
	else
		@headers ["Content-type"]
	end
end

#fixup_headersObject

Examine the content type and data and other things, and perform a final fixup of the header array. We expect this to be called just before sending headers to the remote peer. In the case of multiparts, we ASSUME we will get called before any content gets sent out, because the multipart boundary is created here.



129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/evma_httpserver/response.rb', line 129

def fixup_headers
	if @content
		@headers ["Content-length"] = @content.to_s.length
	elsif @chunks
		@headers ["Transfer-encoding"] = "chunked"
		# Might be nice to ENSURE there is no content-length header,
		# but how to detect all the possible permutations of upper/lower case?
	elsif @multiparts
		@multipart_boundary = self.class.concoct_multipart_boundary
		@headers ["Content-type"] = "multipart/x-mixed-replace; boundary=\"#{@multipart_boundary}\""
	else
		@headers ["Content-length"] = 0
	end
end

#keep_connection_open(arg = true) ⇒ Object



44
45
46
# File 'lib/evma_httpserver/response.rb', line 44

def keep_connection_open arg=true
	@keep_connection_open = arg
end

#multipart(arg) ⇒ Object

To add a multipart to the outgoing response, specify the headers and the body. If only a string is given, it’s treated as the body (in this case, the header is assumed to be empty).



233
234
235
236
237
238
239
240
241
242
# File 'lib/evma_httpserver/response.rb', line 233

def multipart arg
	vals = if arg.is_a?(String)
		{:body => arg, :headers => {}}
	else
		arg
	end

	@multiparts ||= []
	@multiparts << vals
end

#send_bodyObject

we send either content, chunks, or multiparts. Content can only be sent once. Chunks and multiparts can be sent any number of times. DO NOT close the connection or send any goodbye kisses. This method can be called multiple times to send out chunks or multiparts.



148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/evma_httpserver/response.rb', line 148

def send_body
	if @content
		send_content
	elsif @chunks
		send_chunks
	elsif @multiparts
		send_multiparts
	else
		@content = ""
		send_content
	end
end

#send_chunksObject

send the contents of the chunk list and clear it out. ASSUMES that headers have been sent. Does NOT close the connection. Can be called multiple times. According to RFC2616, phg 3.6.1, the last chunk will be zero length. But some caller could accidentally set a zero-length chunk in the middle of the stream. If that should happen, raise an exception. The reason for supporting chunks that are hashes instead of just strings is to enable someday supporting chunk-extension codes (cf the RFC). TODO!!! We’re not supporting the final entity-header that may be transmitted after the last (zero-length) chunk.



219
220
221
222
223
224
225
226
227
# File 'lib/evma_httpserver/response.rb', line 219

def send_chunks
	send_headers unless @sent_headers
	while chunk = @chunks.shift
		raise "last chunk already sent" if @last_chunk_sent
		text = chunk.is_a?(Hash) ? chunk[:text] : chunk.to_s
		send_data "#{format("%x", text.length).upcase}\r\n#{text}\r\n"
		@last_chunk_sent = true if text.length == 0
	end
end

#send_contentObject



185
186
187
188
189
# File 'lib/evma_httpserver/response.rb', line 185

def send_content
	raise "sent content already" if @sent_content
	@sent_content = true
	send_data((@content || "").to_s)
end

#send_headersObject

Send the headers out in alpha-sorted order. This will degrade performance to some degree, and is intended only to simplify the construction of unit tests.



93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/evma_httpserver/response.rb', line 93

def send_headers
	raise "sent headers already" if @sent_headers
	@sent_headers = true

	fixup_headers

	ary = []
	ary << "HTTP/1.1 #{@status || 200} ...\r\n"
	ary += generate_header_lines(@headers)
	ary << "\r\n"

	send_data ary.join
end

#send_multipartsObject

Multipart syntax is defined in RFC 2046, pgh 5.1.1 et seq. The CRLF which introduces the boundary line of each part (content entity) is defined as being part of the boundary, not of the preceding part. So we don’t need to mess with interpreting the last bytes of a part to ensure they are CRLF-terminated.



250
251
252
253
254
255
256
257
258
# File 'lib/evma_httpserver/response.rb', line 250

def send_multiparts
	send_headers unless @sent_headers
	while part = @multiparts.shift
		send_data "\r\n--#{@multipart_boundary}\r\n"
		send_data( generate_header_lines( part[:headers] || {} ).join)
		send_data "\r\n"
		send_data part[:body].to_s
	end
end

#send_redirect(location) ⇒ Object



273
274
275
276
277
# File 'lib/evma_httpserver/response.rb', line 273

def send_redirect location
	@status = 302 # TODO, make 301 available by parameter
	@headers["Location"] = location
	send_response
end

#send_responseObject

This is intended to send a complete HTTP response, including closing the connection if appropriate at the end of the transmission. Don’t use this method to send partial or iterated responses. This method will send chunks and multiparts provided they are all available when we get here. Note that the default @status is 200 if the value doesn’t exist.



83
84
85
86
87
88
# File 'lib/evma_httpserver/response.rb', line 83

def send_response
	send_headers
	send_body
	send_trailer
	close_connection_after_writing unless (@keep_connection_open and (@status || 200) == 200)
end

#send_trailerObject

send a trailer which depends on the type of body we’re dealing with. The assumption is that we’re about to end the transmission of this particular HTTP response. (A connection-close may or may not follow.)



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/evma_httpserver/response.rb', line 165

def send_trailer
	send_headers unless @sent_headers
	if @content
		# no-op
	elsif @chunks
		unless @last_chunk_sent
			chunk ""
			send_chunks
		end
	elsif @multiparts
		# in the lingo of RFC 2046/5.1.1, we're sending an "epilog"
		# consisting of a blank line. I really don't know how that is
		# supposed to interact with the case where we leave the connection
		# open after transmitting the multipart response.
		send_data "\r\n--#{@multipart_boundary}--\r\n\r\n"
	else
		# no-op
	end
end


67
68
69
70
71
72
73
74
75
# File 'lib/evma_httpserver/response.rb', line 67

def set_cookie *ck
	h = (@headers ["Set-cookie"] ||= [])
	if ck.length > 0
		h.clear
		add_set_cookie *ck
	else
		h
	end
end