Class: Unicorn::HttpParser

Inherits:
Object
  • Object
show all
Defined in:
ext/unicorn_http/unicorn_http.c

Constant Summary collapse

CHUNK_MAX =

The maximum size a single chunk when using chunked transfer encoding. This is only a theoretical maximum used to detect errors in clients, it is highly unlikely to encounter clients that send more than several kilobytes at once.

OFFT2NUM(UH_OFF_T_MAX)
LENGTH_MAX =

The maximum size of the body as specified by Content-Length. This is only a theoretical maximum, the actual limit is subject to the limits of the file system used for Dir.tmpdir.

OFFT2NUM(UH_OFF_T_MAX)

Instance Method Summary collapse

Constructor Details

#newObject

Creates a new parser.



3195
3196
3197
3198
3199
3200
# File 'ext/unicorn_http/unicorn_http.c', line 3195

static VALUE HttpParser_init(VALUE self)
{
  http_parser_init(data_get(self));

  return self;
}

Instance Method Details

#body_eof?Boolean

Detects if we’re done filtering the body or not. This can be used to detect when to stop calling HttpParser#filter_body.

Returns:

  • (Boolean)


3306
3307
3308
3309
3310
3311
3312
3313
3314
# File 'ext/unicorn_http/unicorn_http.c', line 3306

static VALUE HttpParser_body_eof(VALUE self)
{
  struct http_parser *hp = data_get(self);

  if (HP_FL_TEST(hp, CHUNKED))
    return chunked_eof(hp) ? Qtrue : Qfalse;

  return hp->len.content == 0 ? Qtrue : Qfalse;
}

#content_lengthnil, Integer

Returns the number of bytes left to run through HttpParser#filter_body. This will initially be the value of the “Content-Length” HTTP header after header parsing is complete and will decrease in value as HttpParser#filter_body is called for each chunk. This should return zero for requests with no body.

This will return nil on “Transfer-Encoding: chunked” requests.

Returns:

  • (nil, Integer)


3244
3245
3246
3247
3248
3249
# File 'ext/unicorn_http/unicorn_http.c', line 3244

static VALUE HttpParser_content_length(VALUE self)
{
  struct http_parser *hp = data_get(self);

  return HP_FL_TEST(hp, CHUNKED) ? Qnil : OFFT2NUM(hp->len.content);
}

#filter_body(buf, data) ⇒ Object

Takes a String of data, will modify data if dechunking is done. Returns nil if there is more data left to process. Returns data if body processing is complete. When returning data, it may modify data so the start of the string points to where the body ended so that trailer processing can begin.

Raises HttpParserError if there are dechunking errors. Basically this is a glorified memcpy(3) that copies data into buf while filtering it through the dechunker.



3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
# File 'ext/unicorn_http/unicorn_http.c', line 3363

static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data)
{
  struct http_parser *hp = data_get(self);
  char *dptr;
  long dlen;

  rb_str_update(data);
  dptr = RSTRING_PTR(data);
  dlen = RSTRING_LEN(data);

  StringValue(buf);
  rb_str_resize(buf, dlen); /* we can never copy more than dlen bytes */
  OBJ_TAINT(buf); /* keep weirdo $SAFE users happy */

  if (HP_FL_TEST(hp, CHUNKED)) {
    if (!chunked_eof(hp)) {
      hp->s.dest_offset = 0;
      http_parser_execute(hp, buf, dptr, dlen);
      if (hp->cs == http_parser_error)
        rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");

      assert(hp->s.dest_offset <= hp->start.offset &&
             "destination buffer overflow");
      advance_str(data, hp->start.offset);
      rb_str_set_len(buf, hp->s.dest_offset);

      if (RSTRING_LEN(buf) == 0 && chunked_eof(hp)) {
        assert(hp->len.chunk == 0 && "chunk at EOF but more to parse");
      } else {
        data = Qnil;
      }
    }
  } else {
    /* no need to enter the Ragel machine for unchunked transfers */
    assert(hp->len.content >= 0 && "negative Content-Length");
    if (hp->len.content > 0) {
      long nr = MIN(dlen, hp->len.content);

      memcpy(RSTRING_PTR(buf), dptr, nr);
      hp->len.content -= nr;
      if (hp->len.content == 0)
        hp->cs = http_parser_first_final;
      advance_str(data, nr);
      rb_str_set_len(buf, nr);
      data = Qnil;
    }
  }
  hp->start.offset = 0; /* for trailer parsing */
  return data;
}

