andyjeffries-eventmachine_httpserver 0.1.201

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+
@@ -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
+ 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
+ // 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
+ Intern_http_conn = rb_intern("http_conn");
281
+ }
@@ -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
+