Rhythm & Biology

Engineering, Science, et al.

libeventでHTTPサーバを試す

libeventに含まれるevhttpを使って、簡単なhttpサーバを作ってみました。

404を返す

まずは404 not foundを返すコードを書いてみます。ただし、GET以外のメソッドの場合はBad Requestを返すようにします。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <event.h>                                                                                  
#include <evhttp.h>

#define HTTPD_ADDR "0.0.0.0"
#define HTTPD_PORT 8080

void req_handler(struct evhttp_request *r, void *arg)
{
  if (r->type != EVHTTP_REQ_GET)
    evhttp_send_error(r, HTTP_BADREQUEST, "Available GET only");
  else
    evhttp_send_error(r, HTTP_NOTFOUND, "Not Found");
}

int main(int argc, const char* argv[])
{
  struct event_base *ev_base;
  struct evhttp *httpd;

  ev_base = event_base_new();
  httpd = evhttp_new(ev_base);
  if (evhttp_bind_socket(httpd, HTTPD_ADDR, HTTPD_PORT) < 0) {
    perror("evhttp_bind_socket()");
    exit(EXIT_FAILURE);
  }
  evhttp_set_gencb(httpd, req_handler, NULL);
  event_base_dispatch(ev_base);
  evhttp_free(httpd);
  event_base_free(ev_base);

  return 0;
}

コンパイル・実行して、ブラウザでlocalhost:8080にアクセスすると"Not Found"と表示されます。

plain textを返す

今度はtext/plainで"Hello World"と返すようにしてみます。main関数は上のものと同じなので省略します。

void req_handler(struct evhttp_request *r, void *arg)
{
  struct evbuffer *evbuf;
  char message[] = "Hello World";
  int message_length = strlen(message);
  char content_length[8];

  evbuf = evbuffer_new();
  if (evbuf == NULL) {
    evhttp_send_error(r, HTTP_SERVUNAVAIL, "Failed to create buffer");
    return;
  }

  snprintf(content_length, 7, "%d", message_length);
  evhttp_add_header(r->output_headers, "Content-Type", "text/plain");
  evhttp_add_header(r->output_headers, "Content-Length", content_length);
  evbuffer_add(evbuf, message, message_length);
  evhttp_send_reply(r, HTTP_OK, "", evbuf);
  evbuffer_free(evbuf);
}

少し複雑になってきましたが、基本的にはheaderを用意して、struct evbufferにbodyを書き込んで送る、というだけです。

もう少しマシなhttpサーバ

エラー対策などはかなり無視しますが、それなりに動くhttpサーバを書いてみます。

#define DOCUMENT_ROOT "."

static const char *get_mime_type(const char *filepath)
{
  const char *filetype = strrchr(filepath, '.');

  if (strcasecmp(filetype, ".html") == 0 ||
      strcasecmp(filetype, ".htm") == 0)
    return "text/html";
  else if (strcasecmp(filetype, ".js") == 0)
    return "text/javascript";
  else if (strcasecmp(filetype, ".css") == 0)
    return "text/css";
  else if (strcasecmp(filetype, ".txt") == 0)
    return "text/plain";
  else if (strcasecmp(filetype, ".ico") == 0)
    return "image/x-icon";
  else if (strcasecmp(filetype, ".png") == 0)
    return "image/png";
  else if (strcasecmp(filetype, ".gif") == 0)
    return "image/gif";
  else if (strcasecmp(filetype, ".jpeg") == 0 ||
           strcasecmp(filetype, ".jpg") == 0)
    return "image/jpeg";
  else if (strcasecmp(filetype, ".pdf") == 0)
    return "application/pdf";
  return "application/octet-stream";
}

static const char *get_content_from_file(const char *req_path, char **content, int *content_length)
{
  struct stat sb;
  static char filepath[1024];
  FILE *fp;
  int rsize;

  *content = NULL;
  sprintf(filepath, "%s%s", DOCUMENT_ROOT, req_path);

  if (stat(filepath, &sb) < 0)
    return filepath;

  if (S_ISDIR(sb.st_mode)) {
    if (strcmp(req_path, "/") == 0)
      sprintf(filepath, "%s%sindex.html", DOCUMENT_ROOT, req_path);
    else
      sprintf(filepath, "%s%s/index.html", DOCUMENT_ROOT, req_path);

    if (stat(filepath, &sb) < 0)
      return filepath;
  }

  *content_length = (int)sb.st_size;

  fp = fopen(filepath, "rb");
  if (fp) {
    *content = (char *)malloc(*content_length);
    rsize = fread(*content, 1, *content_length, fp);
    fclose(fp);                                                                                     
    if (rsize != *content_length) {
      free(*content);
      *content = NULL;
    }
  }

  return filepath;
}

void req_handler(struct evhttp_request *r, void *arg)
{
  struct evbuffer *evbuf;
  const char *req_path, *res_path;
  char *content;
  int content_length;
  char content_length_str[12];

  if (r->type != EVHTTP_REQ_GET) {
    evhttp_send_error(r, HTTP_BADREQUEST, "Available GET only");
    return;
  }

  req_path = evhttp_request_uri(r);
  res_path = get_content_from_file(req_path, &content, &content_length);
  if (content == NULL) {
    evhttp_send_error(r, HTTP_NOTFOUND, "Not Found");
    return;
  }
  sprintf(content_length_str, "%d", content_length);

  evhttp_add_header(r->output_headers, "Content-Type", get_mime_type(res_path));
  evhttp_add_header(r->output_headers, "Content-Length", content_length_str);

  evbuf = evbuffer_new();
  evbuffer_add(evbuf, content, content_length);
  evhttp_send_reply(r, HTTP_OK, "", evbuf);
  evbuffer_free(evbuf);
}

mime typeが全然足りないとか、mime typeの判別はハッシュ使ったほうがいいとかは気にしないでください。とりあえずそれなりにまともな動作をします。

まとめ

evhttpの基本的な使い方は分かったので、あとは細かいことをやっていくだけですね。わざわざhttpサーバを書く機会も滅多にないと思いますが、楽しかったので満足です。