#headers(req, data) ⇒ nil

Takes a Hash and a String of data, parses the String of data filling in the Hash returning the Hash if parsing is finished, nil otherwise When returning the req Hash, it may modify data to point to where body processing should begin.

Raises HttpParserError if there are parsing errors.

Returns:

  • (nil)


3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
# File 'ext/unicorn_http/unicorn_http.c', line 3271

static VALUE HttpParser_headers(VALUE self, VALUE req, VALUE data)
{
  struct http_parser *hp = data_get(self);

  rb_str_update(data);

  http_parser_execute(hp, req, RSTRING_PTR(data), RSTRING_LEN(data));
  VALIDATE_MAX_LENGTH(hp->start.offset, HEADER);

  if (hp->cs == http_parser_first_final ||
      hp->cs == http_parser_en_ChunkedBody) {
    advance_str(data, hp->start.offset + 1);
    hp->start.offset = 0;

    return req;
  }

  if (hp->cs == http_parser_error)
    rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");

  return Qnil;
}

#headers?Boolean

This should be used to detect if a request has headers (and if the response will have headers as well). HTTP/0.9 requests should return false, all subsequent HTTP versions will return true

Returns:

  • (Boolean)


3342
3343
3344
3345
3346
3347
# File 'ext/unicorn_http/unicorn_http.c', line 3342

static VALUE HttpParser_has_headers(VALUE self)
{
  struct http_parser *hp = data_get(self);

  return HP_FL_TEST(hp, HASHEADER) ? Qtrue : Qfalse;
}

#keepalive?Boolean

This should be used to detect if a request can really handle keepalives and pipelining. Currently, the rules are:

  1. MUST be a GET or HEAD request

  2. MUST be HTTP/1.1 or HTTP/1.0 with “Connection: keep-alive”

  3. MUST NOT have “Connection: close” set

Returns:

  • (Boolean)


3327
3328
3329
3330
3331
3332
# File 'ext/unicorn_http/unicorn_http.c', line 3327

static VALUE HttpParser_keepalive(VALUE self)
{
  struct http_parser *hp = data_get(self);

  return HP_FL_ALL(hp, KEEPALIVE) ? Qtrue : Qfalse;
}

#resetnil

Resets the parser to it’s initial state so that you can reuse it rather than making new ones.

Returns:

  • (nil)


3209
3210
3211
3212
3213
3214
# File 'ext/unicorn_http/unicorn_http.c', line 3209

static VALUE HttpParser_reset(VALUE self)
{
  http_parser_init(data_get(self));

  return Qnil;
}

#headers(req, data) ⇒ nil

Takes a Hash and a String of data, parses the String of data filling in the Hash returning the Hash if parsing is finished, nil otherwise When returning the req Hash, it may modify data to point to where body processing should begin.

Raises HttpParserError if there are parsing errors.

Returns:

  • (nil)


3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
# File 'ext/unicorn_http/unicorn_http.c', line 3271

static VALUE HttpParser_headers(VALUE self, VALUE req, VALUE data)
{
  struct http_parser *hp = data_get(self);

  rb_str_update(data);

  http_parser_execute(hp, req, RSTRING_PTR(data), RSTRING_LEN(data));
  VALIDATE_MAX_LENGTH(hp->start.offset, HEADER);

  if (hp->cs == http_parser_first_final ||
      hp->cs == http_parser_en_ChunkedBody) {
    advance_str(data, hp->start.offset + 1);
    hp->start.offset = 0;

    return req;
  }

  if (hp->cs == http_parser_error)
    rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");

  return Qnil;
}