http_parser.rb 0.6.0.beta.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/linux.yml +23 -0
  3. data/.github/workflows/windows.yml +23 -0
  4. data/.gitignore +5 -4
  5. data/.gitmodules +2 -2
  6. data/README.md +2 -2
  7. data/Rakefile +4 -2
  8. data/ext/ruby_http_parser/extconf.rb +1 -1
  9. data/ext/ruby_http_parser/org/ruby_http_parser/RubyHttpParser.java +86 -52
  10. data/ext/ruby_http_parser/ruby_http_parser.c +53 -7
  11. data/ext/ruby_http_parser/vendor/http-parser/AUTHORS +37 -1
  12. data/ext/ruby_http_parser/vendor/http-parser/LICENSE-MIT +1 -5
  13. data/ext/ruby_http_parser/vendor/http-parser/Makefile +110 -8
  14. data/ext/ruby_http_parser/vendor/http-parser/README.md +105 -37
  15. data/ext/ruby_http_parser/vendor/http-parser/bench.c +128 -0
  16. data/ext/ruby_http_parser/vendor/http-parser/contrib/parsertrace.c +157 -0
  17. data/ext/ruby_http_parser/vendor/http-parser/contrib/url_parser.c +47 -0
  18. data/ext/ruby_http_parser/vendor/http-parser/http_parser.c +892 -510
  19. data/ext/ruby_http_parser/vendor/http-parser/http_parser.gyp +34 -2
  20. data/ext/ruby_http_parser/vendor/http-parser/http_parser.h +198 -77
  21. data/ext/ruby_http_parser/vendor/http-parser/test.c +1781 -201
  22. data/ext/ruby_http_parser/vendor/http-parser-java/http_parser.c +271 -154
  23. data/ext/ruby_http_parser/vendor/http-parser-java/http_parser.h +48 -61
  24. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPMethod.java +5 -3
  25. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserSettings.java +37 -104
  26. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPParser.java +116 -101
  27. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/ParserSettings.java +9 -5
  28. data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Message.java +1 -1
  29. data/ext/ruby_http_parser/vendor/http-parser-java/test.c +579 -153
  30. data/http_parser.rb.gemspec +14 -9
  31. data/spec/parser_spec.rb +177 -99
  32. data/spec/support/requests.json +2 -2
  33. data/spec/support/responses.json +20 -0
  34. data/tasks/spec.rake +1 -1
  35. metadata +131 -162
  36. data/Gemfile.lock +0 -39
  37. data/ext/ruby_http_parser/vendor/http-parser/CONTRIBUTIONS +0 -4
  38. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPHeadersCompleteCallback.java +0 -13
  39. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPHeadersCompleteCallback.java +0 -12
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cb02853976523252e0d5f36af3ba83b363010d4c163d9a4951b419054e7486ae
4
+ data.tar.gz: 3b7ac3ea5df5866582e0f68653d205764498e60e01835d2e19661cf4e1089d1b
5
+ SHA512:
6
+ metadata.gz: 67516e3f1024e4bbf8dd00ae441646d53ca279313683b6f8da24668901c59b364608ecd9c69dab63709d9f89245ddc5363e0865f294a5af9d8e0b39d4712328f
7
+ data.tar.gz: 505a790706bc8f84404c2c6dd7e2b0025d591ca30311ca93d4eda4cf58a274c105acf68ed029ce96e38b04b040b86d4e953cf944a80a1e66a72f2a737d8cbfba
@@ -0,0 +1,23 @@
1
+ name: Testing on Ubuntu
2
+ on:
3
+ - push
4
+ - pull_request
5
+ jobs:
6
+ build:
7
+ runs-on: ${{ matrix.os }}
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: [ '2.6', '2.7', '3.0' ]
12
+ os: [ 'ubuntu-latest' ]
13
+ name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }}
14
+ steps:
15
+ - uses: actions/checkout@v2
16
+ - uses: ruby/setup-ruby@v1
17
+ with:
18
+ ruby-version: ${{ matrix.ruby }}
19
+ - name: unit testing
20
+ run: |
21
+ gem install bundler rake
22
+ bundle install --jobs 4 --retry 3
23
+ bundle exec rake
@@ -0,0 +1,23 @@
1
+ name: Testing on Windows
2
+ on:
3
+ - push
4
+ - pull_request
5
+ jobs:
6
+ build:
7
+ runs-on: ${{ matrix.os }}
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: [ '2.6', '2.7', '3.0' ]
12
+ os: [ 'windows-latest' ]
13
+ name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }}
14
+ steps:
15
+ - uses: actions/checkout@v2
16
+ - uses: ruby/setup-ruby@v1
17
+ with:
18
+ ruby-version: ${{ matrix.ruby }}
19
+ - name: unit testing
20
+ run: |
21
+ gem install bundler rake
22
+ bundle install --jobs 4 --retry 3
23
+ bundle exec rake
data/.gitignore CHANGED
@@ -1,11 +1,12 @@
1
- tmp
1
+ *.bundle
2
2
  *.bundle
