2 * test82: test HTTP with a remote server (is $USENETWORK="yes")
8 #define dbgprintf(...) do { \
9 fprintf(stderr, "[%s:%s:%d %d] ", \
10 __FILE__, __FUNCTION__, \
11 __LINE__, getpid()); \
12 fprintf(stderr, __VA_ARGS__); \
16 #define dbgprintf(...)
19 #include <arpa/inet.h>
22 #include <netinet/in.h>
27 #include <sys/socket.h>
32 #define CLOSE(fd) do { assert(fd >= 0); if (close((fd)) != 0) efmt("close failed"); } while (0);
33 #define REALLOC(p, size) do { p = realloc(p, size); if (!p) efmt("realloc of %zu bytes failed", size); } while (0);
35 #define HOST "test82.minix3.org"
37 #define PATH1 "/test1.txt"
38 #define PATH1_DATA "Hello world\n"
39 #define PATH2 "/test2.bin"
41 static void callback_verify_path1(const void *data
, size_t size
);
42 static void callback_verify_path2(const void *data
, size_t size
);
50 void (* callback_verify
)(const void *data
, size_t size
);
53 static const struct url urls
[URL_COUNT
] = {
54 { HOST
, PORT
, PATH1
, callback_verify_path1
},
55 { HOST
, PORT
, PATH2
, callback_verify_path2
},
58 static int http_connect(const char *host
, int port
) {
59 struct addrinfo
*addr
= NULL
;
61 struct addrinfo hints
= {
63 .ai_socktype
= SOCK_STREAM
,
69 snprintf(serv
, sizeof(serv
), "%d", port
);
72 if (getaddrinfo(host
, serv
, &hints
, &addr
) != 0 || !addr
) {
73 efmt("host %s not found", host
);
77 fd
= socket(AF_INET
, SOCK_STREAM
, 0);
79 efmt("cannot create socket");
83 if (connect(fd
, addr
->ai_addr
, addr
->ai_addrlen
) != 0) {
84 efmt("cannot connect to %s:%d", host
, port
);
92 if (fd
>= 0) CLOSE(fd
);
93 if (addr
) freeaddrinfo(addr
);
97 static void write_chunked(
107 assert(chunksize
> 0);
111 if (s
> size
) s
= size
;
114 r
= write(fd
, data
, s
);
115 if (r
<= 0 || (size_t) r
> s
) {
117 efmt("write of %zu bytes failed with result %zd", s
, r
);
126 static void http_send_request(
139 assert(chunksize
> 0);
141 /* http://tools.ietf.org/html/rfc2616#section-5 */
142 len
= snprintf(buf
, sizeof(buf
),
143 "GET %s HTTP/1.1\r\n"
148 while (len
+ 24 < sizeof(buf
)) {
149 len
+= snprintf(buf
+ len
, sizeof(buf
) - len
,
150 "X-Padding%d: %d\r\n",
155 len
+= snprintf(buf
+ len
, sizeof(buf
) - len
, "\r\n");
157 dbgprintf("sending request:\n%.*s", (int) len
, buf
);
158 write_chunked(fd
, buf
, len
, chunksize
);
161 static int is_whitespace(char c
) {
162 return c
== ' ' || c
== '\t';
165 static int is_whitespace_or_linebreak(char c
) {
166 return is_whitespace(c
) || c
== '\r' || c
== '\n';
169 static int is_numeric(char c
) {
170 return c
>= '0' && c
<= '9';
173 static int http_get_header_line(
184 assert(*index_p
<= len
);
187 /* starting the next line with whitespace means the line is continued */
190 while (index
< len
&& data
[index
] != '\n') index
++;
191 if (index
>= len
) goto notfound
;
193 } while (index
< len
&& is_whitespace(data
[index
]));
195 /* exclude LF or CR+LF from line length */
196 assert(index
- 1 >= *index_p
&& data
[index
- 1] == '\n');
197 has_cr
= (index
- 2 >= *index_p
) && data
[index
- 2] == '\r';
198 linelen
= index
- *index_p
- (has_cr
? 2 : 1);
200 /* if LF is the last character in the buffer, the line may be continued
201 * when more data is retrieved unless we reached the end of the headers
203 if (index
>= len
&& linelen
> 0) goto notfound
;
205 *linelen_p
= linelen
;
215 static int http_get_status_line(
226 assert(*index_p
<= len
);
228 assert(*error_p
== 0);
231 /* skip leading whitespace/blank lines */
233 while (index
< len
&& is_whitespace_or_linebreak(data
[index
])) index
++;
236 while (index
< len
&& !is_whitespace(data
[index
])) index
++;
239 while (index
< len
&& is_whitespace(data
[index
])) index
++;
241 /* parse status code */
243 for (i
= 0; i
< 3; i
++) {
244 if (index
>= len
) goto notfound
;
245 if (!is_numeric(data
[index
])) {
247 efmt("HTTP error: bad status line: \"%.*s\"",
248 (int) (index
- *index_p
), data
+ *index_p
);
252 code
= code
* 10 + (data
[index
++] - '0');
256 while (index
< len
&& is_whitespace(data
[index
])) index
++;
258 /* parse reason phrase */
259 while (index
< len
&& data
[index
] != '\n') index
++;
260 if (index
>= len
) goto notfound
;
273 static int http_header_is(
278 size_t *index_value_p
) {
282 assert(index
<= len
);
284 assert(index_value_p
);
286 namelen
= strlen(name
);
287 if (index
+ namelen
> len
) goto notfound
;
288 if (strncasecmp(data
+ index
, name
, namelen
) != 0) goto notfound
;
290 while (index
< len
&& is_whitespace(data
[index
])) index
++;
291 if (index
>= len
|| data
[index
] != ':') goto notfound
;
294 while (index
< len
&& is_whitespace(data
[index
])) index
++;
295 *index_value_p
= index
;
303 static int http_parse_int_header(
312 assert(index
<= index_end
);
317 while (index
< index_end
&& is_numeric(data
[index
])) {
318 value
= value
* 10 + (data
[index
++] - '0');
321 while (index
< index_end
&& is_whitespace_or_linebreak(data
[index
])) {
325 if (index
< index_end
) {
327 efmt("HTTP error: bad numeric header value: \"%.*s\"",
328 (int) (index_end
- index
), data
+ index
);
337 static int http_response_complete(
342 size_t *index_body_p
) {
343 int content_length
= -1;
344 size_t index
= 0, index_line
;
352 assert(index_body_p
);
354 /* parse status line */
355 if (!http_get_status_line(data
, len
, &index
, error_p
, code_p
)) {
362 if (!http_get_header_line(data
, len
, &index
, &linelen
)) {
365 if (linelen
== 0) break;
366 if (http_header_is(data
, len
, index_line
,
367 "Content-Length", &index_value
)) {
368 if (!http_parse_int_header(data
, index_value
,
369 index_line
+ linelen
, &content_length
,
376 /* do we know how long the response will be? */
377 if (content_length
< 0) {
379 efmt("HTTP error: missing Content-Length header "
380 "(maybe Transfer-Encoding is specified instead "
381 "but this is currently unsupported)");
385 /* check whether the amount of data is correct */
386 if (len
> index
+ content_length
) {
388 efmt("HTTP error: more data received than expected");
392 *index_body_p
= index
;
393 return len
== index
+ content_length
;
402 static void http_recv_response(
404 void (* callback_verify
)(const void *data
, size_t size
),
408 size_t datalen
= 0, datasize
= 0;
414 assert(callback_verify
);
415 assert(chunksize
> 0);
419 /* make room for another chunk in the buffer if needed */
420 if (datasize
< datalen
+ chunksize
) {
421 datasize
= (datalen
+ chunksize
) * 2;
422 REALLOC(data
, datasize
);
425 /* read a chunk of data */
427 r
= read(fd
, data
+ datalen
, chunksize
);
428 if (r
< 0 || (size_t) r
> chunksize
) {
429 efmt("read of %zu bytes failed with result %zd",
435 /* if we received all headers+data, we are done */
436 if (http_response_complete(data
, datalen
, &error
, &code
,
440 if (error
) goto cleanup
;
442 /* check for premature disconnection */
445 efmt("server disconnected even though the response "
446 "seems to be incomplete");
451 dbgprintf("received response:\n%.*s", (int) datalen
, data
);
453 assert(index_body
<= datalen
);
455 callback_verify(data
+ index_body
, datalen
- index_body
);
458 efmt("unexpected HTTP status code %d", code
);
462 if (data
) free(data
);
465 static void http_test(
466 const struct url
*url
,
474 assert(chunksize
> 0);
476 dbgprintf("attempting download from http://%s:%d%s, "
477 "chunksize=%zu, bigrequest=%d, delay=%d, withshutdown=%d\n",
478 url
->host
, url
->port
, url
->path
, chunksize
, bigrequest
,
479 delay
, withshutdown
);
481 fd
= http_connect(url
->host
, url
->port
);
484 http_send_request(fd
, url
->host
, url
->path
, chunksize
, bigrequest
);
487 if (withshutdown
&& shutdown(fd
, SHUT_WR
) != 0) {
488 efmt("shutdown failed");
492 http_recv_response(fd
, url
->callback_verify
, chunksize
);
496 dbgprintf("download attempt completed\n");
499 static int child_count
;
501 static void http_test_fork(
502 const struct url
*url
,
511 assert(chunksize
> 0);
532 assert(errct
>= errctold
);
533 exit(errct
- errctold
);
536 static void wait_all(void) {
537 int exitcode
, status
;
540 while (child_count
> 0) {
542 pid
= waitpid(-1, &status
, 0);
544 efmt("waitpid failed");
547 if (WIFEXITED(status
)) {
548 exitcode
= WEXITSTATUS(status
);
549 dbgprintf("child %d completed with exit code %d\n",
550 (int) pid
, exitcode
);
554 efmt("child has negative exit code %d",
557 } else if (WIFSIGNALED(status
)) {
558 dbgprintf("child %d killed by signal %d\n",
559 (int) pid
, WTERMSIG(status
));
560 efmt("child killed by signal %d", WTERMSIG(status
));
562 dbgprintf("child %d gone with status 0x%x\n",
564 efmt("child gone, but neither exit nor signal");
570 if (waitpid(-1, &status
, 0) != -1 || errno
!= ECHILD
) {
571 efmt("waitpid should have returned ECHILD");
575 #define OPTION_BIGREQUEST (1 << 0)
576 #define OPTION_DELAY (1 << 1)
577 #define OPTION_SHUTDOWN (1 << 2)
579 static void http_test_all(int multiproc
) {
580 static const size_t chunksizes
[] = { 1, 1024, 65536 };
581 static const int optionsets
[] = {
586 OPTION_BIGREQUEST
| OPTION_DELAY
| OPTION_SHUTDOWN
,
593 for (urlindex
= 0; urlindex
< URL_COUNT
; urlindex
++) {
594 for (chunksizeindex
= 0; chunksizeindex
< 3; chunksizeindex
++) {
595 for (optionindex
= 0; optionindex
< 3; optionindex
++) {
596 options
= optionsets
[optionindex
];
597 (multiproc
? http_test_fork
: http_test
)(
599 chunksizes
[chunksizeindex
],
600 options
& OPTION_BIGREQUEST
,
601 options
& OPTION_DELAY
,
602 options
& OPTION_SHUTDOWN
);
610 static void verify_data(
611 const void *httpdata
, size_t httpsize
,
612 const void *refdata
, size_t refsize
,
619 if (httpsize
!= refsize
) {
621 efmt("download from http://%s:%d%s returned wrong number "
622 "of bytes: %zd (expected %zd)",
623 HOST
, PORT
, path
, httpsize
, refsize
);
624 } else if (memcmp(httpdata
, refdata
, refsize
) != 0) {
626 efmt("download from http://%s:%d%s returned wrong data",
631 static void callback_verify_path1(const void *data
, size_t size
) {
632 verify_data(data
, size
, PATH1_DATA
, strlen(PATH1_DATA
), PATH1
);
635 static void callback_verify_path2(const void *data
, size_t size
) {
636 unsigned short buf
[65536];
639 for (i
= 0; i
< 65536; i
++) buf
[i
] = htons(i
);
641 verify_data(data
, size
, buf
, sizeof(buf
), PATH2
);
644 int main(int argc
, char **argv
)
650 use_network
= get_setting_use_network();
652 http_test_all(0 /* multiproc */);
653 http_test_all(1 /* multiproc */);
655 dbgprintf("test disabled, set USENETWORK=yes to enable\n");