fishman-eventmachine_httpserver 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/http.h ADDED
@@ -0,0 +1,115 @@
1
+ /*****************************************************************************
2
+
3
+ File: http.h
4
+ Date: 21Apr06
5
+
6
+ Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
7
+ Gmail: garbagecat10
8
+
9
+ This program is free software; you can redistribute it and/or modify
10
+ it under the terms of the GNU General Public License as published by
11
+ the Free Software Foundation; either version 2 of the License, or
12
+ (at your option) any later version.
13
+
14
+ This program is distributed in the hope that it will be useful,
15
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ GNU General Public License for more details.
18
+
19
+ You should have received a copy of the GNU General Public License
20
+ along with this program; if not, write to the Free Software
21
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22
+
23
+ *****************************************************************************/
24
+
25
+
26
+ #ifndef __HttpPersonality__H_
27
+ #define __HttpPersonality__H_
28
+
29
+
30
+
31
+ /**********************
32
+ class HttpConnection_t
33
+ **********************/
34
+
35
+ class HttpConnection_t
36
+ {
37
+ public:
38
+ HttpConnection_t();
39
+ virtual ~HttpConnection_t();
40
+
41
+ void ConsumeData (const char*, int);
42
+
43
+ virtual void SendData (const char*, int);
44
+ virtual void CloseConnection (bool after_writing);
45
+ virtual void ProcessRequest (const char *method,
46
+ const char *cookie,
47
+ const char *ifnonematch,
48
+ const char *content_type,
49
+ const char *query_string,
50
+ const char *path_info,
51
+ const char *request_uri,
52
+ const char *protocol,
53
+ int postlength,
54
+ const char *postdata,
55
+ const char* hdrblock,
56
+ int hdrblksize);
57
+
58
+ virtual void ReceivePostData(const char *data, int len);
59
+ virtual void SetNoEnvironmentStrings() {bSetEnvironmentStrings = false;}
60
+ virtual void SetDontAccumulatePost() {bAccumulatePost = false;}
61
+
62
+ private:
63
+
64
+ enum {
65
+ BaseState,
66
+ PreheaderState,
67
+ HeaderState,
68
+ ReadingContentState,
69
+ DispatchState,
70
+ EndState
71
+ } ProtocolState;
72
+
73
+ enum {
74
+ MaxLeadingBlanks = 12,
75
+ MaxHeaderLineLength = 8 * 1024,
76
+ MaxContentLength = 20 * 1024 * 1024,
77
+ HeaderBlockSize = 16 * 1024
78
+ };
79
+ int nLeadingBlanks;
80
+
81
+ char HeaderLine [MaxHeaderLineLength];
82
+ int HeaderLinePos;
83
+
84
+ char HeaderBlock [HeaderBlockSize];
85
+ int HeaderBlockPos;
86
+
87
+ int ContentLength;
88
+ int ContentPos;
89
+ char *_Content;
90
+
91
+ bool bSetEnvironmentStrings;
92
+ bool bAccumulatePost;
93
+ bool bRequestSeen;
94
+ bool bContentLengthSeen;
95
+
96
+ const char *RequestMethod;
97
+ std::string Cookie;
98
+ std::string IfNoneMatch;
99
+ std::string ContentType;
100
+ std::string PathInfo;
101
+ std::string RequestUri;
102
+ std::string QueryString;
103
+ std::string Protocol;
104
+
105
+ private:
106
+ bool _InterpretHeaderLine (const char*);
107
+ bool _InterpretRequest (const char*);
108
+ bool _DetectVerbAndSetEnvString (const char*, int);
109
+ void _SendError (int);
110
+ };
111
+
112
+ #endif // __HttpPersonality__H_
113
+
114
+
115
+
data/ext/rubyhttp.cpp ADDED
@@ -0,0 +1,279 @@
1
+ /*****************************************************************************
2
+
3
+ File: libmain.cpp
4
+ Date: 06Apr06
5
+
6
+ Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
7
+ Gmail: garbagecat10
8
+
9
+ This program is free software; you can redistribute it and/or modify
10
+ it under the terms of the GNU General Public License as published by
11
+ the Free Software Foundation; either version 2 of the License, or
12
+ (at your option) any later version.
13
+
14
+ This program is distributed in the hope that it will be useful,
15
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ GNU General Public License for more details.
18
+
19
+ You should have received a copy of the GNU General Public License
20
+ along with this program; if not, write to the Free Software
21
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22
+
23
+ *****************************************************************************/
24
+
25
+ #include <iostream>
26
+ #include <string>
27
+ #include <stdexcept>
28
+
29
+ using namespace std;
30
+
31
+ #include <ruby.h>
32
+ #include "http.h"
33
+
34
+
35
+
36
+ /**************************
37
+ class RubyHttpConnection_t
38
+ **************************/
39
+
40
+ class RubyHttpConnection_t: public HttpConnection_t
41
+ {
42
+ public:
43
+ RubyHttpConnection_t (VALUE v): Myself(v) {}
44
+ virtual ~RubyHttpConnection_t() {}
45
+
46
+ virtual void SendData (const char*, int);
47
+ virtual void CloseConnection (bool after_writing);
48
+ virtual void ProcessRequest (const char *request_method,
49
+ const char *cookie,
50
+ const char *ifnonematch,
51
+ const char *contenttype,
52
+ const char *query_string,
53
+ const char *path_info,
54
+ const char *request_uri,
55
+ const char *protocol,
56
+ int postlength,
57
+ const char *postdata,
58
+ const char *hdrblock,
59
+ int hdrblocksize);
60
+ virtual void ReceivePostData (const char *data, int len);
61
+
62
+ private:
63
+ VALUE Myself;
64
+ };
65
+
66
+
67
+ /******************************
68
+ RubyHttpConnection_t::SendData
69
+ ******************************/
70
+
71
+ void RubyHttpConnection_t::SendData (const char *data, int length)
72
+ {
73
+ rb_funcall (Myself, rb_intern ("send_data"), 1, rb_str_new (data, length));
74
+ }
75
+
76
+
77
+ /*************************************
78
+ RubyHttpConnection_t::CloseConnection
79
+ *************************************/
80
+
81
+ void RubyHttpConnection_t::CloseConnection (bool after_writing)
82
+ {
83
+ VALUE v = rb_intern (after_writing ? "close_connection_after_writing" : "close_connection");
84
+ rb_funcall (Myself, v, 0);
85
+ }
86
+
87
+
88
+ /*************************************
89
+ RubyHttpConnection_t::ReceivePostData
90
+ *************************************/
91
+
92
+ void RubyHttpConnection_t::ReceivePostData (const char *data, int len)
93
+ {
94
+ VALUE data_val = Qnil;
95
+
96
+ if ((len > 0) && data) {
97
+ data_val = rb_str_new(data,len);
98
+ rb_funcall (Myself, rb_intern ("receive_post_data"), 1, data_val);
99
+ }
100
+ }
101
+
102
+ /************************************
103
+ RubyHttpConnection_t::ProcessRequest
104
+ ************************************/
105
+
106
+ void RubyHttpConnection_t::ProcessRequest (const char *request_method,
107
+ const char *cookie,
108
+ const char *ifnonematch,
109
+ const char *contenttype,
110
+ const char *query_string,
111
+ const char *path_info,
112
+ const char *request_uri,
113
+ const char *protocol,
114
+ int post_length,
115
+ const char *post_content,
116
+ const char *hdr_block,
117
+ int hdr_block_size)
118
+ {
119
+ VALUE post = Qnil;
120
+ VALUE headers = Qnil;
121
+ VALUE req_method = Qnil;
122
+ VALUE cookie_val = Qnil;
123
+ VALUE ifnonematch_val = Qnil;
124
+ VALUE contenttype_val = Qnil;
125
+ VALUE path_info_val = Qnil;
126
+ VALUE query_string_val = Qnil;
127
+ VALUE request_uri_val = Qnil;
128
+ VALUE protocol_val = Qnil;
129
+
130
+ if ((post_length > 0) && post_content)
131
+ post = rb_str_new (post_content, post_length);
132
+
133
+ if (hdr_block && (hdr_block_size > 0))
134
+ headers = rb_str_new (hdr_block, hdr_block_size);
135
+ else
136
+ headers = rb_str_new ("", 0);
137
+
138
+ if (request_method && *request_method)
139
+ req_method = rb_str_new (request_method, strlen (request_method));
140
+ if (cookie && *cookie)
141
+ cookie_val = rb_str_new (cookie, strlen (cookie));
142
+ if (ifnonematch && *ifnonematch)
143
+ ifnonematch_val = rb_str_new (ifnonematch, strlen (ifnonematch));
144
+ if (contenttype && *contenttype)
145
+ contenttype_val = rb_str_new (contenttype, strlen (contenttype));
146
+ if (path_info && *path_info)
147
+ path_info_val = rb_str_new (path_info, strlen (path_info));
148
+ if (query_string && *query_string)
149
+ query_string_val = rb_str_new (query_string, strlen (query_string));
150
+ if (request_uri && *request_uri)
151
+ request_uri_val = rb_str_new (request_uri, strlen (request_uri));
152
+ if (protocol && *protocol)
153
+ protocol_val = rb_str_new (protocol, strlen (protocol));
154
+
155
+ rb_ivar_set (Myself, rb_intern ("@http_request_method"), req_method);
156
+ rb_ivar_set (Myself, rb_intern ("@http_cookie"), cookie_val);
157
+ rb_ivar_set (Myself, rb_intern ("@http_if_none_match"), ifnonematch_val);
158
+ rb_ivar_set (Myself, rb_intern ("@http_content_type"), contenttype_val);
159
+ rb_ivar_set (Myself, rb_intern ("@http_path_info"), path_info_val);
160
+ rb_ivar_set (Myself, rb_intern ("@http_request_uri"), request_uri_val);
161
+ rb_ivar_set (Myself, rb_intern ("@http_query_string"), query_string_val);
162
+ rb_ivar_set (Myself, rb_intern ("@http_post_content"), post);
163
+ rb_ivar_set (Myself, rb_intern ("@http_headers"), headers);
164
+ rb_ivar_set (Myself, rb_intern ("@http_protocol"), protocol_val);
165
+ rb_funcall (Myself, rb_intern ("process_http_request"), 0);
166
+ }
167
+
168
+
169
+ /*******
170
+ Statics
171
+ *******/
172
+
173
+ VALUE Intern_http_conn;
174
+
175
+ /***********
176
+ t_post_init
177
+ ***********/
178
+
179
+ static VALUE t_post_init (VALUE self)
180
+ {
181
+ RubyHttpConnection_t *hc = new RubyHttpConnection_t (self);
182
+ if (!hc)
183
+ throw std::runtime_error ("no http-connection object");
184
+
185
+ rb_ivar_set (self, Intern_http_conn, LONG2NUM ((long)hc));
186
+ return Qnil;
187
+ }
188
+
189
+
190
+ /**************
191
+ t_receive_data
192
+ **************/
193
+
194
+ static VALUE t_receive_data (VALUE self, VALUE data)
195
+ {
196
+ int length = NUM2INT (rb_funcall (data, rb_intern ("length"), 0));
197
+ RubyHttpConnection_t *hc = (RubyHttpConnection_t*)(NUM2LONG (rb_ivar_get (self, Intern_http_conn)));
198
+ if (hc)
199
+ hc->ConsumeData (StringValuePtr (data), length);
200
+ return Qnil;
201
+ }
202
+
203
+ /*******************
204
+ t_receive_post_data
205
+ *******************/
206
+
207
+ static VALUE t_receive_post_data (VALUE self, VALUE data)
208
+ {
209
+ /** This is a NOOP. It should be overridden. **/
210
+ return Qnil;
211
+ }
212
+
213
+ /********
214
+ t_unbind
215
+ ********/
216
+
217
+ static VALUE t_unbind (VALUE self)
218
+ {
219
+ RubyHttpConnection_t *hc = (RubyHttpConnection_t*)(NUM2LONG (rb_ivar_get (self, Intern_http_conn)));
220
+ if (hc)
221
+ delete hc;
222
+ return Qnil;
223
+ }
224
+
225
+
226
+ /**********************
227
+ t_process_http_request
228
+ **********************/
229
+
230
+ static VALUE t_process_http_request (VALUE self)
231
+ {
232
+ // This is a stub in case the caller doesn't define it.
233
+ rb_funcall (self, rb_intern ("send_data"), 1, rb_str_new2 ("HTTP/1.1 200 ...\r\nContent-type: text/plain\r\nContent-length: 8\r\n\r\nMonorail"));
234
+ return Qnil;
235
+ }
236
+
237
+ /************************
238
+ t_no_environment_strings
239
+ ************************/
240
+
241
+ static VALUE t_no_environment_strings (VALUE self)
242
+ {
243
+ RubyHttpConnection_t *hc = (RubyHttpConnection_t*)(NUM2LONG (rb_ivar_get (self, Intern_http_conn)));
244
+ if (hc)
245
+ hc->SetNoEnvironmentStrings();
246
+ return Qnil;
247
+ }
248
+
249
+ /**********************
250
+ t_dont_accumulate_post
251
+ **********************/
252
+
253
+ static VALUE t_dont_accumulate_post (VALUE self)
254
+ {
255
+ RubyHttpConnection_t *hc = (RubyHttpConnection_t*)(NUM2LONG (rb_ivar_get (self, Intern_http_conn)));
256
+ if (hc)
257
+ hc->SetDontAccumulatePost();
258
+ return Qnil;
259
+ }
260
+
261
+
262
+ /****************************
263
+ Init_eventmachine_httpserver
264
+ ****************************/
265
+
266
+ extern "C" void Init_eventmachine_httpserver()
267
+ {
268
+ Intern_http_conn = rb_intern ("http_conn");
269
+
270
+ VALUE EmModule = rb_define_module ("EventMachine");
271
+ VALUE HttpServer = rb_define_module_under (EmModule, "HttpServer");
272
+ rb_define_method (HttpServer, "post_init", (VALUE(*)(...))t_post_init, 0);
273
+ rb_define_method (HttpServer, "receive_data", (VALUE(*)(...))t_receive_data, 1);
274
+ rb_define_method (HttpServer, "receive_post_data", (VALUE(*)(...))t_receive_post_data, 1);
275
+ rb_define_method (HttpServer, "unbind", (VALUE(*)(...))t_unbind, 0);
276
+ rb_define_method (HttpServer, "process_http_request", (VALUE(*)(...))t_process_http_request, 0);
277
+ rb_define_method (HttpServer, "no_environment_strings", (VALUE(*)(...))t_no_environment_strings, 0);
278
+ rb_define_method (HttpServer, "dont_accumulate_post", (VALUE(*)(...))t_dont_accumulate_post, 0);
279
+ }
@@ -0,0 +1,35 @@
1
+ # EventMachine HTTP Server
2
+ #
3
+ # Author:: blackhedd (gmail address: garbagecat10).
4
+ #
5
+ # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
6
+ #
7
+ # This program is made available under the terms of the GPL version 2.
8
+ #
9
+ #----------------------------------------------------------------------------
10
+ #
11
+ # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
12
+ #
13
+ # Gmail: garbagecat10
14
+ #
15
+ # This program is free software; you can redistribute it and/or modify
16
+ # it under the terms of the GNU General Public License as published by
17
+ # the Free Software Foundation; either version 2 of the License, or
18
+ # (at your option) any later version.
19
+ #
20
+ # This program is distributed in the hope that it will be useful,
21
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
22
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
+ # GNU General Public License for more details.
24
+ #
25
+ # You should have received a copy of the GNU General Public License
26
+ # along with this program; if not, write to the Free Software
27
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
28
+ #
29
+ #---------------------------------------------------------------------------
30
+ #
31
+
32
+
33
+ require 'eventmachine_httpserver'
34
+ require 'evma_httpserver/response'
35
+
@@ -0,0 +1,318 @@
1
+ # EventMachine HTTP Server
2
+ # HTTP Response-support class
3
+ #
4
+ # Author:: blackhedd (gmail address: garbagecat10).
5
+ #
6
+ # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
7
+ #
8
+ # This program is made available under the terms of the GPL version 2.
9
+ #
10
+ #----------------------------------------------------------------------------
11
+ #
12
+ # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
13
+ #
14
+ # Gmail: garbagecat10
15
+ #
16
+ # This program is free software; you can redistribute it and/or modify
17
+ # it under the terms of the GNU General Public License as published by
18
+ # the Free Software Foundation; either version 2 of the License, or
19
+ # (at your option) any later version.
20
+ #
21
+ # This program is distributed in the hope that it will be useful,
22
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
23
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
+ # GNU General Public License for more details.
25
+ #
26
+ # You should have received a copy of the GNU General Public License
27
+ # along with this program; if not, write to the Free Software
28
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
29
+ #
30
+ #---------------------------------------------------------------------------
31
+ #
32
+
33
+ module EventMachine
34
+
35
+ # This class provides a wide variety of features for generating and
36
+ # dispatching HTTP responses. It allows you to conveniently generate
37
+ # headers and content (including chunks and multiparts), and dispatch
38
+ # responses (including deferred or partially-complete responses).
39
+ #
40
+ # Although HttpResponse is coded as a class, it's not complete as it
41
+ # stands. It assumes that it has certain of the behaviors of
42
+ # EventMachine::Connection. You must add these behaviors, either by
43
+ # subclassing HttpResponse, or using the alternate version of this
44
+ # class, DelegatedHttpResponse. See the test cases for current information
45
+ # on which behaviors you have to add.
46
+ #
47
+ # TODO, someday it would be nice to provide a version of this functionality
48
+ # that is coded as a Module, so it can simply be mixed into an instance of
49
+ # EventMachine::Connection.
50
+ #
51
+ class HttpResponse
52
+ attr_accessor :status, :content, :headers, :chunks, :multiparts
53
+
54
+ def initialize
55
+ @headers = {}
56
+ end
57
+
58
+ def keep_connection_open arg=true
59
+ @keep_connection_open = arg
60
+ end
61
+
62
+ def xhr_streaming_enable arg=true
63
+ keep_connection_open arg
64
+ @xhr_streaming_enable = arg
65
+ end
66
+
67
+ # sugarings for headers
68
+ def content_type *mime
69
+ if mime.length > 0
70
+ @headers["Content-type"] = mime.first.to_s
71
+ else
72
+ @headers["Content-type"]
73
+ end
74
+ end
75
+
76
+ # Sugaring for Set-cookie headers. These are a pain because there can easily and
77
+ # legitimately be more than one. So we use an ugly verb to signify that.
78
+ # #add_set_cookies does NOT disturb the set-cookie headers which may have been
79
+ # added on a prior call. #set_cookie clears them out first.
80
+ def add_set_cookie *ck
81
+ if ck.length > 0
82
+ h = (@headers["Set-cookie"] ||= [])
83
+ ck.each {|c| h << c}
84
+ end
85
+ end
86
+ def set_cookie *ck
87
+ h = (@headers["Set-cookie"] ||= [])
88
+ if ck.length > 0
89
+ h.clear
90
+ add_set_cookie *ck
91
+ else
92
+ h
93
+ end
94
+ end
95
+
96
+
97
+ # This is intended to send a complete HTTP response, including closing the connection
98
+ # if appropriate at the end of the transmission. Don't use this method to send partial
99
+ # or iterated responses. This method will send chunks and multiparts provided they
100
+ # are all available when we get here.
101
+ # Note that the default @status is 200 if the value doesn't exist.
102
+ def send_response
103
+ send_headers
104
+ send_body
105
+ send_trailer
106
+ close_connection_after_writing unless (@keep_connection_open and (@status || 200) == 200)
107
+ end
108
+
109
+ # Send the headers out in alpha-sorted order. This will degrade performance to some
110
+ # degree, and is intended only to simplify the construction of unit tests.
111
+ #
112
+ def send_headers
113
+ raise "sent headers already" if @sent_headers
114
+ @sent_headers = true
115
+
116
+ fixup_headers
117
+
118
+ ary = []
119
+ ary << "HTTP/1.1 #{@status || 200} ...\r\n"
120
+ ary += generate_header_lines(@headers)
121
+ ary << "\r\n"
122
+
123
+ send_data ary.join
124
+ end
125
+
126
+
127
+ def generate_header_lines in_hash
128
+ out_ary = []
129
+ in_hash.keys.sort.each {|k|
130
+ v = in_hash[k]
131
+ if v.is_a?(Array)
132
+ v.each {|v1| out_ary << "#{k}: #{v1}\r\n" }
133
+ else
134
+ out_ary << "#{k}: #{v}\r\n"
135
+ end
136
+ }
137
+ out_ary
138
+ end
139
+ private :generate_header_lines
140
+
141
+
142
+ # Examine the content type and data and other things, and perform a final
143
+ # fixup of the header array. We expect this to be called just before sending
144
+ # headers to the remote peer.
145
+ # In the case of multiparts, we ASSUME we will get called before any content
146
+ # gets sent out, because the multipart boundary is created here.
147
+ #
148
+ def fixup_headers
149
+ if @content
150
+ @headers["Content-length"] = @content.to_s.length
151
+ elsif @chunks
152
+ @headers["Transfer-encoding"] = "chunked"
153
+ # Might be nice to ENSURE there is no content-length header,
154
+ # but how to detect all the possible permutations of upper/lower case?
155
+ elsif @multiparts
156
+ @multipart_boundary = self.class.concoct_multipart_boundary
157
+ @headers["Content-type"] = "multipart/x-mixed-replace; boundary=\"#{@multipart_boundary}\""
158
+ elsif !@xhr_streaming_enable
159
+ @headers["Content-length"] = 0
160
+ end
161
+ end
162
+
163
+ # we send either content, chunks, or multiparts. Content can only be sent once.
164
+ # Chunks and multiparts can be sent any number of times.
165
+ # DO NOT close the connection or send any goodbye kisses. This method can
166
+ # be called multiple times to send out chunks or multiparts.
167
+ def send_body
168
+ if @content
169
+ send_content
170
+ elsif @chunks
171
+ send_chunks
172
+ elsif @multiparts
173
+ send_multiparts
174
+ else
175
+ @content = ""
176
+ send_content
177
+ end
178
+ end
179
+
180
+ # send a trailer which depends on the type of body we're dealing with.
181
+ # The assumption is that we're about to end the transmission of this particular
182
+ # HTTP response. (A connection-close may or may not follow.)
183
+ #
184
+ def send_trailer
185
+ send_headers unless @sent_headers
186
+ if @content
187
+ # no-op
188
+ elsif @chunks
189
+ unless @last_chunk_sent
190
+ chunk ""
191
+ send_chunks
192
+ end
193
+ elsif @multiparts
194
+ # in the lingo of RFC 2046/5.1.1, we're sending an "epilog"
195
+ # consisting of a blank line. I really don't know how that is
196
+ # supposed to interact with the case where we leave the connection
197
+ # open after transmitting the multipart response.
198
+ send_data "\r\n--#{@multipart_boundary}--\r\n\r\n"
199
+ else
200
+ # no-op
201
+ end
202
+ end
203
+
204
+ def send_content
205
+ raise "sent content already" if @sent_content
206
+ @sent_content = true
207
+ send_data((@content || "").to_s)
208
+ end
209
+
210
+ # add a chunk to go to the output.
211
+ # Will cause the headers to pick up "content-transfer-encoding"
212
+ # Add the chunk to a list. Calling #send_chunks will send out the
213
+ # available chunks and clear the chunk list WITHOUT closing the connection,
214
+ # so it can be called any number of times.
215
+ # TODO!!! Per RFC2616, we may not send chunks to an HTTP/1.0 client.
216
+ # Raise an exception here if our user tries to do so.
217
+ # Chunked transfer coding is defined in RFC2616 pgh 3.6.1.
218
+ # The argument can be a string or a hash. The latter allows for
219
+ # sending chunks with extensions (someday).
220
+ #
221
+ def chunk text
222
+ @chunks ||= []
223
+ @chunks << text
224
+ end
225
+
226
+ # send the contents of the chunk list and clear it out.
227
+ # ASSUMES that headers have been sent.
228
+ # Does NOT close the connection.
229
+ # Can be called multiple times.
230
+ # According to RFC2616, phg 3.6.1, the last chunk will be zero length.
231
+ # But some caller could accidentally set a zero-length chunk in the middle
232
+ # of the stream. If that should happen, raise an exception.
233
+ # The reason for supporting chunks that are hashes instead of just strings
234
+ # is to enable someday supporting chunk-extension codes (cf the RFC).
235
+ # TODO!!! We're not supporting the final entity-header that may be
236
+ # transmitted after the last (zero-length) chunk.
237
+ #
238
+ def send_chunks
239
+ send_headers unless @sent_headers
240
+ while chunk = @chunks.shift
241
+ raise "last chunk already sent" if @last_chunk_sent
242
+ text = chunk.is_a?(Hash) ? chunk[:text] : chunk.to_s
243
+ send_data "#{format("%x", text.length).upcase}\r\n#{text}\r\n"
244
+ @last_chunk_sent = true if text.length == 0
245
+ end
246
+ end
247
+
248
+ # To add a multipart to the outgoing response, specify the headers and the
249
+ # body. If only a string is given, it's treated as the body (in this case,
250
+ # the header is assumed to be empty).
251
+ #
252
+ def multipart arg
253
+ vals = if arg.is_a?(String)
254
+ {:body => arg, :headers => {}}
255
+ else
256
+ arg
257
+ end
258
+
259
+ @multiparts ||= []
260
+ @multiparts << vals
261
+ end
262
+
263
+ # Multipart syntax is defined in RFC 2046, pgh 5.1.1 et seq.
264
+ # The CRLF which introduces the boundary line of each part (content entity)
265
+ # is defined as being part of the boundary, not of the preceding part.
266
+ # So we don't need to mess with interpreting the last bytes of a part
267
+ # to ensure they are CRLF-terminated.
268
+ #
269
+ def send_multiparts
270
+ send_headers unless @sent_headers
271
+ while part = @multiparts.shift
272
+ send_data "\r\n--#{@multipart_boundary}\r\n"
273
+ send_data( generate_header_lines( part[:headers] || {} ).join)
274
+ send_data "\r\n"
275
+ send_data part[:body].to_s
276
+ end
277
+ end
278
+
279
+ # TODO, this is going to be way too slow. Cache up the uuidgens.
280
+ #
281
+ def self.concoct_multipart_boundary
282
+ @multipart_index ||= 0
283
+ @multipart_index += 1
284
+ if @multipart_index >= 1000
285
+ @multipart_index = 0
286
+ @multipart_guid = nil
287
+ end
288
+ @multipart_guid ||= `uuidgen -r`.chomp.gsub(/\-/,"")
289
+ "#{@multipart_guid}#{@multipart_index}"
290
+ end
291
+
292
+ def send_redirect location
293
+ @status = 302 # TODO, make 301 available by parameter
294
+ @headers["Location"] = location
295
+ send_response
296
+ end
297
+ end
298
+ end
299
+
300
+ #----------------------------------------------------------------------------
301
+
302
+ require 'forwardable'
303
+
304
+ module EventMachine
305
+ class DelegatedHttpResponse < HttpResponse
306
+ extend Forwardable
307
+ def_delegators :@delegate,
308
+ :send_data,
309
+ :close_connection,
310
+ :close_connection_after_writing
311
+
312
+ def initialize dele
313
+ super()
314
+ @delegate = dele
315
+ end
316
+ end
317
+ end
318
+