src/client.c - weighttp

Functions defined

Source code

  1. /*
  2. * weighttp - a lightweight and simple webserver benchmarking tool
  3. *
  4. * Author:
  5. *     Copyright (c) 2009-2011 Thomas Porzelt
  6. *
  7. * License:
  8. *     MIT, see COPYING file
  9. */

  10. #include "weighttp.h"

  11. static uint8_t client_parse(Client *client, int size);
  12. static void client_io_cb(struct ev_loop *loop, ev_io *w, int revents);
  13. static void client_set_events(Client *client, int events);
  14. /*
  15. static void client_add_events(Client *client, int events);
  16. static void client_rem_events(Client *client, int events);

  17. static void client_add_events(Client *client, int events) {
  18.     struct ev_loop *loop = client->worker->loop;
  19.     ev_io *watcher = &client->sock_watcher;

  20.     if ((watcher->events & events) == events)
  21.         return;

  22.     ev_io_stop(loop, watcher);
  23.     ev_io_set(watcher, watcher->fd, watcher->events | events);
  24.     ev_io_start(loop, watcher);
  25. }

  26. static void client_rem_events(Client *client, int events) {
  27.     struct ev_loop *loop = client->worker->loop;
  28.     ev_io *watcher = &client->sock_watcher;

  29.     if (0 == (watcher->events & events))
  30.         return;

  31.     ev_io_stop(loop, watcher);
  32.     ev_io_set(watcher, watcher->fd, watcher->events & ~events);
  33.     ev_io_start(loop, watcher);
  34. }
  35. */

  36. static void client_set_events(Client *client, int events) {
  37.     struct ev_loop *loop = client->worker->loop;
  38.     ev_io *watcher = &client->sock_watcher;

  39.     if (events == (watcher->events & (EV_READ | EV_WRITE)))
  40.         return;

  41.     ev_io_stop(loop, watcher);
  42.     ev_io_set(watcher, watcher->fd, (watcher->events & ~(EV_READ | EV_WRITE)) | events);
  43.     ev_io_start(loop, watcher);
  44. }

  45. Client *client_new(Worker *worker) {
  46.     Client *client;

  47.     client = W_MALLOC(Client, 1);
  48.     client->state = CLIENT_START;
  49.     client->worker = worker;
  50.     client->sock_watcher.fd = -1;
  51.     client->sock_watcher.data = client;
  52.     client->content_length = -1;
  53.     client->buffer_offset = 0;
  54.     client->request_offset = 0;
  55.     client->keepalive = client->worker->config->keep_alive;
  56.     client->chunked = 0;
  57.     client->chunk_size = -1;
  58.     client->chunk_received = 0;

  59.     return client;
  60. }

  61. void client_free(Client *client) {
  62.     if (client->sock_watcher.fd != -1) {
  63.         ev_io_stop(client->worker->loop, &client->sock_watcher);
  64.         shutdown(client->sock_watcher.fd, SHUT_WR);
  65.         close(client->sock_watcher.fd);
  66.     }

  67.     free(client);
  68. }

  69. static void client_reset(Client *client) {
  70.     //printf("keep alive: %d\n", client->keepalive);
  71.     if (!client->keepalive) {
  72.         if (client->sock_watcher.fd != -1) {
  73.             ev_io_stop(client->worker->loop, &client->sock_watcher);
  74.             shutdown(client->sock_watcher.fd, SHUT_WR);
  75.             close(client->sock_watcher.fd);
  76.             client->sock_watcher.fd = -1;
  77.         }

  78.         client->state = CLIENT_START;
  79.     } else {
  80.         client_set_events(client, EV_WRITE);
  81.         client->state = CLIENT_WRITING;
  82.         client->worker->stats.req_started++;
  83.     }

  84.     client->parser_state = PARSER_START;
  85.     client->buffer_offset = 0;
  86.     client->parser_offset = 0;
  87.     client->request_offset = 0;
  88.     client->ts_start = 0;
  89.     client->ts_end = 0;
  90.     client->status_success = 0;
  91.     client->success = 0;
  92.     client->content_length = -1;
  93.     client->bytes_received = 0;
  94.     client->header_size = 0;
  95.     client->keepalive = client->worker->config->keep_alive;
  96.     client->chunked = 0;
  97.     client->chunk_size = -1;
  98.     client->chunk_received = 0;
  99. }

  100. static uint8_t client_connect(Client *client) {
  101.     //printf("connecting...\n");
  102.     start:

  103.     if (-1 == connect(client->sock_watcher.fd, client->worker->config->saddr->ai_addr, client->worker->config->saddr->ai_addrlen)) {
  104.         switch (errno) {
  105.             case EINPROGRESS:
  106.             case EALREADY:
  107.                 /* async connect now in progress */
  108.                 client->state = CLIENT_CONNECTING;
  109.                 return 1;
  110.             case EISCONN:
  111.                 break;
  112.             case EINTR:
  113.                 goto start;
  114.             default:
  115.             {
  116.                 strerror_r(errno, client->buffer, sizeof(client->buffer));
  117.                 W_ERROR("connect() failed: %s (%d)", client->buffer, errno);
  118.                 return 0;
  119.             }
  120.         }
  121.     }

  122.     /* successfully connected */
  123.     client->state = CLIENT_WRITING;
  124.     return 1;
  125. }

  126. static void client_io_cb(struct ev_loop *loop, ev_io *w, int revents) {
  127.     Client *client = w->data;

  128.     UNUSED(loop);
  129.     UNUSED(revents);

  130.     client_state_machine(client);
  131. }

  132. void client_state_machine(Client *client) {
  133.     int r;
  134.     Config *config = client->worker->config;

  135.     start:
  136.     //printf("state: %d\n", client->state);
  137.     switch (client->state) {
  138.         case CLIENT_START:
  139.             client->worker->stats.req_started++;

  140.             do {
  141.                 r = socket(config->saddr->ai_family, config->saddr->ai_socktype, config->saddr->ai_protocol);
  142.             } while (-1 == r && errno == EINTR);

  143.             if (-1 == r) {
  144.                 client->state = CLIENT_ERROR;
  145.                 strerror_r(errno, client->buffer, sizeof(client->buffer));
  146.                 W_ERROR("socket() failed: %s (%d)", client->buffer, errno);
  147.                 goto start;
  148.             }

  149.             /* set non-blocking */
  150.             fcntl(r, F_SETFL, O_NONBLOCK | O_RDWR);

  151.             ev_init(&client->sock_watcher, client_io_cb);
  152.             ev_io_set(&client->sock_watcher, r, EV_WRITE);
  153.             ev_io_start(client->worker->loop, &client->sock_watcher);

  154.             if (!client_connect(client)) {
  155.                 client->state = CLIENT_ERROR;
  156.                 goto start;
  157.             } else {
  158.                 client_set_events(client, EV_WRITE);
  159.                 return;
  160.             }
  161.         case CLIENT_CONNECTING:
  162.             if (!client_connect(client)) {
  163.                 client->state = CLIENT_ERROR;
  164.                 goto start;
  165.             }
  166.         case CLIENT_WRITING:
  167.             while (1) {
  168.                 r = write(client->sock_watcher.fd, &config->request[client->request_offset], config->request_size - client->request_offset);
  169.                 //printf("write(%d - %d = %d): %d\n", config->request_size, client->request_offset, config->request_size - client->request_offset, r);
  170.                 if (r == -1) {
  171.                     /* error */
  172.                     if (errno == EINTR)
  173.                         continue;
  174.                     strerror_r(errno, client->buffer, sizeof(client->buffer));
  175.                     W_ERROR("write() failed: %s (%d)", client->buffer, errno);
  176.                     client->state = CLIENT_ERROR;
  177.                     goto start;
  178.                 } else if (r != 0) {
  179.                     /* success */
  180.                     client->request_offset += r;
  181.                     if (client->request_offset == config->request_size) {
  182.                         /* whole request was sent, start reading */
  183.                         client->state = CLIENT_READING;
  184.                         client_set_events(client, EV_READ);
  185.                     }

  186.                     return;
  187.                 } else {
  188.                     /* disconnect */
  189.                     client->state = CLIENT_END;
  190.                     goto start;
  191.                 }
  192.             }
  193.         case CLIENT_READING:
  194.             while (1) {
  195.                 r = read(client->sock_watcher.fd, &client->buffer[client->buffer_offset], sizeof(client->buffer) - client->buffer_offset - 1);
  196.                 //printf("read(): %d, offset was: %d\n", r, client->buffer_offset);
  197.                 if (r == -1) {
  198.                     /* error */
  199.                     if (errno == EINTR)
  200.                         continue;
  201.                     strerror_r(errno, client->buffer, sizeof(client->buffer));
  202.                     W_ERROR("read() failed: %s (%d)", client->buffer, errno);
  203.                     client->state = CLIENT_ERROR;
  204.                 } else if (r != 0) {
  205.                     /* success */
  206.                     client->bytes_received += r;
  207.                     client->buffer_offset += r;
  208.                     client->worker->stats.bytes_total += r;

  209.                     if (client->buffer_offset >= sizeof(client->buffer)) {
  210.                         /* too big response header */
  211.                         client->state = CLIENT_ERROR;
  212.                         break;
  213.                     }
  214.                     client->buffer[client->buffer_offset] = '\0';
  215.                     //printf("buffer:\n==========\n%s\n==========\n", client->buffer);
  216.                     if (!client_parse(client, r)) {
  217.                         client->state = CLIENT_ERROR;
  218.                         //printf("parser failed\n");
  219.                         break;
  220.                     } else {
  221.                         if (client->state == CLIENT_END)
  222.                             goto start;
  223.                         else
  224.                             return;
  225.                     }
  226.                 } else {
  227.                     /* disconnect */
  228.                     if (client->parser_state == PARSER_BODY && !client->keepalive && client->status_success
  229.                         && !client->chunked && client->content_length == -1) {
  230.                         client->success = 1;
  231.                         client->state = CLIENT_END;
  232.                     } else {
  233.                         client->state = CLIENT_ERROR;
  234.                     }

  235.                     goto start;
  236.                 }
  237.             }

  238.         case CLIENT_ERROR:
  239.             //printf("client error\n");
  240.             client->worker->stats.req_error++;
  241.             client->keepalive = 0;
  242.             client->success = 0;
  243.             client->state = CLIENT_END;
  244.         case CLIENT_END:
  245.             /* update worker stats */
  246.             client->worker->stats.req_done++;

  247.             if (client->success) {
  248.                 client->worker->stats.req_success++;
  249.                 client->worker->stats.bytes_body += client->bytes_received - client->header_size;
  250.             } else {
  251.                 client->worker->stats.req_failed++;
  252.             }

  253.             /* print progress every 10% done */
  254.             if (client->worker->id == 1 && client->worker->stats.req_done % client->worker->progress_interval == 0) {
  255.                 printf("progress: %3d%% done\n",
  256.                     (int) (client->worker->stats.req_done * 100 / client->worker->stats.req_todo)
  257.                 );
  258.             }

  259.             if (client->worker->stats.req_started == client->worker->stats.req_todo) {
  260.                 /* this worker has started all requests */
  261.                 client->keepalive = 0;
  262.                 client_reset(client);

  263.                 if (client->worker->stats.req_done == client->worker->stats.req_todo) {
  264.                     /* this worker has finished all requests */
  265.                     ev_unref(client->worker->loop);
  266.                 }
  267.             } else {
  268.                 client_reset(client);
  269.                 goto start;
  270.             }
  271.     }
  272. }


  273. static uint8_t client_parse(Client *client, int size) {
  274.     char *end, *str;
  275.     uint16_t status_code;

  276.     switch (client->parser_state) {
  277.         case PARSER_START:
  278.             //printf("parse (START):\n%s\n", &client->buffer[client->parser_offset]);
  279.             /* look for HTTP/1.1 200 OK */
  280.             if (client->buffer_offset < sizeof("HTTP/1.1 200\r\n"))
  281.                 return 1;

  282.             if (strncmp(client->buffer, "HTTP/1.1 ", sizeof("HTTP/1.1 ")-1) != 0)
  283.                 return 0;

  284.             // now the status code
  285.             status_code = 0;
  286.             str = client->buffer + sizeof("HTTP/1.1 ")-1;
  287.             for (end = str + 3; str != end; str++) {
  288.                 if (*str < '0' || *str > '9')
  289.                     return 0;

  290.                 status_code *= 10;
  291.                 status_code += *str - '0';
  292.             }

  293.             if (status_code >= 200 && status_code < 300) {
  294.                 client->worker->stats.req_2xx++;
  295.                 client->status_success = 1;
  296.             } else if (status_code < 400) {
  297.                 client->worker->stats.req_3xx++;
  298.                 client->status_success = 1;
  299.             } else if (status_code < 500) {
  300.                 client->worker->stats.req_4xx++;
  301.             } else if (status_code < 600) {
  302.                 client->worker->stats.req_5xx++;
  303.             } else {
  304.                 // invalid status code
  305.                 return 0;
  306.             }

  307.             // look for next \r\n
  308.             end = strchr(end, '\r');
  309.             if (!end || *(end+1) != '\n')
  310.                 return 0;

  311.             client->parser_offset = end + 2 - client->buffer;
  312.             client->parser_state = PARSER_HEADER;
  313.         case PARSER_HEADER:
  314.             //printf("parse (HEADER)\n");
  315.             /* look for Content-Length and Connection header */
  316.             while (NULL != (end = strchr(&client->buffer[client->parser_offset], '\r'))) {
  317.                 if (*(end+1) != '\n')
  318.                     return 0;

  319.                 if (end == &client->buffer[client->parser_offset]) {
  320.                     /* body reached */
  321.                     client->parser_state = PARSER_BODY;
  322.                     client->header_size = end + 2 - client->buffer;
  323.                     //printf("body reached\n");

  324.                     return client_parse(client, size - client->header_size);
  325.                 }

  326.                 *end = '\0';
  327.                 str = &client->buffer[client->parser_offset];
  328.                 //printf("checking header: '%s'\n", str);

  329.                 if (strncmp(str, "Content-Length: ", sizeof("Content-Length: ")-1) == 0) {
  330.                     /* content length header */
  331.                     client->content_length = str_to_uint64(str + sizeof("Content-Length: ") - 1);
  332.                 } else if (strncmp(str, "Connection: ", sizeof("Connection: ")-1) == 0) {
  333.                     /* connection header */
  334.                     str += sizeof("Connection: ") - 1;

  335.                     if (strncmp(str, "close", sizeof("close")-1) == 0)
  336.                         client->keepalive = 0;
  337.                     else if (strncmp(str, "Keep-Alive", sizeof("Keep-Alive")-1) == 0)
  338.                         client->keepalive = client->worker->config->keep_alive;
  339.                     else if (strncmp(str, "keep-alive", sizeof("keep-alive")-1) == 0)
  340.                         client->keepalive = client->worker->config->keep_alive;
  341.                     else
  342.                         return 0;
  343.                 } else if (strncmp(str, "Transfer-Encoding: ", sizeof("Transfer-Encoding: ")-1) == 0) {
  344.                     /* transfer encoding header */
  345.                     str += sizeof("Transfer-Encoding: ") - 1;

  346.                     if (strncmp(str, "chunked", sizeof("chunked")-1) == 0)
  347.                         client->chunked = 1;
  348.                     else
  349.                         return 0;
  350.                 }


  351.                 if (*(end+2) == '\r' && *(end+3) == '\n') {
  352.                     /* body reached */
  353.                     client->parser_state = PARSER_BODY;
  354.                     client->header_size = end + 4 - client->buffer;
  355.                     client->parser_offset = client->header_size;
  356.                     //printf("body reached\n");

  357.                     return client_parse(client, size - client->header_size);
  358.                 }

  359.                 client->parser_offset = end - client->buffer + 2;
  360.             }

  361.             return 1;
  362.         case PARSER_BODY:
  363.             //printf("parse (BODY)\n");
  364.             /* do nothing, just consume the data */
  365.             /*printf("content-l: %"PRIu64", header: %d, recevied: %"PRIu64"\n",
  366.             client->content_length, client->header_size, client->bytes_received);*/

  367.             if (client->chunked) {
  368.                 int consume_max;

  369.                 str = &client->buffer[client->parser_offset];
  370.                 /*printf("parsing chunk: '%s'\n(%"PRIi64" received, %"PRIi64" size, %d parser offset)\n",
  371.                     str, client->chunk_received, client->chunk_size, client->parser_offset
  372.                 );*/

  373.                 if (client->chunk_size == -1) {
  374.                     /* read chunk size */
  375.                     client->chunk_size = 0;
  376.                     client->chunk_received = 0;
  377.                     end = str + size;

  378.                     for (; str < end; str++) {
  379.                         if (*str == ';' || *str == '\r')
  380.                             break;

  381.                         client->chunk_size *= 16;
  382.                         if (*str >= '0' && *str <= '9')
  383.                             client->chunk_size += *str - '0';
  384.                         else if (*str >= 'A' && *str <= 'Z')
  385.                             client->chunk_size += 10 + *str - 'A';
  386.                         else if (*str >= 'a' && *str <= 'z')
  387.                             client->chunk_size += 10 + *str - 'a';
  388.                         else
  389.                             return 0;
  390.                     }

  391.                     str = strstr(str, "\r\n");
  392.                     if (!str)
  393.                         return 0;
  394.                     str += 2;

  395.                     //printf("---------- chunk size: %"PRIi64", %d read, %d offset, data: '%s'\n", client->chunk_size, size, client->parser_offset, str);

  396.                     if (client->chunk_size == 0) {
  397.                         /* chunk of size 0 marks end of content body */
  398.                         client->state = CLIENT_END;
  399.                         client->success = client->status_success ? 1 : 0;
  400.                         return 1;
  401.                     }

  402.                     size -= str - &client->buffer[client->parser_offset];
  403.                     client->parser_offset = str - client->buffer;
  404.                 }

  405.                 /* consume chunk till chunk_size is reached */
  406.                 consume_max = client->chunk_size - client->chunk_received;

  407.                 if (size < consume_max)
  408.                     consume_max = size;

  409.                 client->chunk_received += consume_max;
  410.                 client->parser_offset += consume_max;

  411.                 //printf("---------- chunk consuming: %d, received: %"PRIi64" of %"PRIi64", offset: %d\n", consume_max, client->chunk_received, client->chunk_size, client->parser_offset);

  412.                 if (client->chunk_received == client->chunk_size) {
  413.                     if (client->buffer[client->parser_offset] != '\r' || client->buffer[client->parser_offset+1] != '\n')
  414.                         return 0;

  415.                     /* got whole chunk, next! */
  416.                     //printf("---------- got whole chunk!!\n");
  417.                     client->chunk_size = -1;
  418.                     client->chunk_received = 0;
  419.                     client->parser_offset += 2;
  420.                     consume_max += 2;

  421.                     /* there is stuff left to parse */
  422.                     if (size - consume_max > 0)
  423.                         return client_parse(client, size - consume_max);
  424.                 }

  425.                 client->parser_offset = 0;
  426.                 client->buffer_offset = 0;

  427.                 return 1;
  428.             } else {
  429.                 /* not chunked, just consume all data till content-length is reached */
  430.                 client->buffer_offset = 0;

  431.                 if (client->content_length == -1)
  432.                     return 0;

  433.                 if (client->bytes_received == (uint64_t) (client->header_size + client->content_length)) {
  434.                     /* full response received */
  435.                     client->state = CLIENT_END;
  436.                     client->success = client->status_success ? 1 : 0;
  437.                 }
  438.             }

  439.             return 1;
  440.     }

  441.     return 1;
  442. }