} ngx_js_http_keepalive_cache_t;
+enum {
+ NGX_JS_HTTP_BODY_NONE = 0,
+ NGX_JS_HTTP_BODY_LENGTH,
+ NGX_JS_HTTP_BODY_CHUNKED,
+ NGX_JS_HTTP_BODY_CLOSE,
+};
+
+
#define ngx_js_http_version(major, minor) ((major) * 1000 + (minor))
#define NGX_JS_USER_AGENT "nginx-js"
}
if (rc == NGX_DONE) {
- break;
+ return;
}
continue;
if (len == (sizeof("Content-Length") - 1)
&& ngx_strncasecmp(hp->header_name_start,
- (u_char *) "Content-Length", len) == 0)
+ (u_char *) "Content-Length", len) == 0)
{
content_length_n = ngx_atoof(hp->header_start, vlen);
if (content_length_n == NGX_ERROR) {
http->content_length_n = content_length_n;
if (!http->header_only
+ && http->response.code != 204
+ && http->response.code != 304
&& http->content_length_n
> (off_t) http->max_response_body_size)
{
return NGX_ERROR;
}
+ if (http->response.code >= 100 && http->response.code < 200) {
+ ngx_js_http_error(http, "unsupported http response status");
+ return NGX_ERROR;
+ }
+
njs_chb_destroy(&http->chain);
+ if (http->header_only || http->response.code == 204
+ || http->response.code == 304)
+ {
+ http->body = NGX_JS_HTTP_BODY_NONE;
+
+ } else if (http->chunked) {
+ http->body = NGX_JS_HTTP_BODY_CHUNKED;
+
+ } else if (http->content_length) {
+ http->body = NGX_JS_HTTP_BODY_LENGTH;
+
+ } else {
+ http->body = NGX_JS_HTTP_BODY_CLOSE;
+ }
+
http->process = ngx_js_http_process_body;
return http->process(http);
return NGX_ERROR;
}
- if (!http->header_only
- && http->chunked
- && http->content_length_n == -1)
+ if (http->body == NGX_JS_HTTP_BODY_NONE
+ || http->body == NGX_JS_HTTP_BODY_CLOSE)
{
+ http->ready_handler(http);
+ return NGX_DONE;
+ }
+
+ if (http->body == NGX_JS_HTTP_BODY_CHUNKED) {
ngx_js_http_error(http, "invalid http chunked response");
return NGX_ERROR;
}
- if (http->header_only
- || http->content_length_n == -1
- || size == http->content_length_n)
- {
+ if (size == http->content_length_n) {
http->ready_handler(http);
return NGX_DONE;
}
b = http->buffer;
- if (http->chunked) {
+ if (http->body == NGX_JS_HTTP_BODY_NONE) {
+ http->ready_handler(http);
+ return NGX_DONE;
+ }
+
+ if (http->body == NGX_JS_HTTP_BODY_CHUNKED) {
rc = ngx_js_http_parse_chunked(&http->http_chunk_parse, b,
&http->response.chain);
if (rc == NGX_ERROR) {
}
size = njs_chb_size(&http->response.chain);
+ if (size < 0) {
+ ngx_js_http_error(http, "memory error");
+ return NGX_ERROR;
+ }
if (rc == NGX_OK) {
- http->content_length_n = size;
+ b->pos = http->http_chunk_parse.pos;
+
+ http->ready_handler(http);
+ return NGX_DONE;
}
if (size > http->max_response_body_size * 10) {
} else {
size = njs_chb_size(&http->response.chain);
+ if (size < 0) {
+ ngx_js_http_error(http, "memory error");
+ return NGX_ERROR;
+ }
- if (http->header_only) {
- need = 0;
-
- } else if (http->content_length_n == -1) {
+ if (http->body == NGX_JS_HTTP_BODY_CLOSE) {
need = http->max_response_body_size - size;
+ if (b->last - b->pos > need) {
+ ngx_js_http_error(http, "http response body is too large");
+ return NGX_ERROR;
+ }
+
+ chsize = b->last - b->pos;
+
} else {
need = http->content_length_n - size;
+ chsize = ngx_min(need, b->last - b->pos);
}
- chsize = ngx_min(need, b->last - b->pos);
-
- if (size + chsize > http->max_response_body_size) {
+ if (need < 0) {
ngx_js_http_error(http, "http response body is too large");
return NGX_ERROR;
}
b->pos += chsize;
}
- rc = (need > chsize) ? NGX_AGAIN : NGX_DONE;
+ if (http->body == NGX_JS_HTTP_BODY_CLOSE) {
+ rc = NGX_AGAIN;
+
+ } else {
+ rc = (need > chsize) ? NGX_AGAIN : NGX_DONE;
+
+ if (rc == NGX_DONE) {
+ http->ready_handler(http);
+ return NGX_DONE;
+ }
+ }
}
if (b->pos == b->end) {
unsigned content_length:1;
unsigned chunked:1;
unsigned keepalive:1;
+ unsigned body:3;
off_t content_length_n;
#if (NGX_SSL)
$t->try_run('no njs.fetch');
-$t->plan(49);
+$t->plan(54);
$t->run_daemon(\&http_daemon, port(8082));
$t->waitforsocket('127.0.0.1:' . port(8082));
like(http_get('/framing?loc=transfer_content_length'),
qr/501.*conflicting http response framing/s,
'fetch transfer and content length');
+like(http_get('/body_special?loc=no_body/204'), qr/200 OK.*<empty>$/s,
+ 'fetch 204 response body');
+like(http_get('/body_special?loc=no_body/304'), qr/200 OK.*<empty>$/s,
+ 'fetch 304 response body');
+like(http_get('/framing?loc=continue'),
+ qr/501.*unsupported http response status/s,
+ 'fetch interim response unsupported');
+like(http_get('/framing?loc=no_body/304_large'), qr/200 OK.*ok:0$/s,
+ 'fetch 304 response large content length');
+like(http_get('/framing?loc=length'),
+ qr/501.*prematurely closed connection/s,
+ 'fetch incomplete fixed-length response');
like(http_get('/header_iter?loc=duplicate_header_large'),
qr/\["A:a","B:a","C:a","D:a","E:a","F:a","G:a","H:a","Moo:a, ?b"]$/s,
'fetch header duplicate large');
"Connection: close" . CRLF .
CRLF;
+ } elsif ($uri eq '/continue') {
+ print $client
+ "HTTP/1.1 100 Continue" . CRLF .
+ "Content-Length: 0" . CRLF .
+ CRLF;
+
} elsif ($uri eq '/content_length_conflict') {
print $client
"HTTP/1.1 200 OK" . CRLF .
"Connection: close" . CRLF .
CRLF;
+ } elsif ($uri eq '/no_body/204' || $uri eq '/no_body/304'
+ || $uri eq '/no_body/304_large')
+ {
+ my $code = ($uri eq '/no_body/204') ? 204 : 304;
+ my $length = ($uri eq '/no_body/304_large') ? 1000000 : 5;
+ print $client
+ "HTTP/1.1 $code No Content" . CRLF .
+ "Content-Length: $length" . CRLF .
+ "Connection: close" . CRLF .
+ CRLF;
+
} elsif ($uri eq '/parted') {
print $client
"HTTP/1.1 200 OK" . CRLF .
js_content test.keepalive;
}
+ location /keepalive_head {
+ js_fetch_keepalive 4;
+ js_content test.keepalive_head;
+ }
+
location /no_keepalive {
js_fetch_keepalive 0;
js_content test.keepalive;
r.return(200, JSON.stringify(responses));
}
- export default {engine, keepalive, keepalive_simultaneous};
+ async function keepalive_head(r) {
+ let responses = [];
+
+ for (let i = 0; i < 3; i++) {
+ let reply = await ngx.fetch('http://127.0.0.1:$p1/count',
+ {method: 'HEAD'});
+ responses.push(reply.headers.get('Connection-ID'));
+ }
+
+ r.return(200, responses.toString());
+ }
+
+ export default {engine, keepalive, keepalive_head, keepalive_simultaneous};
EOF
$t->try_run('no js_fetch_keepalive');
$t->run_daemon(\&http_daemon, $p2);
$t->waitforsocket('127.0.0.1:' . $p2);
-$t->plan(16);
+$t->plan(17);
###############################################################################
'keepalive reuses connection');
like(http_get('/keepalive?path=count'), qr/4,5,6/,
'keepalive reuses connection across requests');
+like(http_get('/keepalive_head'), qr/1,2,3/,
+ 'keepalive reuses head response connection');
like(http_get('/keepalive_simultaneous?n=8'), qr/1,1,1,1,1,1,1,1/,
'keepalive simultaneous requests');
like(http_get('/keepalive_simultaneous?n=8'), qr/2,2,2,2,1,1,1,1/,