3
3
  *.gem
4
+ *.jar
4
5
  *.o
6
+ *.rbc
5
7
  *.so
6
- *.bundle
7
- *.jar
8
8
  *.swp
9
+ Gemfile.lock
9
10
  Makefile
10
11
  tags
11
- *.rbc
12
+ tmp
data/.gitmodules CHANGED
@@ -1,6 +1,6 @@
1
1
  [submodule "http-parser"]
2
2
  path = ext/ruby_http_parser/vendor/http-parser
3
- url = git://github.com/joyent/http-parser.git
3
+ url = https://github.com/nodejs/http-parser.git
4
4
  [submodule "http-parser-java"]
5
5
  path = ext/ruby_http_parser/vendor/http-parser-java
6
- url = git://github.com/http-parser/http-parser.java
6
+ url = https://github.com/tmm1/http-parser.java
data/README.md CHANGED
@@ -3,13 +3,13 @@
3
3
  A simple callback-based HTTP request/response parser for writing http
4
4
  servers, clients and proxies.
5
5
 
6
- This gem is built on top of [joyent/http-parser](http://github.com/joyent/http-parser) and its java port [a2800276/http-parser.java](http://github.com/a2800276/http-parser.java).
6
+ This gem is built on top of [joyent/http-parser](https://github.com/joyent/http-parser) and its java port [http-parser/http-parser.java](https://github.com/http-parser/http-parser.java).
7
7
 
8
8
  ## Supported Platforms
9
9
 
10
10
  This gem aims to work on all major Ruby platforms, including:
11
11
 
12
- - MRI 1.8 and 1.9
12
+ - MRI 1.8, 1.9 and 2.0; should work on MRI 2.4+
13
13
  - Rubinius
14
14
  - JRuby
15
15
  - win32
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- # load tasks
2
- Dir['tasks/*.rake'].sort.each { |f| load f }
1
+ require 'bundler/gem_tasks'
3
2
 
4
3
  # default task
5
4
  task :compile => :submodules
6
5
  task :default => [:compile, :spec]
6
+
7
+ # load tasks
8
+ Dir['tasks/*.rake'].sort.each { |f| load f }
@@ -18,7 +18,7 @@ src_dir = File.expand_path('../', __FILE__)
18
18
  }
19
19
  end
20
20
 
21
- $CFLAGS << " -I#{src_dir}"
21
+ $CFLAGS << " -I\"#{src_dir}\""
22
22
 
23
23
  dir_config("ruby_http_parser")
24
24
  create_makefile("ruby_http_parser")
@@ -1,32 +1,34 @@
1
1
  package org.ruby_http_parser;
2
2
 
3
+ import http_parser.HTTPException;
4
+ import http_parser.HTTPMethod;
5
+ import http_parser.HTTPParser;
6
+ import http_parser.lolevel.HTTPCallback;
7
+ import http_parser.lolevel.HTTPDataCallback;
8
+ import http_parser.lolevel.ParserSettings;
9
+
10
+ import java.nio.ByteBuffer;
11
+ import java.util.Arrays;
12
+ import java.util.ArrayList;
13
+ import java.util.List;
14
+
15
+ import org.jcodings.Encoding;
16
+ import org.jcodings.specific.UTF8Encoding;
3
17
  import org.jruby.Ruby;
4
18
  import org.jruby.RubyArray;
5
19
  import org.jruby.RubyClass;
6
20
  import org.jruby.RubyHash;
7
- import org.jruby.RubyModule;
8
21
  import org.jruby.RubyNumeric;
9
22
  import org.jruby.RubyObject;
10
23
  import org.jruby.RubyString;
11
24
  import org.jruby.RubySymbol;
12
-
25
+ import org.jruby.anno.JRubyMethod;
26
+ import org.jruby.exceptions.RaiseException;
13
27
  import org.jruby.runtime.ObjectAllocator;
14
28
  import org.jruby.runtime.ThreadContext;
15
29
  import org.jruby.runtime.builtin.IRubyObject;
16
-
17
- import org.jruby.anno.JRubyMethod;
18
- import org.jruby.exceptions.RaiseException;
19
-
20
30
  import org.jruby.util.ByteList;
21
31
 
22
- import org.jcodings.specific.UTF8Encoding;
23
-
24
- import java.nio.ByteBuffer;
25
- import http_parser.*;
26
- import http_parser.lolevel.ParserSettings;
27
- import http_parser.lolevel.HTTPCallback;
28
- import http_parser.lolevel.HTTPDataCallback;
29
-
30
32
  public class RubyHttpParser extends RubyObject {
31
33
 
32
34
  @JRubyMethod(name = "strict?", module = true)
@@ -40,7 +42,7 @@ public class RubyHttpParser extends RubyObject {
40
42
  }
41
43
  };
42
44
 
43
- byte[] fetchBytes (ByteBuffer b, int pos, int len) {
45
+ byte[] fetchBytes(ByteBuffer b, int pos, int len) {
44
46
  byte[] by = new byte[len];
45
47
  int saved = b.position();
46
48
  b.position(pos);
@@ -80,11 +82,17 @@ public class RubyHttpParser extends RubyObject {
80
82
  private byte[] _current_header;
81
83
  private byte[] _last_header;
82
84
 
85
+ private static final Encoding UTF8 = UTF8Encoding.INSTANCE;
86
+
87
+ private static final List<String> VALUE_TYPES = new ArrayList<String>(
88
+ Arrays.asList("mixed", "arrays", "strings")
89
+ );
90
+
83
91
  public RubyHttpParser(final Ruby runtime, RubyClass clazz) {
84
- super(runtime,clazz);
92
+ super(runtime, clazz);
85
93
 
86
94
  this.runtime = runtime;
87
- this.eParserError = (RubyClass)runtime.getModule("HTTP").getClass("Parser").getConstant("Error");
95
+ this.eParserError = (RubyClass) runtime.getModule("HTTP").getClass("Parser").getConstant("Error");
88
96
 
89
97
  this.on_message_begin = null;
90
98
  this.on_headers_complete = null;
@@ -95,7 +103,8 @@ public class RubyHttpParser extends RubyObject {
95
103
 
96
104
  this.completed = false;
97
105
 
98
- this.header_value_type = runtime.getModule("HTTP").getClass("Parser").getInstanceVariable("@default_header_value_type");
106
+ this.header_value_type = runtime.getModule("HTTP").getClass("Parser")
107
+ .getInstanceVariable("@default_header_value_type");
99
108
 
100
109
  initSettings();
101
110
  init();
@@ -105,15 +114,19 @@ public class RubyHttpParser extends RubyObject {
105
114
  this.settings = new ParserSettings();
106
115
 
107
116
  this.settings.on_url = new HTTPDataCallback() {
108
- public int cb (http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
117
+ public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
109
118
  byte[] data = fetchBytes(buf, pos, len);
110
- ((RubyString)requestUrl).cat(data);
119
+ if (runtime.is1_9() || runtime.is2_0()) {
120
+ ((RubyString) requestUrl).cat(data, 0, data.length, UTF8);
121
+ } else {
122
+ ((RubyString) requestUrl).cat(data);
123
+ }
111
124
  return 0;
112
125
  }
113
126
  };
114
127
 
115
128
  this.settings.on_header_field = new HTTPDataCallback() {
116
- public int cb (http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
129
+ public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
117
130
  byte[] data = fetchBytes(buf, pos, len);
118
131
 
119
132
  if (_current_header == null)
@@ -123,7 +136,7 @@ public class RubyHttpParser extends RubyObject {
123
136
  System.arraycopy(_current_header, 0, tmp, 0, _current_header.length);
124
137
  System.arraycopy(data, 0, tmp, _current_header.length, data.length);
125
138
  _current_header = tmp;
126
- }
139
+ }
127
140
 
128
141
  return 0;
129
142
  }
@@ -133,7 +146,7 @@ public class RubyHttpParser extends RubyObject {
133
146
  final RubySymbol stopSym = runtime.newSymbol("stop");
134
147
  final RubySymbol resetSym = runtime.newSymbol("reset");
135
148
  this.settings.on_header_value = new HTTPDataCallback() {
136
- public int cb (http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
149
+ public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
137
150
  byte[] data = fetchBytes(buf, pos, len);
138
151
  ThreadContext context = headers.getRuntime().getCurrentContext();
139
152
  IRubyObject key, val;
@@ -145,57 +158,74 @@ public class RubyHttpParser extends RubyObject {
145
158
  _current_header = null;
146
159
  }
147
160
 
148
- key = RubyString.newString(runtime, new ByteList(_last_header, UTF8Encoding.INSTANCE, false));
161
+ key = RubyString.newString(runtime, new ByteList(_last_header, UTF8, false));
149
162
  val = headers.op_aref(context, key);
150
163
 
151
164
  if (new_field == 1) {
152
165
  if (val.isNil()) {
153
166
  if (header_value_type == arraysSym) {
154
- headers.op_aset(context, key, RubyArray.newArrayLight(runtime, RubyString.newStringLight(runtime, 10)));
167
+ headers.op_aset(context, key,
168
+ RubyArray.newArrayLight(runtime, RubyString.newStringLight(runtime, 10, UTF8)));
155
169
  } else {
156
- headers.op_aset(context, key, RubyString.newStringLight(runtime, 10));
170
+ headers.op_aset(context, key, RubyString.newStringLight(runtime, 10, UTF8));
157
171
  }
158
172
  } else {
159
173
  if (header_value_type == mixedSym) {
160
174
  if (val instanceof RubyString) {
161
- headers.op_aset(context, key, RubyArray.newArrayLight(runtime, val, RubyString.newStringLight(runtime, 10)));
175
+ headers.op_aset(context, key,
176
+ RubyArray.newArrayLight(runtime, val, RubyString.newStringLight(runtime, 10, UTF8)));
162
177
  } else {
163
- ((RubyArray)val).add(RubyString.newStringLight(runtime, 10));
178
+ ((RubyArray) val).add(RubyString.newStringLight(runtime, 10, UTF8));
164
179
  }
165
180
  } else if (header_value_type == arraysSym) {
166
- ((RubyArray)val).add(RubyString.newStringLight(runtime, 10));
181
+ ((RubyArray) val).add(RubyString.newStringLight(runtime, 10, UTF8));
167
182
  } else {
168
- ((RubyString)val).cat(',').cat(' ');
183
+ if (runtime.is1_9() || runtime.is2_0()) {
184
+ ((RubyString) val).cat(',', UTF8).cat(' ', UTF8);
185
+ } else {
186
+ ((RubyString) val).cat(',').cat(' ');
187
+ }
169
188
  }
170
189
  }
171
190
  val = headers.op_aref(context, key);
172
191
  }
173
192
 
174
193
  if (val instanceof RubyArray) {
175
- val = ((RubyArray)val).entry(-1);
194
+ val = ((RubyArray) val).entry(-1);
176
195
  }
177
196
 
178
- ((RubyString)val).cat(data);
197
+ if (runtime.is1_9() || runtime.is2_0()) {
198
+ ((RubyString) val).cat(data, 0, data.length, UTF8);
199
+ } else {
200
+ ((RubyString) val).cat(data);
201
+ }
179
202
 
180
203
  return 0;
181
204
  }
182
205
  };
183
206
 
184
207
  this.settings.on_message_begin = new HTTPCallback() {
185
- public int cb (http_parser.lolevel.HTTPParser p) {
208
+ public int cb(http_parser.lolevel.HTTPParser p) {
186
209
  headers = new RubyHash(runtime);
187
210
 
188
- requestUrl = RubyString.newEmptyString(runtime);
189
- requestPath = RubyString.newEmptyString(runtime);
190
- queryString = RubyString.newEmptyString(runtime);
191
- fragment = RubyString.newEmptyString(runtime);
192
-
193
- upgradeData = RubyString.newEmptyString(runtime);
211
+ if (runtime.is1_9() || runtime.is2_0()) {
212
+ requestUrl = RubyString.newEmptyString(runtime, UTF8);
213
+ requestPath = RubyString.newEmptyString(runtime, UTF8);
214
+ queryString = RubyString.newEmptyString(runtime, UTF8);
215
+ fragment = RubyString.newEmptyString(runtime, UTF8);
216
+ upgradeData = RubyString.newEmptyString(runtime, UTF8);
217
+ } else {
218
+ requestUrl = RubyString.newEmptyString(runtime);
219
+ requestPath = RubyString.newEmptyString(runtime);
220
+ queryString = RubyString.newEmptyString(runtime);
221
+ fragment = RubyString.newEmptyString(runtime);
222
+ upgradeData = RubyString.newEmptyString(runtime);
223
+ }
194
224
 
195
225
  IRubyObject ret = runtime.getNil();
196
226
 
197
227
  if (callback_object != null) {
198
- if (((RubyObject)callback_object).respondsTo("on_message_begin")) {
228
+ if (((RubyObject) callback_object).respondsTo("on_message_begin")) {
199
229
  ThreadContext context = callback_object.getRuntime().getCurrentContext();
200
230
  ret = callback_object.callMethod(context, "on_message_begin");
201
231
  }
@@ -212,13 +242,13 @@ public class RubyHttpParser extends RubyObject {
212
242
  }
213
243
  };
214
244
  this.settings.on_message_complete = new HTTPCallback() {
215
- public int cb (http_parser.lolevel.HTTPParser p) {
245
+ public int cb(http_parser.lolevel.HTTPParser p) {
216
246
  IRubyObject ret = runtime.getNil();
217
247
 
218
248
  completed = true;
219
249
 
220
250
  if (callback_object != null) {
221
- if (((RubyObject)callback_object).respondsTo("on_message_complete")) {
251
+ if (((RubyObject) callback_object).respondsTo("on_message_complete")) {
222
252
  ThreadContext context = callback_object.getRuntime().getCurrentContext();
223
253
  ret = callback_object.callMethod(context, "on_message_complete");
224
254
  }
@@ -235,11 +265,11 @@ public class RubyHttpParser extends RubyObject {
235
265
  }
236
266
  };
237
267
  this.settings.on_headers_complete = new HTTPCallback() {
238
- public int cb (http_parser.lolevel.HTTPParser p) {
268
+ public int cb(http_parser.lolevel.HTTPParser p) {
239
269
  IRubyObject ret = runtime.getNil();
240
270
 
241
271
  if (callback_object != null) {
242
- if (((RubyObject)callback_object).respondsTo("on_headers_complete")) {
272
+ if (((RubyObject) callback_object).respondsTo("on_headers_complete")) {
243
273
  ThreadContext context = callback_object.getRuntime().getCurrentContext();
244
274
  ret = callback_object.callMethod(context, "on_headers_complete", headers);
245
275
  }
@@ -258,18 +288,19 @@ public class RubyHttpParser extends RubyObject {
258
288
  }
259
289
  };
260
290
  this.settings.on_body = new HTTPDataCallback() {
261
- public int cb (http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
291
+ public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
262
292
  IRubyObject ret = runtime.getNil();
263
293
  byte[] data = fetchBytes(buf, pos, len);
264
294
 
265
295
  if (callback_object != null) {
266
- if (((RubyObject)callback_object).respondsTo("on_body")) {
296
+ if (((RubyObject) callback_object).respondsTo("on_body")) {
267
297
  ThreadContext context = callback_object.getRuntime().getCurrentContext();
268
- ret = callback_object.callMethod(context, "on_body", RubyString.newString(runtime, new ByteList(data, UTF8Encoding.INSTANCE, false)));
298
+ ret = callback_object.callMethod(context, "on_body",
299
+ RubyString.newString(runtime, new ByteList(data, UTF8, false)));
269
300
  }
270
301
  } else if (on_body != null) {
271
302
  ThreadContext context = on_body.getRuntime().getCurrentContext();
272
- ret = on_body.callMethod(context, "call", RubyString.newString(runtime, new ByteList(data, UTF8Encoding.INSTANCE, false)));
303
+ ret = on_body.callMethod(context, "call", RubyString.newString(runtime, new ByteList(data, UTF8, false)));
273
304
  }
274
305
 
275
306
  if (ret == stopSym) {
@@ -337,7 +368,7 @@ public class RubyHttpParser extends RubyObject {
337
368
 
338
369
  @JRubyMethod(name = "<<")
339
370
  public IRubyObject execute(IRubyObject data) {
340
- RubyString str = (RubyString)data;
371
+ RubyString str = (RubyString) data;
341
372
  ByteList byteList = str.getByteList();
342
373
  ByteBuffer buf = ByteBuffer.wrap(byteList.getUnsafeBytes(), byteList.getBegin(), byteList.getRealSize());
343
374
  boolean stopped = false;
@@ -352,8 +383,11 @@ public class RubyHttpParser extends RubyObject {
352
383
 
353
384
  if (parser.getUpgrade()) {
354
385
  byte[] upData = fetchBytes(buf, buf.position(), buf.limit() - buf.position());
355
- ((RubyString)upgradeData).cat(upData);
356
-
386
+ if (runtime.is1_9() || runtime.is2_0()) {
387
+ ((RubyString) upgradeData).cat(upData, 0, upData.length, UTF8);
388
+ } else {
389
+ ((RubyString) upgradeData).cat(upData);
390
+ }
357
391
  } else if (buf.hasRemaining() && !completed) {
358
392
  if (!stopped)
359
393
  throw new RaiseException(runtime, eParserError, "Could not parse data entirely", true);
@@ -447,7 +481,7 @@ public class RubyHttpParser extends RubyObject {
447
481
  @JRubyMethod(name = "header_value_type=")
448
482
  public IRubyObject set_header_value_type(IRubyObject val) {
449
483
  String valString = val.toString();
450
- if (valString != "mixed" && valString != "arrays" && valString != "strings") {
484
+ if (!VALUE_TYPES.contains(valString)) {
451
485
  throw runtime.newArgumentError("Invalid header value type");
452
486
  }
453
487
  header_value_type = val;
@@ -16,6 +16,7 @@
16
16
  typedef struct ParserWrapper {
17
17
  ryah_http_parser parser;
18
18
 
19
+ VALUE status;
19
20
  VALUE request_url;
20
21
 
21
22
  VALUE headers;
@@ -45,6 +46,7 @@ void ParserWrapper_init(ParserWrapper *wrapper) {
45
46
  wrapper->parser.http_major = 0;
46
47
  wrapper->parser.http_minor = 0;
47
48
 
49
+ wrapper->status = Qnil;
48
50
  wrapper->request_url = Qnil;
49
51
 
50
52
  wrapper->upgrade_data = Qnil;
@@ -59,6 +61,7 @@ void ParserWrapper_init(ParserWrapper *wrapper) {
59
61
  void ParserWrapper_mark(void *data) {
60
62
  if(data) {
61
63
  ParserWrapper *wrapper = (ParserWrapper *) data;
64
+ rb_gc_mark_maybe(wrapper->status);
62
65
  rb_gc_mark_maybe(wrapper->request_url);
63
66
  rb_gc_mark_maybe(wrapper->upgrade_data);
64
67
  rb_gc_mark_maybe(wrapper->headers);
@@ -101,6 +104,7 @@ static VALUE Smixed;
101
104
  int on_message_begin(ryah_http_parser *parser) {
102
105
  GET_WRAPPER(wrapper, parser);
103
106
 
107
+ wrapper->status = rb_str_new2("");
104
108
  wrapper->request_url = rb_str_new2("");
105
109
  wrapper->headers = rb_hash_new();
106
110
  wrapper->upgrade_data = rb_str_new2("");
@@ -121,9 +125,28 @@ int on_message_begin(ryah_http_parser *parser) {
121
125
  }
122
126
  }
123
127
 
128
+ int on_status(ryah_http_parser *parser, const char *at, size_t length) {
129
+ GET_WRAPPER(wrapper, parser);
130
+
131
+ if (at && length) {
132
+ if (wrapper->status == Qnil) {
133
+ wrapper->status = rb_str_new(at, length);
134
+ } else {
135
+ rb_str_cat(wrapper->status, at, length);
136
+ }
137
+ }
138
+ return 0;
139
+ }
140
+
124
141
  int on_url(ryah_http_parser *parser, const char *at, size_t length) {
125
142
  GET_WRAPPER(wrapper, parser);
126
- rb_str_cat(wrapper->request_url, at, length);
143
+ if (at && length) {
144
+ if (wrapper->request_url == Qnil) {
145
+ wrapper->request_url = rb_str_new(at, length);
146
+ } else {
147
+ rb_str_cat(wrapper->request_url, at, length);
148
+ }
149
+ }
127
150
  return 0;
128
151
  }
129
152
 
@@ -136,7 +159,6 @@ int on_header_field(ryah_http_parser *parser, const char *at, size_t length) {
136
159
  } else {
137
160
  rb_str_cat(wrapper->curr_field_name, at, length);
138
161
  }
139
-
140
162
  return 0;
141
163
  }
142
164
 
@@ -248,6 +270,7 @@ int on_message_complete(ryah_http_parser *parser) {
248
270
 
249
271
  static ryah_http_parser_settings settings = {
250
272
  .on_message_begin = on_message_begin,
273
+ .on_status = on_status,
251
274
  .on_url = on_url,
252
275
  .on_header_field = on_header_field,
253
276
  .on_header_value = on_header_value,
@@ -293,7 +316,17 @@ VALUE Parser_initialize(int argc, VALUE *argv, VALUE self) {
293
316
  ParserWrapper *wrapper = NULL;
294
317
  DATA_GET(self, ParserWrapper, wrapper);
295
318
 
296
- wrapper->header_value_type = rb_iv_get(CLASS_OF(self), "@default_header_value_type");
319
+ VALUE default_header_value_type = Qnil;
320
+
321
+ if (argc > 0 && RB_TYPE_P(argv[argc-1], T_HASH)) {
322
+ ID keyword_ids[1];
323
+ keyword_ids[0] = rb_intern("default_header_value_type");
324
+ rb_get_kwargs(argv[argc-1], keyword_ids, 0, 1, &default_header_value_type);
325
+ if (default_header_value_type == Qundef) {
326
+ default_header_value_type = Qnil;
327
+ }
328
+ --argc;
329
+ }
297
330
 
298
331
  if (argc == 1) {
299
332
  wrapper->callback_object = argv[0];
@@ -301,7 +334,13 @@ VALUE Parser_initialize(int argc, VALUE *argv, VALUE self) {
301
334
 
302
335
  if (argc == 2) {
303
336
  wrapper->callback_object = argv[0];
304
- wrapper->header_value_type = argv[1];
337
+ default_header_value_type = argv[1];
338
+ }
339
+
340
+ if (default_header_value_type == Qnil) {
341
+ wrapper->header_value_type = rb_iv_get(CLASS_OF(self), "@default_header_value_type");
342
+ } else {
343
+ wrapper->header_value_type = default_header_value_type;
305
344
  }
306
345
 
307
346
  return self;
@@ -320,11 +359,12 @@ VALUE Parser_execute(VALUE self, VALUE data) {
320
359
  size_t nparsed = ryah_http_parser_execute(&wrapper->parser, &settings, ptr, len);
321
360
 
322
361
  if (wrapper->parser.upgrade) {
323
- if (RTEST(wrapper->stopped))
362
+ if (RTEST(wrapper->stopped) && !RTEST(wrapper->completed))
324
363
  nparsed += 1;
325
364
 
326
- rb_str_cat(wrapper->upgrade_data, ptr + nparsed, len - nparsed);
327
-
365
+ if (nparsed < len)
366
+ rb_str_cat(wrapper->upgrade_data, ptr + nparsed, len - nparsed);
367
+
328
368
  } else if (nparsed != (size_t)len) {
329
369
  if (!RTEST(wrapper->stopped) && !RTEST(wrapper->completed))
330
370
  rb_raise(eParserError, "Could not parse data entirely (%zu != %zu)", nparsed, len);
@@ -438,6 +478,7 @@ VALUE Parser_status_code(VALUE self) {
438
478
  return wrapper->name; \
439
479
  }
440
480
 
481
+ DEFINE_GETTER(status);
441
482
  DEFINE_GETTER(request_url);
442
483
  DEFINE_GETTER(headers);
443
484
  DEFINE_GETTER(upgrade_data);
@@ -464,6 +505,10 @@ VALUE Parser_reset(VALUE self) {
464
505
  }
465
506
 
466
507
  void Init_ruby_http_parser() {
508
+ #ifdef HAVE_RB_EXT_RACTOR_SAFE
509
+ rb_ext_ractor_safe(true);
510
+ #endif
511
+
467
512
  VALUE mHTTP = rb_define_module("HTTP");
468
513
  cParser = rb_define_class_under(mHTTP, "Parser", rb_cObject);
469
514
  cRequestParser = rb_define_class_under(mHTTP, "RequestParser", cParser);
@@ -505,6 +550,7 @@ void Init_ruby_http_parser() {
505
550
  rb_define_method(cParser, "http_method", Parser_http_method, 0);
506
551
  rb_define_method(cParser, "status_code", Parser_status_code, 0);
507
552
 
553
+ rb_define_method(cParser, "status", Parser_status, 0);
508
554
  rb_define_method(cParser, "request_url", Parser_request_url, 0);
509
555
  rb_define_method(cParser, "headers", Parser_headers, 0);
510
556
  rb_define_method(cParser, "upgrade_data", Parser_upgrade_data, 0);