1 #include <sys/ioctl.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <sys/socket.h>
5 #include <netdb.h>
6 #include <errno.h>
7 #include <string.h>
8
9 #include "types.h"
10 #include "socket_io.h"
11 #include "http_util.h"
12 #include "xs_util.h"
13 #include "regression_data.h"
14
15 /* --------------------------------------------------------- */
16
17 /* make an fd non blocking */
18
19 static void
nonblock(int fd)20 nonblock(int fd) {
21 int i = 1;
22 #ifdef BEOS
23 setsockopt(fd, SOL_SOCKET, SO_NONBLOCK, &i, sizeof(i));
24 #else
25 ioctl(fd, FIONBIO, &i);
26 #endif
27 }
28
29 /* --------------------------------------------------------- */
30
31 /* start asnchronous non-blocking connection */
32
33 static void
start_connect(struct global * registry,struct connection * c)34 start_connect(struct global * registry, struct connection * c) {
35 c->read = 0;
36 c->bread = 0;
37 c->keepalive = 0;
38 c->cbx = 0;
39 c->gotheader = 0;
40 c->fd = socket(AF_INET, SOCK_STREAM, 0);
41
42 #ifdef AB_DEBUG
43 printf("AB_DEBUG: start of start_connect()\n");
44 #endif
45
46 if (c->fd < 0) {
47 myerr(registry->warn_and_error, "socket error");
48 registry->failed[c->url]++;
49 close_connection(registry, c);
50 return;
51 }
52 nonblock(c->fd);
53
54 #ifdef AB_DEBUG
55 printf("AB_DEBUG: start_connect() - stage 1\n");
56 #endif
57
58 c->connect_time.tv_sec = 0;
59 c->connect_time.tv_usec = 0;
60 c->sent_request_time.tv_sec = 0;
61 c->sent_request_time.tv_usec = 0;
62 gettimeofday(&c->start_time, 0);
63
64 {
65 /* get server information */
66 struct hostent *he;
67 #ifdef AB_DEBUG
68 printf("AB_DEBUG: start_connect() - stage 2, c->url: '%d'\n", c->url);
69 #endif
70 he = gethostbyname(registry->hostname[c->url]);
71 #ifdef AB_DEBUG
72 printf("AB_DEBUG: start_connect() - stage 3\n");
73 #endif
74 if (!he) {
75 char * warn = malloc(256 * sizeof(char));
76 sprintf(warn, "Bad hostname: %s, the information stored for it could be wrong!", registry->hostname[c->url]);
77 myerr(registry->warn_and_error, warn);
78 free(warn);
79 /* bad hostname, yields the resource */
80 registry->failed[c->url]++;
81 close_connection(registry, c);
82 return;
83 }
84 #ifdef AB_DEBUG
85 printf("AB_DEBUG: start_connect() - stage 4\n");
86 #endif
87 registry->server.sin_family = he->h_addrtype;
88 registry->server.sin_port = htons(registry->port[c->url]);
89 registry->server.sin_addr.s_addr = ((unsigned long *) (he->h_addr_list[0]))[0];
90 }
91
92 #ifdef AB_DEBUG
93 printf("AB_DEBUG: start_connect() - stage 5\n");
94 #endif
95
96 if (connect(c->fd, (struct sockaddr *) & registry->server, sizeof(registry->server)) < 0) {
97 if (errno == EINPROGRESS) {
98 FD_SET(c->fd, ®istry->writebits);
99 registry->started[c->url]++;
100 return;
101 } else {
102 ab_close(c->fd);
103 /* retry the same request 10 times if it fails to connect */
104 if (registry->failed[c->url]++ > 10) {
105 myerr(registry->warn_and_error,
106 "Test aborted after 10 failures");
107 /* yields the resource */
108 close_connection(registry, c);
109 return;
110 }
111 start_connect(registry, c);
112 return;
113 }
114 }
115
116 #ifdef AB_DEBUG
117 printf("AB_DEBUG: start_connect() - stage 6\n");
118 #endif
119
120 /* connected first time */
121 registry->started[c->url]++;
122 FD_SET(c->fd, ®istry->writebits);
123 }
124
125 /* --------------------------------------------------------- */
126
127 /* read data from connection */
128
129 static void
read_connection(struct global * registry,struct connection * c)130 read_connection(struct global * registry, struct connection * c) {
131 int r;
132
133 #ifdef AB_DEBUG
134 printf("AB_DEBUG: start of read_connection(), postdata[%d] = %s\n", c->url, registry->postdata[c->url]);
135 #endif
136
137 r = ab_read(c->fd, registry->buffer, sizeof(registry->buffer));
138 if (r == 0 || (r < 0 && errno != EAGAIN)) {
139 if (errno == EINPROGRESS)
140 registry->good[c->url]++;
141 close_connection(registry, c);
142 return;
143 }
144
145 #ifdef AB_DEBUG
146 printf("AB_DEBUG: read_connection() - stage 1\n");
147 #endif
148
149 if (r < 0 && errno == EAGAIN)
150 return;
151 c->read += r;
152 if (c->read < registry->buffersize[c->run]-1 &&
153 registry->memory[c->run] >= 3)
154 strncat(c->response, registry->buffer, r);
155
156 #ifdef AB_DEBUG
157 printf("AB_DEBUG: read_connection() - stage 2\n");
158 #endif
159
160 if (!c->gotheader) {
161 char *s;
162 int wslen = 4;
163 int space = CBUFFSIZE - c->cbx - 1; /* -1 to allow for 0
164 * terminator */
165 int tocopy = (space < r) ? space : r;
166 #ifndef CHARSET_EBCDIC
167 memcpy(c->cbuff + c->cbx, registry->buffer, tocopy);
168 #else /* CHARSET_EBCDIC */
169 ascii2ebcdic(c->cbuff + c->cbx, registry->buffer, tocopy);
170 #endif /* CHARSET_EBCDIC */
171 c->cbx += tocopy;
172 space -= tocopy;
173 c->cbuff[c->cbx] = 0; /* terminate for benefit of strstr */
174 s = strstr(c->cbuff, "\r\n\r\n");
175 /*
176 * this next line is so that we talk to NCSA 1.5 which blatantly
177 * breaks the http specification
178 */
179 if (!s) {
180 s = strstr(c->cbuff, "\n\n");
181 wslen = 2;
182 }
183 if (!s) {
184 /* read rest next time */
185 if (registry->memory[c->run] >= 2)
186 c->response_headers = "";
187 if (space)
188 return;
189 else {
190 /*
191 * header is in invalid or too big - close connection
192 */
193 ab_close(c->fd);
194 FD_CLR(c->fd, ®istry->writebits);
195 start_connect(registry, c);
196 }
197 } else {
198 /* have full header */
199
200 /*
201 * XXX: this parsing isn't even remotely HTTP compliant... but in
202 * the interest of speed it doesn't totally have to be, it just
203 * needs to be extended to handle whatever servers folks want to
204 * test against. -djg
205 */
206
207 c->gotheader = 1;
208 *s = 0; /* terminate at end of header */
209 if (registry->memory[c->run] >= 2) {
210 c->response_headers = malloc(CBUFFSIZE * sizeof(char));
211 strcpy(c->response_headers, c->cbuff);
212 }
213 if (registry->keepalive[c->url] &&
214 (strstr(c->cbuff, "Keep-Alive") ||
215 strstr(c->cbuff, "keep-alive"))) { /* for benefit of MSIIS */
216 char *cl;
217 cl = strstr(c->cbuff, "Content-Length:");
218 /* handle NCSA, which sends Content-length: */
219 if (!cl)
220 cl = strstr(c->cbuff, "Content-length:");
221 if (cl) {
222 c->keepalive = 1;
223 c->length = atoi(cl + 16);
224 }
225 }
226 c->bread += c->cbx - (s + wslen - c->cbuff) + r - tocopy;
227 }
228 } else {
229 /* outside header, everything we have read is entity body */
230 c->bread += r;
231 }
232
233 /*
234 * cater for the case where we're using keepalives and doing HEAD
235 * requests
236 */
237 if (c->keepalive &&
238 ((c->bread >= c->length) || (registry->posting[c->url] < 0))) {
239 /* save current url for checking for hostname/port changes */
240 int prev = c->url;
241
242 /* finished a keep-alive connection */
243 registry->good[c->url]++;
244 registry->finished[c->url]++;
245
246 store_regression_data(registry, c);
247
248 if (++registry->done >= registry->need_to_be_done)
249 return;
250
251 if (!schedule_next_request(registry, c))
252 return;
253
254 c->length = 0;
255 c->gotheader = 0;
256 c->cbx = 0;
257 c->read = c->bread = 0;
258 c->keepalive = 0;
259
260 /* if new hostname/port is different from last hostname/port, or new
261 url is *not* keepalive, then we need to close connection and start
262 a new connection */
263 if (registry->keepalive[c->url] &&
264 strcmp(registry->hostname[c->url], registry->hostname[prev]) == 0
265 && registry->port[c->url] == registry->port[prev]) {
266 write_request(registry, c);
267 registry->started[c->url]++;
268 c->start_time = c->connect_time; /* zero connect time with keep-alive */
269 } else {
270 ab_close(c->fd);
271 FD_CLR(c->fd, ®istry->readbits);
272 FD_CLR(c->fd, ®istry->writebits);
273 start_connect(registry, c);
274 }
275 }
276 }
277
278 /* --------------------------------------------------------- */
279
280 /* close down connection and save stats */
281
282 static void
close_connection(struct global * registry,struct connection * c)283 close_connection(struct global * registry, struct connection * c) {
284 #ifdef AB_DEBUG
285 printf("AB_DEBUG: start of close_connection(), postdata[%d] = %s\n", c->url, registry->postdata[c->url]);
286 #endif
287
288 if (registry->use_auto_cookies[c->run])
289 extract_cookies_from_response(registry, c);
290 store_regression_data(registry, c);
291 registry->finished[c->url]++;
292
293 #ifdef AB_DEBUG
294 printf("AB_DEBUG: close_connection() - stage 1\n");
295 #endif
296
297 ab_close(c->fd);
298 FD_CLR(c->fd, ®istry->readbits);
299 FD_CLR(c->fd, ®istry->writebits);
300
301 #ifdef AB_DEBUG
302 printf("AB_DEBUG: close_connection() - stage 2\n");
303 #endif
304
305 /* finish if last response has been received */
306 if (++registry->done >= registry->need_to_be_done)
307 return;
308
309 #ifdef AB_DEBUG
310 printf("AB_DEBUG: close_connection() - stage 3\n");
311 #endif
312
313 /* else continue with requests in run queues */
314 if (schedule_next_request(registry, c))
315 start_connect(registry, c);
316 }
317
318
319 /* --------------------------------------------------------- */
320
321 /* write out request to a connection - assumes we can write
322 (small) request out in one go into our new socket buffer */
323
324 static void
write_request(struct global * registry,struct connection * c)325 write_request(struct global * registry, struct connection * c) {
326
327 #ifndef NO_WRITEV
328 struct iovec out[2];
329 int outcnt = 1;
330 #endif
331 int bytes_sent;
332 STRLEN len;
333 SV *res;
334 char *post_body;
335
336 #ifdef AB_DEBUG
337 printf("AB_DEBUG: write_request() - stage 1, registry->done = %d\n", registry->done);
338 #endif
339 gettimeofday(&c->before_postdata_time, 0);
340
341 /* the url in this run has dynamicly-generated postdata */
342 if (registry->posting[c->url] == 2) {
343 res = call_perl_function__one_arg(registry->postsubs[c->url],
344 newSVpv(c->url > 0 ? registry->stats[c->url - 1][c->thread].response : "", 0));
345
346 if (SvPOK(res)) {
347 post_body = SvPV(res, len);
348 #ifdef AB_DEBUG
349 printf("AB_DEBUG: write_request() - stage 1-postsub.2, postsub res %s, length %d\n", post_body, (int)len);
350 #endif
351 registry->postdata[c->url] = post_body;
352 registry->postlen[c->url] = (int) len;
353 } else {
354 registry->postdata[c->url] = "";
355 registry->postlen[c->url] = 0;
356 registry->posting[c->url] = 0; // change back to a GET request
357 }
358 }
359
360 gettimeofday(&c->connect_time, 0); // start timer
361
362 reset_request(registry, c); // this generates the request headers; must call the above first to determine POST content
363
364 #ifdef AB_DEBUG
365 printf("AB_DEBUG: write_request() - stage 2, registry->done = %d\n", registry->done);
366 #endif
367
368 #ifndef NO_WRITEV
369 out[0].iov_base = c->request;
370 out[0].iov_len = c->reqlen;
371
372 #ifdef AB_DEBUG
373 printf("AB_DEBUG: write_request() - stage 2a.1, registry->done = %d, postdata[%d] = %s\n", registry->done, c->url, registry->postdata[c->url]);
374 #endif
375 if (registry->posting[c->url] > 0) {
376 out[1].iov_base = registry->postdata[c->url];
377 out[1].iov_len = registry->postlen[c->url];
378 outcnt = 2;
379 registry->totalposted[c->url] = (c->reqlen + registry->postlen[c->url]);
380 }
381 #ifdef AB_DEBUG
382 printf("AB_DEBUG: write_request() - stage 2a.2, registry->done = %d\n", registry->done);
383 #endif
384 bytes_sent = writev(c->fd, out, outcnt);
385
386 #else /* NO_WRITEV */
387
388 #ifdef AB_DEBUG
389 printf("AB_DEBUG: write_request() - stage 2b.1, registry->done = %d, postdata[%d] = %s\n", registry->done, c->url, registry->postdata[c->url]);
390 #endif
391 ab_write(c->fd, c->request, c->reqlen);
392 if (registry->posting[c->url] > 0)
393 bytes_sent = ab_write(c->fd, registry->postdata[c->url], registry->postlen[c->url]);
394 #endif /* NO_WRITEV */
395
396 #ifdef AB_DEBUG
397 printf("AB_DEBUG: write_request() - stage 3, registry->done = %d, postdata[%d] = %s\n", registry->done, c->url, registry->postdata[c->url]);
398 #endif
399
400 FD_SET(c->fd, ®istry->readbits);
401 FD_CLR(c->fd, ®istry->writebits);
402 gettimeofday(&c->sent_request_time, 0);
403
404 if (registry->memory[c->run] >= 3)
405 c->response = calloc(1, registry->buffersize[c->run]);
406 }
407
408 /* --------------------------------------------------------- */
409
410 /* setup or reset request */
411 static int
reset_request(struct global * registry,struct connection * c)412 reset_request(struct global * registry, struct connection * c) {
413 int i = c->url;
414
415 char * ctype = calloc(40, sizeof(char));
416 strcpy(ctype, "application/x-www-form-urlencoded");
417
418 #ifdef AB_DEBUG
419 printf("AB_DEBUG: reset_request() - stage 0.1\n");
420 #endif
421 if (registry->ctypes[i]) {
422 #ifdef AB_DEBUG
423 printf("AB_DEBUG: reset_request() - stage 0.1.1\n");
424 #endif
425 free(ctype);
426
427 #ifdef AB_DEBUG
428 printf("AB_DEBUG: reset_request() - stage 0.1.2\n");
429 #endif
430 ctype = registry->ctypes[i];
431 }
432
433 #ifdef AB_DEBUG
434 printf("AB_DEBUG: reset_request() - stage 1\n");
435 #endif
436
437 c->request = calloc(registry->buffersize[c->run], sizeof(char));
438 c->request_headers = calloc(registry->buffersize[c->run], sizeof(char));
439
440 if (registry->posting[i] <= 0) {
441 #ifdef AB_DEBUG
442 printf("AB_DEBUG: reset_request() - stage 1.1 (GET)\n");
443 #endif
444 sprintf(c->request_headers, "%s %s HTTP/1.0\r\n"
445 "User-Agent: ApacheBench-Perl/%s\r\n"
446 "Host: %s\r\n"
447 "Accept: */*\r\n",
448 (registry->posting[i] == 0) ? "GET" : "HEAD",
449 registry->path[i],
450 registry->version,
451 registry->hostname[i]);
452 } else {
453 #ifdef AB_DEBUG
454 printf("AB_DEBUG: reset_request() - stage 1.1 (POST)\n");
455 #endif
456 sprintf(c->request_headers, "POST %s HTTP/1.0\r\n"
457 "User-Agent: ApacheBench-Perl/%s\r\n"
458 "Host: %s\r\n"
459 "Accept: */*\r\n"
460 "Content-length: %d\r\n"
461 "Content-type: %s\r\n",
462 registry->path[i],
463 registry->version,
464 registry->hostname[i],
465 registry->postlen[i],
466 ctype);
467 }
468
469 #ifdef AB_DEBUG
470 printf("AB_DEBUG: reset_request() - stage 2\n");
471 #endif
472
473 if (registry->keepalive[i])
474 strcat(c->request_headers, "Connection: Keep-Alive\r\n");
475 if (registry->cookie[c->run]) {
476 strcat(c->request_headers, "Cookie: ");
477 strcat(c->request_headers, registry->cookie[c->run]);
478 strcat(c->request_headers, "\r\n");
479 }
480
481 #ifdef AB_DEBUG
482 printf("AB_DEBUG: reset_request() - stage 2.1: c->run %d; c->thread %d\n", c->run, c->thread);
483 #endif
484
485 allocate_auto_cookie_memory(registry, c);
486
487 if (registry->use_auto_cookies[c->run] && registry->auto_cookies[c->run] != NULL && registry->auto_cookies[c->run][c->thread] != NULL) {
488
489 #ifdef AB_DEBUG
490 printf("AB_DEBUG: reset_request() - stage 2.2a: request_headers %s\n", c->request_headers);
491 printf("AB_DEBUG: reset_request() - stage 2.2b: auto_cookies %s\n", registry->auto_cookies[c->run][c->thread]);
492 #endif
493
494 strcat(c->request_headers, registry->auto_cookies[c->run][c->thread]);
495 }
496
497 #ifdef AB_DEBUG
498 printf("AB_DEBUG: reset_request() - stage 2.3: c->run %d; c->thread %d\n", c->run, c->thread);
499 #endif
500
501 if (registry->req_headers[i]) {
502 strcat(c->request_headers, registry->req_headers[i]);
503 strcat(c->request_headers, "\r\n");
504 }
505
506 strcat(c->request_headers, "\r\n");
507
508 #ifdef AB_DEBUG
509 printf("AB_DEBUG: reset_request() - stage 2.4: c->run %d; c->thread %d\n", c->run, c->thread);
510 #endif
511
512 strcpy(c->request, c->request_headers);
513 c->reqlen = strlen(c->request);
514
515 #ifdef AB_DEBUG
516 printf("AB_DEBUG: reset_request() - stage 3\n");
517 #endif
518
519 #ifdef CHARSET_EBCDIC
520 ebcdic2ascii(c->request, c->request, c->reqlen);
521 #endif /* CHARSET_EBCDIC */
522
523 return 0;
524 }
525
526 /* --------------------------------------------------------- */
527
528 /* setup the next request in the sequence / repetition / run to be sent
529 returns 1 if the next request is ready to be sent,
530 returns 0 if this connection is done,
531 sets the connection values: c->run, c->url, c->thread, and c->state,
532 as well as helper structures: registry->which_thread[][],
533 registry->ready_to_run_queue[], and registry->arranged[]
534 */
535
536 static int
schedule_next_request(struct global * registry,struct connection * c)537 schedule_next_request(struct global * registry, struct connection * c) {
538
539 if (registry->priority == RUN_PRIORITY) {
540 /* if the last url in this run has repeated enough, go to next run */
541 if (registry->started[registry->position[c->run + 1] - 1] >= registry->repeats[c->run])
542 return schedule_request_in_next_run(registry, c);
543
544 /* possible more resources needed in this group */
545 /* started[position[c->run + 1] - 1] < repeats[c->run] */
546 if (registry->order[c->run] == DEPTH_FIRST) {
547 /* for depth_first, connect the next one and restart the
548 sequence if we're at the last url in the run */
549 if (++c->url == registry->position[c->run + 1]) {
550 c->url = registry->position[c->run];
551 c->thread = registry->started[c->url];
552 }
553 return 1;
554 } else { /* breadth_first */
555 if (c->url < (registry->position[c->run + 1] - 1))
556 /* TODO: check if (registry->finished[c->url] > 0) ??? */
557 registry->which_thread[c->url+1][registry->finished[c->url] - 1] = c->thread;
558 if (registry->started[c->url] == registry->repeats[c->run])
559 /* go to next url in sequence if we repeated this one enough */
560 c->url++;
561 if (c->url == registry->position[c->run]) {
562 /* this is the first url in the sequence: set its repetition
563 number to the initial incremental value (0, 1, 2, 3, ...) */
564 c->thread = registry->which_thread[c->url][registry->started[c->url]];
565 return 1;
566 }
567 /* only start another request from this run if more requests of the
568 previous url in the sequence have finished(in-order execution)*/
569 if (registry->started[c->url] < registry->finished[c->url - 1]) {
570 c->thread = registry->started[c->url];
571 return 1;
572 } else {
573 return schedule_request_in_next_run(registry, c);
574 }
575 }
576
577 } else { /* equal_opportunity */
578 /* we use a FIFO to queue up requests to be sent */
579 if (c->url < registry->position[c->run + 1]-1) {
580 /* if url is before the end of the url sequence,
581 add it to the tail of the request queue */
582 registry->ready_to_run_queue[registry->tail].url = c->url + 1;
583 registry->ready_to_run_queue[registry->tail].thread = c->thread;
584 registry->ready_to_run_queue[registry->tail++].run = c->run;
585 registry->arranged[c->url + 1]++;
586 } else if (registry->order[c->run] == DEPTH_FIRST
587 && registry->arranged[registry->position[c->run]] < registry->repeats[c->run]) {
588 /* end of the url sequence in depth_first with more repetitions
589 necessary: start from the beginning of the url sequence */
590 registry->ready_to_run_queue[registry->tail].url = registry->position[c->run];
591 registry->ready_to_run_queue[registry->tail].thread = registry->arranged[registry->position[c->run]]++;
592 registry->ready_to_run_queue[registry->tail++].run = c->run;
593 }
594
595 if (registry->head >= registry->tail) {
596 c->state = STATE_DONE;
597 return 0;
598 }
599 c->thread = registry->ready_to_run_queue[registry->head].thread;
600 c->url = registry->ready_to_run_queue[registry->head].url;
601 c->run = registry->ready_to_run_queue[registry->head++].run;
602 return 1;
603 }
604 }
605
606 /* --------------------------------------------------------- */
607
608 /* move connection to the next run, because the current run either doesn't need
609 or cannot use any more connection slots (resources)
610 returns 1 if the next request is ready to be sent,
611 returns 0 if this connection is done */
612
613 static int
schedule_request_in_next_run(struct global * registry,struct connection * c)614 schedule_request_in_next_run(struct global * registry, struct connection * c) {
615 c->run++;
616 while (c->run < registry->number_of_runs) {
617 if (registry->started[registry->position[c->run + 1] - 1] >= registry->repeats[c->run]
618 || (registry->order[c->run] == DEPTH_FIRST
619 && registry->started[registry->position[c->run]] > 0)) {
620 /* this run has finished all repetitions of url requests
621 or is a depth_first run which only requires one slot,
622 so doesn't need this resource anymore */
623 c->run++;
624 continue;
625 }
626 /* start at first url in the run */
627 c->url = registry->position[c->run];
628 if (registry->started[c->url] < registry->repeats[c->run]) {
629 /* for breadth_first, start one more connect to 1st url if possible
630 for depth_first, get started here */
631 c->thread = registry->which_thread[c->url][registry->started[c->url]];
632 return 1;
633 }
634 /* look at each url in the sequence until we find one which needs
635 to be repeated more */
636 while (++c->url < registry->position[c->run + 1]
637 && registry->started[c->url] >= registry->repeats[c->run]);
638 /* only start another request from this run if more requests of the
639 previous url in the sequence have finished (in-order execution) */
640 if (registry->started[c->url] < registry->finished[c->url - 1]) {
641 c->thread = registry->which_thread[c->url][registry->started[c->url]];
642 return 1;
643 } else
644 /* this run doesn't need any more resources */
645 c->run++;
646 }
647 /* no one needs any more resources */
648 c->state = STATE_DONE;
649 return 0;
650 }
651
652