eventmachine_httpserver 0.1.1-x86-mswin32-60

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,281 @@
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
+
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, rb_intern ("@http______conn"), INT2NUM ((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*)(NUM2INT (rb_ivar_get (self, rb_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*)(NUM2INT (rb_ivar_get (self, rb_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*)(NUM2INT (rb_ivar_get (self, rb_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*)(NUM2INT (rb_ivar_get (self, rb_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
+ // INCOMPLETE, we need to define class Connections inside module EventMachine
269
+
270
+ VALUE Monorail = rb_define_module ("EventMachine");
271
+ VALUE EmModule = rb_define_module_under (Monorail, "HttpServer");
272
+ rb_define_method (EmModule, "post_init", (VALUE(*)(...))t_post_init, 0);
273
+ rb_define_method (EmModule, "receive_data", (VALUE(*)(...))t_receive_data, 1);
274
+ rb_define_method (EmModule, "receive_post_data", (VALUE(*)(...))t_receive_post_data, 1);
275
+ rb_define_method (EmModule, "unbind", (VALUE(*)(...))t_unbind, 0);
276
+ rb_define_method (EmModule, "process_http_request", (VALUE(*)(...))t_process_http_request, 0);
277
+ rb_define_method (EmModule, "no_environment_strings", (VALUE(*)(...))t_no_environment_strings, 0);
278
+ rb_define_method (EmModule, "dont_accumulate_post", (VALUE(*)(...))t_dont_accumulate_post, 0);
279
+ }
280
+
281
+
Binary file
@@ -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,313 @@
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
+ # sugarings for headers
63
+ def content_type *mime
64
+ if mime.length > 0
65
+ @headers ["Content-type"] = mime.first.to_s
66
+ else
67
+ @headers ["Content-type"]
68
+ end
69
+ end
70
+
71
+ # Sugaring for Set-cookie headers. These are a pain because there can easily and
72
+ # legitimately be more than one. So we use an ugly verb to signify that.
73
+ # #add_set_cookies does NOT disturb the set-cookie headers which may have been
74
+ # added on a prior call. #set_cookie clears them out first.
75
+ def add_set_cookie *ck
76
+ if ck.length > 0
77
+ h = (@headers ["Set-cookie"] ||= [])
78
+ ck.each {|c| h << c}
79
+ end
80
+ end
81
+ def set_cookie *ck
82
+ h = (@headers ["Set-cookie"] ||= [])
83
+ if ck.length > 0
84
+ h.clear
85
+ add_set_cookie *ck
86
+ else
87
+ h
88
+ end
89
+ end
90
+
91
+
92
+ # This is intended to send a complete HTTP response, including closing the connection
93
+ # if appropriate at the end of the transmission. Don't use this method to send partial
94
+ # or iterated responses. This method will send chunks and multiparts provided they
95
+ # are all available when we get here.
96
+ # Note that the default @status is 200 if the value doesn't exist.
97
+ def send_response
98
+ send_headers
99
+ send_body
100
+ send_trailer
101
+ close_connection_after_writing unless (@keep_connection_open and (@status || 200) == 200)
102
+ end
103
+
104
+ # Send the headers out in alpha-sorted order. This will degrade performance to some
105
+ # degree, and is intended only to simplify the construction of unit tests.
106
+ #
107
+ def send_headers
108
+ raise "sent headers already" if @sent_headers
109
+ @sent_headers = true
110
+
111
+ fixup_headers
112
+
113
+ ary = []
114
+ ary << "HTTP/1.1 #{@status || 200} ...\r\n"
115
+ ary += generate_header_lines(@headers)
116
+ ary << "\r\n"
117
+
118
+ send_data ary.join
119
+ end
120
+
121
+
122
+ def generate_header_lines in_hash
123
+ out_ary = []
124
+ in_hash.keys.sort.each {|k|
125
+ v = in_hash[k]
126
+ if v.is_a?(Array)
127
+ v.each {|v1| out_ary << "#{k}: #{v1}\r\n" }
128
+ else
129
+ out_ary << "#{k}: #{v}\r\n"
130
+ end
131
+ }
132
+ out_ary
133
+ end
134
+ private :generate_header_lines
135
+
136
+
137
+ # Examine the content type and data and other things, and perform a final
138
+ # fixup of the header array. We expect this to be called just before sending
139
+ # headers to the remote peer.
140
+ # In the case of multiparts, we ASSUME we will get called before any content
141
+ # gets sent out, because the multipart boundary is created here.
142
+ #
143
+ def fixup_headers
144
+ if @content
145
+ @headers ["Content-length"] = @content.to_s.length
146
+ elsif @chunks
147
+ @headers ["Transfer-encoding"] = "chunked"
148
+ # Might be nice to ENSURE there is no content-length header,
149
+ # but how to detect all the possible permutations of upper/lower case?
150
+ elsif @multiparts
151
+ @multipart_boundary = self.class.concoct_multipart_boundary
152
+ @headers ["Content-type"] = "multipart/x-mixed-replace; boundary=\"#{@multipart_boundary}\""
153
+ else
154
+ @headers ["Content-length"] = 0
155
+ end
156
+ end
157
+
158
+ # we send either content, chunks, or multiparts. Content can only be sent once.
159
+ # Chunks and multiparts can be sent any number of times.
160
+ # DO NOT close the connection or send any goodbye kisses. This method can
161
+ # be called multiple times to send out chunks or multiparts.
162
+ def send_body
163
+ if @content
164
+ send_content
165
+ elsif @chunks
166
+ send_chunks
167
+ elsif @multiparts
168
+ send_multiparts
169
+ else
170
+ @content = ""
171
+ send_content
172
+ end
173
+ end
174
+
175
+ # send a trailer which depends on the type of body we're dealing with.
176
+ # The assumption is that we're about to end the transmission of this particular
177
+ # HTTP response. (A connection-close may or may not follow.)
178
+ #
179
+ def send_trailer
180
+ send_headers unless @sent_headers
181
+ if @content
182
+ # no-op
183
+ elsif @chunks
184
+ unless @last_chunk_sent
185
+ chunk ""
186
+ send_chunks
187
+ end
188
+ elsif @multiparts
189
+ # in the lingo of RFC 2046/5.1.1, we're sending an "epilog"
190
+ # consisting of a blank line. I really don't know how that is
191
+ # supposed to interact with the case where we leave the connection
192
+ # open after transmitting the multipart response.
193
+ send_data "\r\n--#{@multipart_boundary}--\r\n\r\n"
194
+ else
195
+ # no-op
196
+ end
197
+ end
198
+
199
+ def send_content
200
+ raise "sent content already" if @sent_content
201
+ @sent_content = true
202
+ send_data((@content || "").to_s)
203
+ end
204
+
205
+ # add a chunk to go to the output.
206
+ # Will cause the headers to pick up "content-transfer-encoding"
207
+ # Add the chunk to a list. Calling #send_chunks will send out the
208
+ # available chunks and clear the chunk list WITHOUT closing the connection,
209
+ # so it can be called any number of times.
210
+ # TODO!!! Per RFC2616, we may not send chunks to an HTTP/1.0 client.
211
+ # Raise an exception here if our user tries to do so.
212
+ # Chunked transfer coding is defined in RFC2616 pgh 3.6.1.
213
+ # The argument can be a string or a hash. The latter allows for
214
+ # sending chunks with extensions (someday).
215
+ #
216
+ def chunk text
217
+ @chunks ||= []
218
+ @chunks << text
219
+ end
220
+
221
+ # send the contents of the chunk list and clear it out.
222
+ # ASSUMES that headers have been sent.
223
+ # Does NOT close the connection.
224
+ # Can be called multiple times.
225
+ # According to RFC2616, phg 3.6.1, the last chunk will be zero length.
226
+ # But some caller could accidentally set a zero-length chunk in the middle
227
+ # of the stream. If that should happen, raise an exception.
228
+ # The reason for supporting chunks that are hashes instead of just strings
229
+ # is to enable someday supporting chunk-extension codes (cf the RFC).
230
+ # TODO!!! We're not supporting the final entity-header that may be
231
+ # transmitted after the last (zero-length) chunk.
232
+ #
233
+ def send_chunks
234
+ send_headers unless @sent_headers
235
+ while chunk = @chunks.shift
236
+ raise "last chunk already sent" if @last_chunk_sent
237
+ text = chunk.is_a?(Hash) ? chunk[:text] : chunk.to_s
238
+ send_data "#{format("%x", text.length).upcase}\r\n#{text}\r\n"
239
+ @last_chunk_sent = true if text.length == 0
240
+ end
241
+ end
242
+
243
+ # To add a multipart to the outgoing response, specify the headers and the
244
+ # body. If only a string is given, it's treated as the body (in this case,
245
+ # the header is assumed to be empty).
246
+ #
247
+ def multipart arg
248
+ vals = if arg.is_a?(String)
249
+ {:body => arg, :headers => {}}
250
+ else
251
+ arg
252
+ end
253
+
254
+ @multiparts ||= []
255
+ @multiparts << vals
256
+ end
257
+
258
+ # Multipart syntax is defined in RFC 2046, pgh 5.1.1 et seq.
259
+ # The CRLF which introduces the boundary line of each part (content entity)
260
+ # is defined as being part of the boundary, not of the preceding part.
261
+ # So we don't need to mess with interpreting the last bytes of a part
262
+ # to ensure they are CRLF-terminated.
263
+ #
264
+ def send_multiparts
265
+ send_headers unless @sent_headers
266
+ while part = @multiparts.shift
267
+ send_data "\r\n--#{@multipart_boundary}\r\n"
268
+ send_data( generate_header_lines( part[:headers] || {} ).join)
269
+ send_data "\r\n"
270
+ send_data part[:body].to_s
271
+ end
272
+ end
273
+
274
+ # TODO, this is going to be way too slow. Cache up the uuidgens.
275
+ #
276
+ def self.concoct_multipart_boundary
277
+ @multipart_index ||= 0
278
+ @multipart_index += 1
279
+ if @multipart_index >= 1000
280
+ @multipart_index = 0
281
+ @multipart_guid = nil
282
+ end
283
+ @multipart_guid ||= `uuidgen -r`.chomp.gsub(/\-/,"")
284
+ "#{@multipart_guid}#{@multipart_index}"
285
+ end
286
+
287
+ def send_redirect location
288
+ @status = 302 # TODO, make 301 available by parameter
289
+ @headers["Location"] = location
290
+ send_response
291
+ end
292
+ end
293
+ end
294
+
295
+ #----------------------------------------------------------------------------
296
+
297
+ require 'forwardable'
298
+
299
+ module EventMachine
300
+ class DelegatedHttpResponse < HttpResponse
301
+ extend Forwardable
302
+ def_delegators :@delegate,
303
+ :send_data,
304
+ :close_connection,
305
+ :close_connection_after_writing
306
+
307
+ def initialize dele
308
+ super()
309
+ @delegate = dele
310
+ end
311
+ end
312
+ end
313
+