noderb-http 0.0.1 → 0.0.3

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.
@@ -28,7 +28,7 @@ extern "C" {
28
28
  #define HTTP_PARSER_VERSION_MINOR 0
29
29
 
30
30
  #include <sys/types.h>
31
- #if defined(_WIN32) && !defined(__MINGW32__)
31
+ #if defined(_WIN32) && !defined(__MINGW32__) && !defined(_MSC_VER)
32
32
  typedef __int8 int8_t;
33
33
  typedef unsigned __int8 uint8_t;
34
34
  typedef __int16 int16_t;
@@ -51,6 +51,13 @@ typedef int ssize_t;
51
51
  # define HTTP_PARSER_STRICT 1
52
52
  #endif
53
53
 
54
+ /* Compile with -DHTTP_PARSER_DEBUG=1 to add extra debugging information to
55
+ * the error reporting facility.
56
+ */
57
+ #ifndef HTTP_PARSER_DEBUG
58
+ # define HTTP_PARSER_DEBUG 0
59
+ #endif
60
+
54
61
 
55
62
  /* Maximium header size allowed */
56
63
  #define HTTP_MAX_HEADER_SIZE (80*1024)
@@ -58,6 +65,7 @@ typedef int ssize_t;
58
65
 
59
66
  typedef struct http_parser http_parser;
60
67
  typedef struct http_parser_settings http_parser_settings;
68
+ typedef struct http_parser_result http_parser_result;
61
69
 
62
70
 
63
71
  /* Callbacks should return non-zero to indicate an error. The parser will
@@ -125,6 +133,72 @@ enum flags
125
133
  };
126
134
 
127
135
 
136
+ /* Map for errno-related constants
137
+ *
138
+ * The provided argument should be a macro that takes 2 arguments.
139
+ */
140
+ #define HTTP_ERRNO_MAP(XX) \
141
+ /* No error */ \
142
+ XX(OK, "success") \
143
+ \
144
+ /* Callback-related errors */ \
145
+ XX(CB_message_begin, "the on_message_begin callback failed") \
146
+ XX(CB_path, "the on_path callback failed") \
147
+ XX(CB_query_string, "the on_query_string callback failed") \
148
+ XX(CB_url, "the on_url callback failed") \
149
+ XX(CB_fragment, "the on_fragment callback failed") \
150
+ XX(CB_header_field, "the on_header_field callback failed") \
151
+ XX(CB_header_value, "the on_header_value callback failed") \
152
+ XX(CB_headers_complete, "the on_headers_complete callback failed") \
153
+ XX(CB_body, "the on_body callback failed") \
154
+ XX(CB_message_complete, "the on_message_complete callback failed") \
155
+ \
156
+ /* Parsing-related errors */ \
157
+ XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
158
+ XX(HEADER_OVERFLOW, \
159
+ "too many header bytes seen; overflow detected") \
160
+ XX(CLOSED_CONNECTION, \
161
+ "data received after completed connection: close message") \
162
+ XX(INVALID_VERSION, "invalid HTTP version") \
163
+ XX(INVALID_STATUS, "invalid HTTP status code") \
164
+ XX(INVALID_METHOD, "invalid HTTP method") \
165
+ XX(INVALID_URL, "invalid URL") \
166
+ XX(INVALID_HOST, "invalid host") \
167
+ XX(INVALID_PORT, "invalid port") \
168
+ XX(INVALID_PATH, "invalid path") \
169
+ XX(INVALID_QUERY_STRING, "invalid query string") \
170
+ XX(INVALID_FRAGMENT, "invalid fragment") \
171
+ XX(LF_EXPECTED, "LF character expected") \
172
+ XX(INVALID_HEADER_TOKEN, "invalid character in header") \
173
+ XX(INVALID_CONTENT_LENGTH, \
174
+ "invalid character in content-length header") \
175
+ XX(INVALID_CHUNK_SIZE, \
176
+ "invalid character in chunk size header") \
177
+ XX(INVALID_CONSTANT, "invalid constant string") \
178
+ XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
179
+ XX(STRICT, "strict mode assertion failed") \
180
+ XX(UNKNOWN, "an unknown error occurred")
181
+
182
+
183
+ /* Define HPE_* values for each errno value above */
184
+ #define HTTP_ERRNO_GEN(n, s) HPE_##n,
185
+ enum http_errno {
186
+ HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
187
+ };
188
+ #undef HTTP_ERRNO_GEN
189
+
190
+
191
+ /* Get an http_errno value from an http_parser */
192
+ #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
193
+
194
+ /* Get the line number that generated the current error */
195
+ #if HTTP_PARSER_DEBUG
196
+ #define HTTP_PARSER_ERRNO_LINE(p) ((p)->error_lineno)
197
+ #else
198
+ #define HTTP_PARSER_ERRNO_LINE(p) 0
199
+ #endif
200
+
201
+
128
202
  struct http_parser {
129
203
  /** PRIVATE **/
130
204
  unsigned char type : 2;
@@ -141,13 +215,18 @@ struct http_parser {
141
215
  unsigned short http_minor;
142
216
  unsigned short status_code; /* responses only */
143
217
  unsigned char method; /* requests only */
218
+ unsigned char http_errno : 7;
144
219
 
145
220
  /* 1 = Upgrade header was present and the parser has exited because of that.
146
221
  * 0 = No upgrade header present.
147
222
  * Should be checked when http_parser_execute() returns in addition to
148
223
  * error checking.
149
224
  */
150
- char upgrade;
225
+ char upgrade : 1;
226
+
227
+ #if HTTP_PARSER_DEBUG
228
+ uint32_t error_lineno;
229
+ #endif
151
230
 
152
231
  /** PUBLIC **/
153
232
  void *data; /* A pointer to get hook to the "connection" or "socket" object */
@@ -156,10 +235,7 @@ struct http_parser {
156
235
 
157
236
  struct http_parser_settings {
158
237
  http_cb on_message_begin;
159
- http_data_cb on_path;
160
- http_data_cb on_query_string;
161
238
  http_data_cb on_url;
162
- http_data_cb on_fragment;
163
239
  http_data_cb on_header_field;
164
240
  http_data_cb on_header_value;
165
241
  http_cb on_headers_complete;
@@ -186,7 +262,13 @@ size_t http_parser_execute(http_parser *parser,
186
262
  int http_should_keep_alive(http_parser *parser);
187
263
 
188
264
  /* Returns a string version of the HTTP method. */
189
- const char *http_method_str(enum http_method);
265
+ const char *http_method_str(enum http_method m);
266
+
267
+ /* Return a string name of the given error */
268
+ const char *http_errno_name(enum http_errno err);
269
+
270
+ /* Return a string description of the given error */
271
+ const char *http_errno_description(enum http_errno err);
190
272
 
191
273
  #ifdef __cplusplus
192
274
  }
@@ -2,9 +2,11 @@
2
2
 
3
3
  typedef struct {
4
4
  long parser;
5
+ int pre_headers;
5
6
  } nodeRb_http;
6
7
 
7
8
  VALUE nodeRbHttpParser;
9
+ VALUE nodeRbHttpPointer;
8
10
 
9
11
  VALUE nodeRb_get_object_from_id(long id) {
10
12
  return rb_funcall(rb_const_get(rb_cObject, rb_intern("ObjectSpace")), rb_intern("_id2ref"), 1, rb_int2inum(id));
@@ -17,101 +19,89 @@ int nodeRb_http_on_message_begin(http_parser* parser) {
17
19
  return 0;
18
20
  }
19
21
 
20
- int nodeRb_http_on_message_complete(http_parser* parser) {
21
- nodeRb_http* client = parser->data;
22
- VALUE self = nodeRb_get_object_from_id(client->parser);
23
- if (http_should_keep_alive(parser)) {
24
- rb_funcall(self, rb_intern("on_close_keep_alive"), 0);
25
- };
26
- rb_funcall(self, rb_intern("on_message_complete"), 0);
27
- return 0;
28
- }
29
-
30
- int nodeRb_http_on_headers_complete(http_parser* parser) {
22
+ int nodeRb_http_on_url(http_parser* parser, const char *buf, size_t len) {
31
23
  nodeRb_http* client = parser->data;
32
24
  VALUE self = nodeRb_get_object_from_id(client->parser);
33
- rb_funcall(self, rb_intern("on_headers_complete_internal"), 0);
25
+ if(parser->type == HTTP_REQUEST){
26
+ rb_funcall(self, rb_intern("on_method"), 1, rb_str_new2(http_method_str(parser->method)));
27
+ }
28
+ rb_funcall(self, rb_intern("on_url"), 1, rb_str_new(buf, len));
34
29
  return 0;
35
30
  }
36
31
 
37
32
  int nodeRb_http_on_header_field(http_parser* parser, const char *buf, size_t len) {
38
33
  nodeRb_http* client = parser->data;
39
34
  VALUE self = nodeRb_get_object_from_id(client->parser);
40
- rb_funcall(self, rb_intern("on_header_field_internal"), 1, rb_str_new(buf, len));
35
+ rb_funcall(self, rb_intern("on_header_field"), 1, rb_str_new(buf, len));
41
36
  return 0;
42
37
  }
43
38
 
44
39
  int nodeRb_http_on_header_value(http_parser* parser, const char *buf, size_t len) {
45
40
  nodeRb_http* client = parser->data;
46
41
  VALUE self = nodeRb_get_object_from_id(client->parser);
47
- rb_funcall(self, rb_intern("on_header_value_internal"), 1, rb_str_new(buf, len));
42
+ rb_funcall(self, rb_intern("on_header_value"), 1, rb_str_new(buf, len));
48
43
  return 0;
49
44
  }
50
45
 
51
- int nodeRb_http_on_path(http_parser* parser, const char *buf, size_t len) {
52
- nodeRb_http* client = parser->data;
53
- VALUE self = nodeRb_get_object_from_id(client->parser);
54
- rb_funcall(self, rb_intern("on_method"), 1, rb_str_new2(http_method_str(parser->method)));
55
- rb_funcall(self, rb_intern("on_path"), 1, rb_str_new(buf, len));
56
- return 0;
57
- }
58
-
59
- int nodeRb_http_on_query_string(http_parser* parser, const char *buf, size_t len) {
60
- nodeRb_http* client = parser->data;
61
- VALUE self = nodeRb_get_object_from_id(client->parser);
62
- rb_funcall(self, rb_intern("on_query_string"), 1, rb_str_new(buf, len));
63
- return 0;
64
- }
65
-
66
- int nodeRb_http_on_url(http_parser* parser, const char *buf, size_t len) {
46
+ int nodeRb_http_on_headers_complete(http_parser* parser) {
67
47
  nodeRb_http* client = parser->data;
68
48
  VALUE self = nodeRb_get_object_from_id(client->parser);
69
- rb_funcall(self, rb_intern("on_url"), 1, rb_str_new(buf, len));
49
+ rb_funcall(self, rb_intern("on_version"), 2, rb_int2inum(parser->http_major), rb_int2inum(parser->http_minor));
50
+ if(parser->type == HTTP_RESPONSE){
51
+ rb_funcall(self, rb_intern("on_status_code"), 1, rb_int2inum(parser->status_code));
52
+ }
53
+ if (http_should_keep_alive(parser)) {
54
+ rb_funcall(self, rb_intern("on_keep_alive"), 1, Qtrue);
55
+ }
56
+ rb_funcall(self, rb_intern("on_headers_complete"), 0);
70
57
  return 0;
71
58
  }
72
59
 
73
- int nodeRb_http_on_fragment(http_parser* parser, const char *buf, size_t len) {
60
+ int nodeRb_http_on_body(http_parser* parser, const char *buf, size_t len) {
74
61
  nodeRb_http* client = parser->data;
75
62
  VALUE self = nodeRb_get_object_from_id(client->parser);
76
- rb_funcall(self, rb_intern("on_fragment"), 1, rb_str_new(buf, len));
63
+ rb_funcall(self, rb_intern("on_body"), 1, rb_str_new(buf, len));
77
64
  return 0;
78
65
  }
79
66
 
80
- int nodeRb_http_on_body(http_parser* parser, const char *buf, size_t len) {
67
+ int nodeRb_http_on_message_complete(http_parser* parser) {
81
68
  nodeRb_http* client = parser->data;
82
69
  VALUE self = nodeRb_get_object_from_id(client->parser);
83
- rb_funcall(self, rb_intern("on_body"), 1, rb_str_new(buf, len));
70
+ rb_funcall(self, rb_intern("on_message_complete"), 0);
84
71
  return 0;
85
72
  }
86
73
 
87
- VALUE nodeRb_http_setup(VALUE self) {
74
+ VALUE nodeRb_http_setup(VALUE self, VALUE type) {
88
75
  http_parser_settings* settings = malloc(sizeof (http_parser_settings));
89
76
 
90
77
  settings->on_message_begin = nodeRb_http_on_message_begin;
91
- settings->on_message_complete = nodeRb_http_on_message_complete;
92
- settings->on_headers_complete = nodeRb_http_on_headers_complete;
93
-
94
- settings->on_path = nodeRb_http_on_path;
95
- settings->on_query_string = nodeRb_http_on_query_string;
78
+
96
79
  settings->on_url = nodeRb_http_on_url;
97
80
 
98
81
  settings->on_header_field = nodeRb_http_on_header_field;
99
82
  settings->on_header_value = nodeRb_http_on_header_value;
100
83
 
101
- settings->on_fragment = nodeRb_http_on_fragment;
84
+ settings->on_headers_complete = nodeRb_http_on_headers_complete;
85
+
102
86
  settings->on_body = nodeRb_http_on_body;
103
87
 
88
+ settings->on_message_complete = nodeRb_http_on_message_complete;
104
89
 
105
90
  http_parser* parser = malloc(sizeof (http_parser));
106
- http_parser_init(parser, HTTP_REQUEST);
91
+
92
+ if(ID2SYM(rb_intern("response")) == type){
93
+ http_parser_init(parser, HTTP_RESPONSE);
94
+ }else{
95
+ http_parser_init(parser, HTTP_REQUEST);
96
+ }
107
97
 
108
98
  nodeRb_http* client = malloc(sizeof (nodeRb_http));
109
99
  parser->data = client;
110
100
 
111
101
  client->parser = rb_num2long(rb_obj_id(self));
112
102
 
113
- rb_iv_set(self, "@settings", Data_Wrap_Struct(nodeRbHttpParser, 0, NULL, settings));
114
- rb_iv_set(self, "@parser", Data_Wrap_Struct(nodeRbHttpParser, 0, NULL, parser));
103
+ rb_iv_set(self, "@settings", Data_Wrap_Struct(nodeRbHttpPointer, 0, NULL, settings));
104
+ rb_iv_set(self, "@parser", Data_Wrap_Struct(nodeRbHttpPointer, 0, NULL, parser));
115
105
  };
116
106
 
117
107
  VALUE nodeRb_http_parse(VALUE self, VALUE data) {
@@ -132,8 +122,8 @@ VALUE nodeRb_http_parse(VALUE self, VALUE data) {
132
122
 
133
123
  if (parser->upgrade) {
134
124
  rb_funcall(self, rb_intern("on_upgrade"), 0);
135
- } else if (parsed != RSTRING_LEN(data)) {
136
- rb_funcall(self, rb_intern("on_error"), 0);
125
+ } else if (parsed != (unsigned) RSTRING_LEN(data)) {
126
+ rb_funcall(self, rb_intern("on_error"), 2, rb_str_new2(http_errno_name(parser->http_errno)), rb_str_new2(http_errno_description(parser->http_errno)));
137
127
  };
138
128
  };
139
129
 
@@ -149,7 +139,7 @@ VALUE nodeRb_http_dispose(VALUE self) {
149
139
  free(parser);
150
140
  };
151
141
 
152
- void Init_noderb_http() {
142
+ void Init_noderb_http_extension() {
153
143
  // Define module
154
144
  VALUE nodeRb = rb_define_module("NodeRb");
155
145
  // Modules
@@ -157,9 +147,10 @@ void Init_noderb_http() {
157
147
  // Http
158
148
  VALUE nodeRbHttp = rb_define_module_under(nodeRbModules, "Http");
159
149
  // Http parser
160
- nodeRbHttpParser = rb_define_class_under(nodeRbHttp, "Parser", rb_cObject);
150
+ nodeRbHttpPointer = rb_define_class_under(nodeRbHttp, "Pointer", rb_cObject);
151
+ nodeRbHttpParser = rb_define_module_under(nodeRbHttp, "Parser");
161
152
  // Methods
162
- rb_define_method(nodeRbHttpParser, "setup", nodeRb_http_setup, 0);
153
+ rb_define_method(nodeRbHttpParser, "setup", nodeRb_http_setup, 1);
163
154
  rb_define_method(nodeRbHttpParser, "parse", nodeRb_http_parse, 1);
164
155
  rb_define_method(nodeRbHttpParser, "dispose", nodeRb_http_dispose, 0);
165
156
  }
@@ -0,0 +1,112 @@
1
+ module NodeRb
2
+ module Modules
3
+ module Http
4
+
5
+ module Parser
6
+
7
+ def on_message_begin
8
+ @_header_name = ""
9
+ @_header_value = ""
10
+ @headers = {}
11
+ @body = ""
12
+ @method = ""
13
+ @url = ""
14
+ @status_code = 0
15
+ @upgrade = false
16
+ @keep_alive = false
17
+ @_active = true
18
+ end
19
+
20
+ def on_method method
21
+ @method = method
22
+ end
23
+
24
+ def on_url url
25
+ @url = url
26
+ end
27
+
28
+ def on_header_field name
29
+ if @_header_state == :value
30
+ on_header(@_header_name, @_header_value)
31
+ @_header_name = ""
32
+ @_header_value = ""
33
+ end
34
+ @_header_state = :field
35
+ @_header_name << name
36
+ end
37
+
38
+ def on_header_value value
39
+ @_header_state = :value
40
+ @_header_value ||= ""
41
+ @_header_value << value
42
+ end
43
+
44
+ def on_header name, value
45
+ @headers[name] = value if @_active
46
+ end
47
+
48
+ def on_version major, minor
49
+ @version_major = major if @_active
50
+ @version_minor = minor if @_active
51
+ end
52
+
53
+ def on_status_code status_code
54
+ @status_code = status_code if @_active
55
+ end
56
+
57
+ def on_keep_alive keep_alive
58
+ @keep_alive = keep_alive if @_active
59
+ end
60
+
61
+ def on_headers_complete
62
+ on_header(@_header_name, @_header_value)
63
+ @_header_name = ""
64
+ @_header_value = ""
65
+ on_message_header
66
+ end
67
+
68
+ def on_upgrade
69
+ @_active = false
70
+ @upgrade = true
71
+ end
72
+
73
+ def on_body body
74
+ @body << body if @_active
75
+ end
76
+
77
+ def on_message_complete
78
+ on_message_body
79
+ end
80
+
81
+ def on_error name, description
82
+ @_active = true
83
+ on_message_error(name, description)
84
+ end
85
+
86
+ def on_message_header
87
+ # User overrides this to get whole message header
88
+ # ------
89
+ # @headers => Hash
90
+ # @url => String
91
+ # @version_major => Integer
92
+ # @version_minor => Integer
93
+ # @status_code (response) => Integer
94
+ # @method (request) => String
95
+ # @upgrade => Boolean
96
+ # @keep_alive => Boolean
97
+ end
98
+
99
+ def on_message_body
100
+ # User overrides this to get whole message body
101
+ # @body => String
102
+ end
103
+
104
+ def on_message_error name, description
105
+ # User overrides this to be notified of errors
106
+ end
107
+
108
+ end
109
+
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,9 @@
1
+ module NodeRb
2
+ module Modules
3
+ module Http
4
+
5
+ VERSION = "0.0.3" unless const_defined?(:VERSION)
6
+
7
+ end
8
+ end
9
+ end
@@ -1,99 +1,2 @@
1
- module NodeRb
2
- module Modules
3
- module Http
4
-
5
- module Rack
6
-
7
- def initialize
8
- @env = {}
9
- end
10
-
11
- def on_header name, value
12
- end
13
-
14
- end
15
-
16
- class Parser
17
-
18
- def on_message_begin
19
- end
20
-
21
- def on_method method
22
- end
23
-
24
- def on_path path
25
- end
26
-
27
- def on_query_string query_string
28
- end
29
-
30
- def on_url url
31
- end
32
-
33
- def on_fragment fragment
34
- end
35
-
36
- def on_header_field_internal name
37
- if @_header_state == :value
38
- on_header_value(@_header_value)
39
- on_header(@_header_name, @_header_value)
40
- @_header_name = nil
41
- @_header_value = nil
42
- end
43
- @_header_state = :field
44
- @_header_name ||= ""
45
- @_header_name << name
46
- end
47
-
48
- def on_header_field name
49
-
50
- end
51
-
52
- def on_header_value_internal value
53
- if @_header_state == :field
54
- on_header_field(@_header_name)
55
- end
56
- @_header_state = :value
57
- @_header_value ||= ""
58
- @_header_value << value
59
- end
60
-
61
- def on_header_value value
62
-
63
- end
64
-
65
- def on_header name, value
66
- end
67
-
68
- def on_headers_complete_internal
69
- on_header_value(@_header_value)
70
- on_header(@_header_name, @_header_value)
71
- @_header_name = nil
72
- @_header_value = nil
73
- on_headers_complete
74
- end
75
-
76
- def on_headers_complete
77
-
78
- end
79
-
80
- def on_close_keep_alive
81
- end
82
-
83
- def on_upgrade
84
- end
85
-
86
- def on_body body
87
- end
88
-
89
- def on_message_complete
90
- end
91
-
92
- def on_error
93
- end
94
-
95
- end
96
-
97
- end
98
- end
99
- end
1
+ require "noderb/modules/http/version"
2
+ require "noderb/modules/http/parser"
data/lib/noderb-http.rb CHANGED
@@ -1,2 +1,2 @@
1
- require 'noderb_http_extension/noderb_http'
2
- require "noderb/modules/http"
1
+ require 'noderb_http_extension'
2
+ require 'noderb/modules/http'
@@ -0,0 +1,25 @@
1
+ CASES << {
2
+ :name => "curl_get",
3
+ :type => :request,
4
+ :message_complete_on_eof => false,
5
+ :data => [
6
+ "GET /test HTTP/1.1\n",
7
+ "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\n",
8
+ "Host: 0.0.0.0=5000\n",
9
+ "Accept: */*\n",
10
+ "\n"
11
+ ],
12
+ :should_keep_alive => true,
13
+ :http_major => 1,
14
+ :http_minor => 1,
15
+ :method => "GET",
16
+ :request_url => "/test",
17
+ :num_headers => 3,
18
+ :headers => {
19
+ "User-Agent" => "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1",
20
+ "Host" => "0.0.0.0=5000",
21
+ "Accept" => "*/*",
22
+ },
23
+ :body => "",
24
+ :upgrade => false
25
+ }
@@ -0,0 +1,29 @@
1
+ CASES << {
2
+ :name => "patch_request",
3
+ :type => :request,
4
+ :message_complete_on_eof => false,
5
+ :data => [
6
+ "PATCH /file.txt HTTP/1.1\n",
7
+ "Host: www.example.com\n",
8
+ "Content-Type: application/example\n",
9
+ "If-Match: \"e0023aa4e\"\n",
10
+ "Content-Length: 10\n",
11
+ "\n",
12
+ "abcde",
13
+ "efghi"
14
+ ],
15
+ :should_keep_alive => true,
16
+ :http_major => 1,
17
+ :http_minor => 1,
18
+ :method => "PATCH",
19
+ :request_url => "/file.txt",
20
+ :num_headers => 4,
21
+ :headers => {
22
+ "Host" => "www.example.com",
23
+ "Content-Length" => "10",
24
+ "If-Match" => "\"e0023aa4e\"",
25
+ "Content-Type" => "application/example"
26
+ },
27
+ :body => "abcdeefghi",
28
+ :upgrade => false
29
+ }
@@ -0,0 +1,38 @@
1
+ CASES << {
2
+ :name => "response",
3
+ :type => :response,
4
+ :message_complete_on_eof => false,
5
+ :data => [
6
+ "HTTP/1.0 301 Moved Permanently\n",
7
+ "Date: Thu, 03 Jun 2010 09:56:32 GMT\n",
8
+ "Server: Apache/2.2.3 (Red Hat)\n",
9
+ "Cache-Control: public\n",
10
+ "Pragma: \n",
11
+ "Location: http://www.bonjourmadame.fr/\n",
12
+ "Vary: Accept-Encoding\n",
13
+ "Content-Length: 0\n",
14
+ "Content-Type: text/html; charset=UTF-8\n",
15
+ "Connection: keep-alive\n",
16
+ "\n"
17
+ ],
18
+ :should_keep_alive => true,
19
+ :http_major => 1,
20
+ :http_minor => 0,
21
+ :method => "",
22
+ :request_url => "",
23
+ :num_headers => 9,
24
+ :status_code => 301,
25
+ :headers => {
26
+ "Date" => "Thu, 03 Jun 2010 09:56:32 GMT",
27
+ "Server" => "Apache/2.2.3 (Red Hat)",
28
+ "Cache-Control" => "public",
29
+ "Pragma" => "",
30
+ "Location" => "http://www.bonjourmadame.fr/",
31
+ "Vary" => "Accept-Encoding",
32
+ "Content-Length" => "0",
33
+ "Content-Type" => "text/html; charset=UTF-8",
34
+ "Connection" => "keep-alive"
35
+ },
36
+ :upgrade => false,
37
+ :body => ""
38
+ }