http_parser.rb 0.6.0.beta.1 → 0.8.0

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.
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);