Skip to content

Commit eec19e8

Browse files
committed
Add http2 support
fixes fdintino#82, fdintino#87, fdintino#92
1 parent 3481680 commit eec19e8

File tree

4 files changed

+299
-16
lines changed

4 files changed

+299
-16
lines changed

.travis.yml

+51-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
sudo: false
1+
sudo: required
22
language: c
33
compiler: gcc
44
dist: trusty
@@ -8,28 +8,68 @@ cache:
88
- perl5
99
- dl
1010

11+
addons:
12+
apt:
13+
packages:
14+
- libssl-dev
15+
1116
env:
1217
global:
1318
- TESTNGINX_VER=12152a5
14-
- PATH=$TRAVIS_BUILD_DIR/bin:$TRAVIS_BUILD_DIR/nginx/objs:$PATH
19+
- PATH=/usr/local/bin:$TRAVIS_BUILD_DIR/nginx/objs:$PATH
20+
- CURL=7.58.0
21+
- NGHTTP2=1.24.0
1522
matrix:
1623
- NGINX_VERSION=1.9.15
1724
- NGINX_VERSION=1.11.13
1825
- NGINX_VERSION=1.12.2
1926
- NGINX_VERSION=1.13.8
2027

2128
before_install:
22-
- curl --version
23-
- mkdir -p {bin,dl}
24-
- if [ ! -f dl/nginx-${NGINX_VERSION}.tar.gz ]; then curl -o dl/nginx-${NGINX_VERSION}.tar.gz -L http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz; fi
25-
- if [ ! -f dl/test-nginx-${TESTNGINX_VER}.tar.gz ]; then curl -o dl/test-nginx-${TESTNGINX_VER}.tar.gz -L "https://github.com/openresty/test-nginx/archive/${TESTNGINX_VER}.tar.gz"; fi
29+
- mkdir -p dl
30+
- |
31+
if [ ! -f dl/nghttp2-${NGHTTP2}.tar.gz ]; then
32+
(cd dl && curl -O -L https://github.com/nghttp2/nghttp2/releases/download/v${NGHTTP2}/nghttp2-${NGHTTP2}.tar.gz)
33+
fi
34+
- |
35+
if [ ! -f dl/curl-${CURL}.tar.gz ]; then
36+
(cd dl && curl -O -L https://curl.haxx.se/download/curl-${CURL}.tar.gz)
37+
fi
38+
- |
39+
if [ ! -f dl/nginx-${NGINX_VERSION}.tar.gz ]; then
40+
curl -o dl/nginx-${NGINX_VERSION}.tar.gz -L http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz;
41+
fi
42+
- |
43+
if [ ! -f dl/test-nginx-${TESTNGINX_VER}.tar.gz ]; then
44+
curl -o dl/test-nginx-${TESTNGINX_VER}.tar.gz -L "https://github.com/openresty/test-nginx/archive/${TESTNGINX_VER}.tar.gz";
45+
fi
2646
- if [ ! -f dl/cpanm ]; then curl -o dl/cpanm https://cpanmin.us/; chmod +x dl/cpanm; fi
27-
- cp dl/cpanm bin/cpanm
28-
- tar -zxvf dl/nginx-${NGINX_VERSION}.tar.gz && mv nginx-${NGINX_VERSION} nginx
47+
- |
48+
if [ ! -e dl/nghttp2-${NGHTTP2} ]; then
49+
(cd dl && tar -zxf nghttp2-${NGHTTP2}.tar.gz)
50+
fi
51+
(cd dl/nghttp2-${NGHTTP2} &&
52+
./configure --prefix=/usr --disable-threads &&
53+
make && sudo make install)
54+
- |
55+
if [ ! -e dl/curl-${CURL} ]; then
56+
(cd dl && tar -zxf curl-${CURL}.tar.gz)
57+
fi
58+
(cd dl/curl-${CURL} && ./configure --with-nghttp2 --prefix=/usr/local && make && sudo make install)
59+
- sudo ldconfig
60+
- sudo cp dl/cpanm /usr/local/bin/cpanm
61+
- tar -zxf dl/nginx-${NGINX_VERSION}.tar.gz && mv nginx-${NGINX_VERSION} nginx
2962
- cpanm --notest --local-lib=perl5 local::lib && eval $(perl -I perl5/lib/perl5/ -Mlocal::lib)
30-
- cpanm --notest dl/test-nginx-${TESTNGINX_VER}.tar.gz
31-
- cpanm --notest Test::File
32-
- cpanm --notest URI::Query
63+
- |
64+
if [ ! -f perl5/lib/perl5/Test/Nginx.pm ]; then
65+
cpanm --notest dl/test-nginx-${TESTNGINX_VER}.tar.gz
66+
fi
67+
- |
68+
if [ ! -f perl5/lib/perl5/Test/File.pm ]; then
69+
cpanm --notest Test::File
70+
fi
71+
72+
install:
3373
- cd nginx
3474
- ./configure --with-http_v2_module --with-http_ssl_module --add-module=..
3575
- make

ngx_http_upload_module.c

+115-5
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,15 @@ typedef ngx_md5_t MD5_CTX;
5959
#define FIELDNAME_STRING "name=\""
6060
#define BYTES_UNIT_STRING "bytes "
6161

62-
#define NGX_UPLOAD_MALFORMED -1
63-
#define NGX_UPLOAD_NOMEM -2
64-
#define NGX_UPLOAD_IOERROR -3
65-
#define NGX_UPLOAD_SCRIPTERROR -4
66-
#define NGX_UPLOAD_TOOLARGE -5
62+
#define NGX_UPLOAD_MALFORMED -11
63+
#define NGX_UPLOAD_NOMEM -12
64+
#define NGX_UPLOAD_IOERROR -13
65+
#define NGX_UPLOAD_SCRIPTERROR -14
66+
#define NGX_UPLOAD_TOOLARGE -15
67+
68+
#ifndef NGX_HTTP_V2
69+
#define NGX_HTTP_V2 0
70+
#endif
6771

6872
/*
6973
* State of multipart/form-data parser
@@ -287,6 +291,9 @@ typedef struct ngx_http_upload_ctx_s {
287291

288292
static ngx_int_t ngx_http_upload_test_expect(ngx_http_request_t *r);
289293

294+
#if (NGX_HTTP_V2)
295+
static void ngx_http_upload_read_event_handler(ngx_http_request_t *r);
296+
#endif
290297
static ngx_int_t ngx_http_upload_handler(ngx_http_request_t *r);
291298
static ngx_int_t ngx_http_upload_options_handler(ngx_http_request_t *r);
292299
static ngx_int_t ngx_http_upload_body_handler(ngx_http_request_t *r);
@@ -896,6 +903,21 @@ ngx_http_upload_handler(ngx_http_request_t *r)
896903
if(upload_start(u, ulcf) != NGX_OK)
897904
return NGX_HTTP_INTERNAL_SERVER_ERROR;
898905

906+
#if (NGX_HTTP_V2)
907+
if (r->stream) {
908+
r->request_body_no_buffering = 1;
909+
910+
rc = ngx_http_read_client_request_body(r, ngx_http_upload_read_event_handler);
911+
912+
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
913+
upload_shutdown_ctx(u);
914+
return rc;
915+
}
916+
917+
return NGX_DONE;
918+
}
919+
#endif
920+
899921
rc = ngx_http_read_upload_client_request_body(r);
900922

901923
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
@@ -905,6 +927,94 @@ ngx_http_upload_handler(ngx_http_request_t *r)
905927
return NGX_DONE;
906928
} /* }}} */
907929

930+
#if (NGX_HTTP_V2)
931+
static void
932+
ngx_http_upload_read_event_handler(ngx_http_request_t *r)
933+
{
934+
ngx_http_upload_ctx_t *u;
935+
ngx_http_request_body_t *rb;
936+
ngx_int_t rc;
937+
ngx_chain_t *in;
938+
939+
if (ngx_exiting || ngx_terminate) {
940+
ngx_http_finalize_request(r, NGX_HTTP_CLOSE);
941+
return;
942+
}
943+
944+
rb = r->request_body;
945+
946+
if (rb == NULL) {
947+
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
948+
return;
949+
}
950+
951+
u = ngx_http_get_module_ctx(r, ngx_http_upload_module);
952+
953+
rc = NGX_OK;
954+
955+
in = rb->bufs;
956+
957+
for ( ;; ) {
958+
while (in) {
959+
rc = u->data_handler(u, in->buf->pos, in->buf->last);
960+
if (rc != NGX_OK) {
961+
goto err;
962+
}
963+
in->buf->pos = in->buf->last;
964+
in = in->next;
965+
}
966+
967+
// We're done reading the request body, break out of loop
968+
if (!r->reading_body) {
969+
rc = u->data_handler(u, NULL, NULL);
970+
if (rc == NGX_OK) {
971+
break;
972+
} else {
973+
goto err;
974+
}
975+
}
976+
977+
rc = ngx_http_read_unbuffered_request_body(r);
978+
979+
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
980+
goto err;
981+
}
982+
983+
in = rb->bufs;
984+
rb->bufs = NULL;
985+
986+
if (in == NULL) {
987+
r->read_event_handler = ngx_http_upload_read_event_handler;
988+
return;
989+
}
990+
}
991+
992+
// Finally, send the response
993+
rc = ngx_http_upload_body_handler(r);
994+
995+
err:
996+
switch(rc) {
997+
case NGX_UPLOAD_MALFORMED:
998+
rc = NGX_HTTP_BAD_REQUEST;
999+
break;
1000+
case NGX_UPLOAD_TOOLARGE:
1001+
rc = NGX_HTTP_REQUEST_ENTITY_TOO_LARGE;
1002+
break;
1003+
case NGX_UPLOAD_IOERROR:
1004+
rc = NGX_HTTP_SERVICE_UNAVAILABLE;
1005+
break;
1006+
case NGX_UPLOAD_NOMEM:
1007+
case NGX_UPLOAD_SCRIPTERROR:
1008+
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
1009+
break;
1010+
}
1011+
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
1012+
upload_shutdown_ctx(u);
1013+
ngx_http_finalize_request(r, rc);
1014+
}
1015+
}
1016+
#endif
1017+
9081018
static ngx_int_t ngx_http_upload_add_headers(ngx_http_request_t *r, ngx_http_upload_loc_conf_t *ulcf) { /* {{{ */
9091019
ngx_str_t name;
9101020
ngx_str_t value;

t/http2.t

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use strict;
2+
use warnings;
3+
4+
use File::Basename qw(dirname);
5+
use lib dirname(__FILE__) . "/lib";
6+
use Cwd qw(abs_path);
7+
8+
use Test::Nginx::Socket tests => 11;
9+
use Test::Nginx::UploadModule;
10+
11+
$ENV{TEST_DIR} = abs_path(dirname(__FILE__));
12+
13+
14+
our $config = <<'_EOC_';
15+
location = /upload/ {
16+
upload_pass @upstream;
17+
upload_resumable on;
18+
19+
upload_set_form_field upload_file_name $upload_file_name;
20+
upload_set_form_field upload_file_number $upload_file_number;
21+
upload_set_form_field "upload_field_name" "$upload_field_name";
22+
upload_set_form_field "upload_content_type" "$upload_content_type";
23+
upload_set_form_field "upload_tmp_path" "$upload_tmp_path";
24+
upload_set_form_field "upload_content_range" "$upload_content_range";
25+
upload_aggregate_form_field "upload_file_size" "$upload_file_size";
26+
upload_max_file_size 0;
27+
upload_pass_args on;
28+
upload_cleanup 400 404 499 500-505;
29+
}
30+
_EOC_
31+
32+
no_long_string();
33+
no_shuffle();
34+
run_tests();
35+
36+
__DATA__
37+
=== TEST 1: http2 simple upload
38+
--- config eval: $::config
39+
--- http2
40+
--- skip_nginx
41+
3: < 1.10.0
42+
--- more_headers
43+
X-Content-Range: bytes 0-3/4
44+
Session-ID: 0000000001
45+
Content-Type: text/plain
46+
Content-Disposition: form-data; name="file"; filename="test.txt"
47+
--- request eval
48+
qq{POST /upload/
49+
test}
50+
--- error_code: 200
51+
--- response_body eval
52+
qq{upload_content_range = bytes 0-3/4
53+
upload_content_type = text/plain
54+
upload_field_name = file
55+
upload_file_name = test.txt
56+
upload_file_number = 1
57+
upload_file_size = 4
58+
upload_tmp_path = $ENV{TEST_NGINX_UPLOAD_PATH}/store/1/0000000001
59+
}
60+
--- upload_file eval
61+
"test"
62+
63+
=== TEST 2: http2 multiple chunk uploads
64+
--- http_config eval: $::http_config
65+
--- config eval: $::config
66+
--- http2
67+
--- skip_nginx
68+
3: < 1.10.0
69+
--- more_headers eval
70+
[qq{X-Content-Range: bytes 0-1/4
71+
Session-ID: 0000000002
72+
Content-Type: text/plain
73+
Content-Disposition: form-data; name="file"; filename="test.txt"},
74+
qq{X-Content-Range: bytes 2-3/4
75+
Session-ID: 0000000002
76+
Content-Type: text/plain
77+
Content-Disposition: form-data; name="file"; filename="test.txt"}]
78+
--- request eval
79+
[["POST /upload/\r\n",
80+
"te"],
81+
["POST /upload/\r\n",
82+
"st"]]
83+
--- error_code eval
84+
[201, 200]
85+
--- response_body eval
86+
["0-1/4", qq{upload_content_range = bytes 2-3/4
87+
upload_content_type = text/plain
88+
upload_field_name = file
89+
upload_file_name = test.txt
90+
upload_file_number = 1
91+
upload_file_size = 4
92+
upload_tmp_path = $ENV{TEST_NGINX_UPLOAD_PATH}/store/2/0000000002
93+
}]
94+
--- upload_file eval
95+
"test"
96+
97+
=== Test 3: http2 large multiple chunk uploads
98+
--- http_config eval: $::http_config
99+
--- skip_nginx
100+
5: < 1.10.0
101+
--- http2
102+
--- config eval: $::config
103+
--- more_headers eval
104+
[qq{X-Content-Range: bytes 0-131071/262144
105+
Session-ID: 0000000003
106+
Content-Type: text/plain
107+
Content-Disposition: form-data; name="file"; filename="test.txt"},
108+
qq{X-Content-Range: bytes 131072-262143/262144
109+
Session-ID: 0000000003
110+
Content-Type: text/plain
111+
Content-Disposition: form-data; name="file"; filename="test.txt"}]
112+
--- request eval
113+
[["POST /upload/\r\n",
114+
"@" . $ENV{TEST_NGINX_UPLOAD_FILE}],
115+
["POST /upload/\r\n",
116+
"@" . $ENV{TEST_NGINX_UPLOAD_FILE}]]
117+
--- error_code eval
118+
[201, 200]
119+
--- response_body eval
120+
["0-131071/262144", qq{upload_content_range = bytes 131072-262143/262144
121+
upload_content_type = text/plain
122+
upload_field_name = file
123+
upload_file_name = test.txt
124+
upload_file_number = 1
125+
upload_file_size = 262144
126+
upload_tmp_path = ${ENV{TEST_NGINX_UPLOAD_PATH}}/store/3/0000000003
127+
}]
128+
--- upload_file_like eval
129+
qr/^(??{'x' x 262144})$/

t/lib/Test/Nginx/UploadModule.pm

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use warnings;
55

66
my $PORT = $ENV{TEST_NGINX_UPSTREAM_PORT} ||= 12345;
77
$ENV{TEST_NGINX_UPLOAD_PATH} ||= '/tmp/upload';
8+
$ENV{TEST_NGINX_UPLOAD_FILE} = $ENV{TEST_NGINX_UPLOAD_PATH} . "/test_data.txt";
89

910

1011
use base 'Exporter';
@@ -32,6 +33,9 @@ sub make_upload_paths {
3233
for (my $i = 0; $i < 10; $i++) {
3334
mkpath("${ENV{TEST_NGINX_UPLOAD_PATH}}/store/$i");
3435
}
36+
open(my $fh, ">", $ENV{TEST_NGINX_UPLOAD_FILE});
37+
print $fh ('x' x 131072);
38+
close($fh);
3539
}
3640

3741
add_cleanup_handler(sub {

0 commit comments

Comments
 (0)