#ifdef __cplusplus
extern "C" {
#endif

#define PERL_NO_GET_CONTEXT /* we want efficiency */
#include <EXTERN.h>
#include <perl.h>
#include <XSUB.h>
#include <poll.h>
#include <perlio.h>

#ifdef __cplusplus
} /* extern "C" */
#endif

#define NEED_newSVpvn_flags

#include "ppport.h"
#ifndef __need_IOV_MAX
#define __need_IOV_MAX
#endif

#include <sys/uio.h>
#include <errno.h>
#include <limits.h>

#include <sys/types.h>
#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include "picohttpparser/picohttpparser.c"

#ifndef STATIC_INLINE /* a public perl API from 5.13.4 */
#   if defined(__GNUC__) || defined(__cplusplus) || (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L))
#       define STATIC_INLINE static inline
#   else
#       define STATIC_INLINE static
#   endif
#endif /* STATIC_INLINE */

#ifndef IOV_MAX
#if defined(__FreeBSD__) || defined(__APPLE__)
# define IOV_MAX 128
#endif
#endif

#ifndef IOV_MAX
#  error "Unable to determine IOV_MAX from system headers"
#endif


#define MAX_HEADER_SIZE 16384
#define MAX_HEADER_NAME_LEN 1024
#define MAX_HEADERS         128
#if defined(__OpenBSD__)
#define READ_BUFSZ 16383
#else
#define READ_BUFSZ 16384
#endif
#define BAD_REQUEST "HTTP/1.0 400 Bad Request\r\nConnection: close\r\n\r\n400 Bad Request\r\n"
#define EXPECT_CONTINUE "HTTP/1.1 100 Continue\r\n\r\n"
#define EXPECT_FAILED "HTTP/1.1 417 Expectation Failed\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nExpectation Failed\r\n"
#define TOU(ch) (('a' <= ch && ch <= 'z') ? ch - ('a' - 'A') : ch)

static const char *DoW[] = {
  "Sun","Mon","Tue","Wed","Thu","Fri","Sat"
};
static const char *MoY[] = {
  "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
};
static const char xdigit[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};

static HV *env_template;

/* stolen from HTTP::Status and Feersum */
/* Unmarked codes are from RFC 2616 */
/* See also: http://en.wikipedia.org/wiki/List_of_HTTP_status_codes */
static const char *
status_message (int code) {
  switch (code) {
    case 100: return "Continue";
    case 101: return "Switching Protocols";
    case 102: return "Processing";                      /* RFC 2518 (WebDAV) */
    case 200: return "OK";
    case 201: return "Created";
    case 202: return "Accepted";
    case 203: return "Non-Authoritative Information";
    case 204: return "No Content";
    case 205: return "Reset Content";
    case 206: return "Partial Content";
    case 207: return "Multi-Status";                    /* RFC 2518 (WebDAV) */
    case 208: return "Already Reported";              /* RFC 5842 */
    case 300: return "Multiple Choices";
    case 301: return "Moved Permanently";
    case 302: return "Found";
    case 303: return "See Other";
    case 304: return "Not Modified";
    case 305: return "Use Proxy";
    case 307: return "Temporary Redirect";
    case 400: return "Bad Request";
    case 401: return "Unauthorized";
    case 402: return "Payment Required";
    case 403: return "Forbidden";
    case 404: return "Not Found";
    case 405: return "Method Not Allowed";
    case 406: return "Not Acceptable";
    case 407: return "Proxy Authentication Required";
    case 408: return "Request Timeout";
    case 409: return "Conflict";
    case 410: return "Gone";
    case 411: return "Length Required";
    case 412: return "Precondition Failed";
    case 413: return "Request Entity Too Large";
    case 414: return "Request-URI Too Large";
    case 415: return "Unsupported Media Type";
    case 416: return "Request Range Not Satisfiable";
    case 417: return "Expectation Failed";
    case 418: return "I'm a teapot";              /* RFC 2324 */
    case 422: return "Unprocessable Entity";            /* RFC 2518 (WebDAV) */
    case 423: return "Locked";                          /* RFC 2518 (WebDAV) */
    case 424: return "Failed Dependency";               /* RFC 2518 (WebDAV) */
    case 425: return "No code";                         /* WebDAV Advanced Collections */
    case 426: return "Upgrade Required";                /* RFC 2817 */
    case 428: return "Precondition Required";
    case 429: return "Too Many Requests";
    case 431: return "Request Header Fields Too Large";
    case 449: return "Retry with";                      /* unofficial Microsoft */
    case 500: return "Internal Server Error";
    case 501: return "Not Implemented";
    case 502: return "Bad Gateway";
    case 503: return "Service Unavailable";
    case 504: return "Gateway Timeout";
    case 505: return "HTTP Version Not Supported";
    case 506: return "Variant Also Negotiates";         /* RFC 2295 */
    case 507: return "Insufficient Storage";            /* RFC 2518 (WebDAV) */
    case 509: return "Bandwidth Limit Exceeded";        /* unofficial */
    case 510: return "Not Extended";                    /* RFC 2774 */
    case 511: return "Network Authentication Required";
    default: break;
  }
  /* default to the Nxx group names in RFC 2616 */
  if (100 <= code && code <= 199) {
    return "Informational";
  }
  else if (200 <= code && code <= 299) {
    return "Success";
    }
    else if (300 <= code && code <= 399) {
        return "Redirection";
    }
    else if (400 <= code && code <= 499) {
        return "Client Error";
    }
    else {
        return "Error";
    }
}

/* stolen from HTTP::Parser::XS */
static
size_t find_ch(const char* s, size_t len, char ch)
{
  size_t i;
  for (i = 0; i != len; ++i, ++s)
    if (*s == ch)
      break;
  return i;
}

static
int header_is(const struct phr_header* header, const char* name,
                    size_t len)
{
  const char* x, * y;
  if (header->name_len != len)
    return 0;
  for (x = header->name, y = name; len != 0; --len, ++x, ++y)
    if (TOU(*x) != *y)
      return 0;
  return 1;
}



STATIC_INLINE
int store_path_info(pTHX_ HV* env, const char* src, size_t src_len) {
  size_t dlen = 0, i = 0;
  char *d;
  char s2, s3;
  SV * dst;

  dst = newSV(0);
  (void)SvUPGRADE(dst, SVt_PV);
  d = SvGROW(dst, src_len * 3 + 1);

  for (i = 0; i < src_len; i++ ) {
    if ( src[i] == '%' ) {
      if ( !isxdigit(src[i+1]) || !isxdigit(src[i+2]) ) {
        return -1;
      }
      s2 = src[i+1];
      s3 = src[i+2];
      s2 -= s2 <= '9' ? '0'
          : s2 <= 'F' ? 'A' - 10
          : 'a' - 10;
      s3 -= s3 <= '9' ? '0'
          : s3 <= 'F' ? 'A' - 10
          : 'a' - 10;
       d[dlen++] = s2 * 16 + s3;
       i += 2;
    }
    else {
      d[dlen++] = src[i];
    }
  }
  SvCUR_set(dst, dlen);
  *SvEND(dst) = '\0';
  SvPOK_only(dst);
  (void)hv_stores(env, "PATH_INFO", dst);
  return 1;
}


STATIC_INLINE
int
_parse_http_request(pTHX_ char *buf, ssize_t buf_len, HV *env) {
  const char* method;
  size_t method_len;
  const char* path;
  size_t path_len;
  int minor_version;
  struct phr_header headers[MAX_HEADERS];
  size_t num_headers = MAX_HEADERS;
  size_t question_at;
  size_t i;
  int ret;
  SV* last_value;
  char tmp[MAX_HEADER_NAME_LEN + sizeof("HTTP_") - 1];

  ret = phr_parse_request(
    buf, buf_len,
    &method, &method_len,
    &path, &path_len,
    &minor_version, headers, &num_headers, 0
  );

  if (ret < 0)
    goto done;
  if (minor_version > 1 || minor_version < 0 ) {
    ret = -1;
    goto done;
  }

  (void)hv_stores(env, "REQUEST_METHOD", newSVpvn(method, method_len));
  (void)hv_stores(env, "REQUEST_URI", newSVpvn(path, path_len));
  (void)hv_stores(env, "SCRIPT_NAME", newSVpvn("", 0));
  strcpy(tmp, "HTTP/1.");
  tmp[sizeof("HTTP/1.")-1] = '0' + minor_version;
  (void)hv_stores(env, "SERVER_PROTOCOL", newSVpvn(tmp, sizeof("HTTP/1.1")-1));

  /* PATH_INFO QUERY_STRING */
  path_len = find_ch(path, path_len, '#'); /* strip off all text after # after storing request_uri */
  question_at = find_ch(path, path_len, '?');
  if ( store_path_info(aTHX_ env, path, question_at) < 0 ) {
    hv_clear(env);
    ret = -1;
    goto done;
  }
  if (question_at != path_len) ++question_at;
  (void)hv_stores(env, "QUERY_STRING", newSVpvn(path + question_at, path_len - question_at));

  last_value = NULL;
  for (i = 0; i < num_headers; ++i) {
    if (headers[i].name != NULL) {
      const char* name;
      size_t name_len;
      SV** slot;
      if (header_is(headers + i, "CONTENT-TYPE", sizeof("CONTENT-TYPE") - 1)) {
        name = "CONTENT_TYPE";
        name_len = sizeof("CONTENT_TYPE") - 1;
      } else if (header_is(headers + i, "CONTENT-LENGTH", sizeof("CONTENT-LENGTH") - 1)) {
        name = "CONTENT_LENGTH";
        name_len = sizeof("CONTENT_LENGTH") - 1;
      } else {
        const char* s;
        char* d;
        size_t n;
        if (sizeof(tmp) - 5 < headers[i].name_len) {
          hv_clear(env);
          ret = -1;
          goto done;
        }
        strcpy(tmp, "HTTP_");
        for (s = headers[i].name, n = headers[i].name_len, d = tmp + 5;
          n != 0;
          s++, --n, d++) {
            *d = *s == '-' ? '_' : TOU(*s);
            name = tmp;
            name_len = headers[i].name_len + 5;
        }
      }
      slot = hv_fetch(env, name, name_len, 1);
      if ( !slot ) croak("ERROR: failed to create hash entry");
      if (SvOK(*slot)) {
        sv_catpvn(*slot, ", ", 2);
        sv_catpvn(*slot, headers[i].value, headers[i].value_len);
      } else {
        sv_setpvn(*slot, headers[i].value, headers[i].value_len);
        last_value = *slot;
      }
    } else {
      /* continuing lines of a mulitiline header */
      sv_catpvn(last_value, headers[i].value, headers[i].value_len);
    }
  }
 done:
  return ret;
}


STATIC_INLINE
char *
svpv2char(pTHX_ SV *sv, STRLEN *lp)
{
  if (SvGAMAGIC(sv))
    sv = sv_2mortal(newSVsv(sv));
  return SvPV(sv, *lp);
}


STATIC_INLINE
int
_accept(int fileno, struct sockaddr *addr, unsigned int addrlen) {
    int fd;
#ifdef HAVE_ACCEPT4
    fd = accept4(fileno, addr, &addrlen, SOCK_CLOEXEC|SOCK_NONBLOCK);
#else
    fd = accept(fileno, addr, &addrlen);
#endif
    if (fd < 0) {
      return fd;
    }
#ifndef HAVE_ACCEPT4
    fcntl(fd, F_SETFD, FD_CLOEXEC);
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
#endif
    return fd;
}


STATIC_INLINE
ssize_t
_writev_timeout(const int fileno, const double timeout, struct iovec *iovec, const int iovcnt, const int do_select ) {
    int rv;
    int nfound;
    struct pollfd wfds[1];
    if ( do_select == 1) goto WAIT_WRITE;
  DO_WRITE:
    rv = writev(fileno, iovec, iovcnt);
    if ( rv >= 0 ) {
      return rv;
    }
    if ( rv < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK ) {
      return rv;
    }
  WAIT_WRITE:
    while (1) {
      wfds[0].fd = fileno;
      wfds[0].events = POLLOUT;
      nfound = poll(wfds, 1, (int)timeout*1000);
      if ( nfound == 1 ) {
        break;
      }
      if ( nfound == 0 && errno != EINTR ) {
        return -1;
      }
    }
    goto DO_WRITE;
}

STATIC_INLINE
ssize_t
_read_timeout(const int fileno, const double timeout, char * read_buf, const int read_len ) {
    int rv;
    int nfound;
    struct pollfd rfds[1];
  DO_READ:
    rfds[0].fd = fileno;
    rfds[0].events = POLLIN;
    rv = read(fileno, read_buf, read_len);
    if ( rv >= 0 ) {
      return rv;
    }
    if ( rv < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK ) {
      return rv;
    }
  WAIT_READ:
    while (1) {
      nfound = poll(rfds, 1, (int)timeout*1000);
      if ( nfound == 1 ) {
        break;
      }
      if ( nfound == 0 && errno != EINTR ) {
        return -1;
      }
    }
    goto DO_READ;
}

STATIC_INLINE
ssize_t
_write_timeout(const int fileno, const double timeout, char * write_buf, const int write_len ) {
    int rv;
    int nfound;
    struct pollfd wfds[1];
  DO_WRITE:
    rv = write(fileno, write_buf, write_len);
    if ( rv >= 0 ) {
      return rv;
    }
    if ( rv < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK ) {
      return rv;
    }
  WAIT_WRITE:
    while (1) {
      wfds[0].fd = fileno;
      wfds[0].events = POLLOUT;
      nfound = poll(wfds, 1, (int)timeout*1000);
      if ( nfound == 1 ) {
        break;
      }
      if ( nfound == 0 && errno != EINTR ) {
        return -1;
      }
    }
    goto DO_WRITE;
}

STATIC_INLINE
void
str_s(char * dst, size_t *dst_len, const char * src, int src_len) {
  int i;
  int dlen = *dst_len;
  for ( i=0; i<src_len; i++) {
    dst[dlen++] = src[i];
  }
  *dst_len = dlen;
}


STATIC_INLINE
void
str_i(char * dst, size_t * dst_len, int src, int fig) {
  int dlen = *dst_len + fig - 1;
  do {
    dst[dlen] = '0' + (src % 10);
    dlen--;
    src /= 10;
  } while( dlen >= *dst_len );
  *dst_len += fig;
}


STATIC_INLINE
int _date_line(char * date_line) {
    struct tm gtm;
    time_t lt;
    size_t i = 0;
    time(&lt);
    gmtime_r(&lt, &gtm);
    date_line[i++] = 'D';
    date_line[i++] = 'a';
    date_line[i++] = 't';
    date_line[i++] = 'e';
    date_line[i++] = ':';
    date_line[i++] = ' ';
    str_s(date_line, &i, DoW[gtm.tm_wday], 3);
    date_line[i++] = ',';
    date_line[i++] = ' ';
    str_i(date_line, &i, gtm.tm_mday, 2);
    date_line[i++] = ' ';
    str_s(date_line, &i, MoY[gtm.tm_mon], 3);
    date_line[i++] = ' ';
    str_i(date_line, &i, gtm.tm_year + 1900, 4);
    date_line[i++] = ' ';
    str_i(date_line, &i, gtm.tm_hour,2);
    date_line[i++] = ':';
    str_i(date_line, &i, gtm.tm_min,2);
    date_line[i++] = ':';
    str_i(date_line, &i, gtm.tm_sec,2);
    date_line[i++] = ' ';
    date_line[i++] = 'G';
    date_line[i++] = 'M';
    date_line[i++] = 'T';
    date_line[i++] = 13;
    date_line[i++] = 10;
    return i;
}

STATIC_INLINE
int _chunked_header(char *buf, ssize_t len) {
    int dlen = 0, i;
    ssize_t l = len;
    while ( l > 0 ) {
        dlen++;
        l /= 16;
    }
    i = dlen;
    buf[i++] = 13;
    buf[i++] = 10;
    buf[i+1] = 0;
    while ( len > 0 ) {
        buf[--dlen] = xdigit[len % 16];
        len /= 16;
    }
    return i;
}

MODULE = Plack::Handler::Gazelle    PACKAGE = Plack::Handler::Gazelle

PROTOTYPES: DISABLE

BOOT:
{
    AV * psgi_version;
    psgi_version = newAV();
    av_extend(psgi_version, 2);
    (void)av_push(psgi_version,newSViv(1));
    (void)av_push(psgi_version,newSViv(1));
    SvREADONLY_on((SV*)psgi_version);

    HV *e;
    e = newHV();
    (void)hv_stores(e,"SCRIPT_NAME",          newSVpvs(""));
    (void)hv_stores(e,"psgi.version",         newRV((SV*)psgi_version));
    (void)hv_stores(e,"psgi.errors",          newRV((SV*)PL_stderrgv));
    (void)hv_stores(e,"psgi.url_scheme",      newSVpvs("http"));
    (void)hv_stores(e,"psgi.run_once",        newSV(0));
    (void)hv_stores(e,"psgi.multithread",     newSV(0));
    (void)hv_stores(e,"psgi.multiprocess",    newSViv(1));
    (void)hv_stores(e,"psgi.streaming",       newSViv(1));
    (void)hv_stores(e,"psgi.nonblocking",     newSV(0));
    (void)hv_stores(e,"psgix.input.buffered", newSViv(1));
    (void)hv_stores(e,"psgix.harakiri",       newSViv(1));

    /* stolenn from Feersum */
    /* placeholders that get defined for every request */
    (void)hv_stores(e, "SERVER_PROTOCOL", &PL_sv_undef);
    (void)hv_stores(e, "SERVER_NAME",     &PL_sv_undef);
    (void)hv_stores(e, "SERVER_PORT",     &PL_sv_undef);
    (void)hv_stores(e, "REQUEST_URI",     &PL_sv_undef);
    (void)hv_stores(e, "REQUEST_METHOD",  &PL_sv_undef);
    (void)hv_stores(e, "PATH_INFO",       &PL_sv_undef);
    (void)hv_stores(e, "REMOTE_ADDR",     &PL_sv_placeholder);
    (void)hv_stores(e, "REMOTE_PORT",     &PL_sv_placeholder);

    /* defaults that get changed for some requests */
    (void)hv_stores(e, "psgi.input",      &PL_sv_placeholder);
    (void)hv_stores(e, "CONTENT_LENGTH",  &PL_sv_placeholder);
    (void)hv_stores(e, "QUERY_STRING",    &PL_sv_placeholder);

    /* anticipated headers */
    (void)hv_stores(e, "CONTENT_TYPE",           &PL_sv_placeholder);
    (void)hv_stores(e, "HTTP_HOST",              &PL_sv_placeholder);
    (void)hv_stores(e, "HTTP_USER_AGENT",        &PL_sv_placeholder);
    (void)hv_stores(e, "HTTP_ACCEPT",            &PL_sv_placeholder);
    (void)hv_stores(e, "HTTP_ACCEPT_LANGUAGE",   &PL_sv_placeholder);
    (void)hv_stores(e, "HTTP_ACCEPT_CHARSET",    &PL_sv_placeholder);
    (void)hv_stores(e, "HTTP_REFERER",           &PL_sv_placeholder);
    (void)hv_stores(e, "HTTP_COOKIE",            &PL_sv_placeholder);
    (void)hv_stores(e, "HTTP_IF_MODIFIED_SINCE", &PL_sv_placeholder);
    (void)hv_stores(e, "HTTP_IF_NONE_MATCH",     &PL_sv_placeholder);
    (void)hv_stores(e, "HTTP_IF_MODIFIED_SINCE", &PL_sv_placeholder);
    (void)hv_stores(e, "HTTP_IF_NONE_MATCH",     &PL_sv_placeholder);
    (void)hv_stores(e, "HTTP_CACHE_CONTROL",     &PL_sv_placeholder);
    (void)hv_stores(e, "HTTP_X_FORWARDED_FOR",   &PL_sv_placeholder);

    env_template = e;
}

SV *
accept_psgi(fileno, timeout, tcp, host, port)
    int fileno
    double timeout
    int tcp
    SV * host
    SV * port
PREINIT:
    int fd;
    struct sockaddr_in cliaddr;
    unsigned int len;
    char read_buf[MAX_HEADER_SIZE];
    HV * env;
    int flag = 1;
    ssize_t rv = 0;
    ssize_t buf_len;
    ssize_t reqlen;
PPCODE:
{
    /* if ( my ($conn, $buf, $env) = accept_buffer(fileno($server),timeout,tcp,host,port) */

    len = sizeof(cliaddr);
    fd = _accept(fileno, (struct sockaddr *)&cliaddr, len);
    /* endif */
    if (fd < 0) {
      goto badexit;
    }

    rv = _read_timeout(fd, timeout, &read_buf[0], MAX_HEADER_SIZE);
    // printf("fd:%d rv:%ld %f %d\n",fd,rv,timeout);
    if ( rv <= 0 ) {
      close(fd);
      goto badexit;
    }

    env = newHVhv(env_template);

    if ( tcp == 1 ) {
      setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int));
      (void)hv_stores(env,"REMOTE_ADDR",newSVpv(inet_ntoa(cliaddr.sin_addr),0));
      (void)hv_stores(env,"REMOTE_PORT",newSViv(ntohs(cliaddr.sin_port)));
    }
    else {
      (void)hv_stores(env,"REMOTE_ADDR",newSV(0));
      (void)hv_stores(env,"REMOTE_PORT",newSViv(0));
    }
    (void)hv_stores(env,"SERVER_PORT",SvREFCNT_inc(port));
    (void)hv_stores(env,"SERVER_NAME",SvREFCNT_inc(host));

    buf_len = rv;
    while (1) {
      reqlen = _parse_http_request(aTHX_ &read_buf[0],buf_len,env);
      if ( reqlen >= 0 ) {
        break;
      }
      else if ( reqlen == -1 ) {
        /* error */
        close(fd);
        goto badexit_clear;
      }
      if ( MAX_HEADER_SIZE - buf_len == 0 ) {
        /* too large header  */
       char* badreq;
       badreq = BAD_REQUEST;
       rv = _write_timeout(fd, timeout, badreq, sizeof(BAD_REQUEST) - 1);
       close(fd);
       goto badexit_clear;
      }
      /* request is incomplete */
      rv = _read_timeout(fd, timeout, &read_buf[buf_len], MAX_HEADER_SIZE - buf_len);
      if ( rv <= 0 ) {
        close(fd);
        goto badexit_clear;
      }
      buf_len += rv;
    }

    /* expect */
    SV **expect_val = hv_fetch(env, "HTTP_EXPECT" , sizeof("HTTP_EXPECT")-1, 0);
    if (expect_val != NULL) {
      if ( strncmp(SvPV_nolen(*expect_val), "100-continue", SvCUR(*expect_val)) == 0 ) {
        rv = _write_timeout(fd, timeout, EXPECT_CONTINUE, sizeof(EXPECT_CONTINUE) - 1);
        if ( rv <= 0 ) {
          close(fd);
          goto badexit;
        }
      } else {
        rv = _write_timeout(fd, timeout, EXPECT_FAILED, sizeof(EXPECT_FAILED) - 1);
        close(fd);
        goto badexit;
      }
    }

    PUSHs(sv_2mortal(newSViv(fd)));
    PUSHs(sv_2mortal(newSVpvn(&read_buf[reqlen], buf_len - reqlen)));
    PUSHs(sv_2mortal(newRV_noinc((SV*)env)));
    XSRETURN(3);

    badexit_clear:
    sv_2mortal((SV*)env);
    badexit:
    XSRETURN(0);
}

unsigned long
read_timeout(fileno, rbuf, len, offset, timeout)
    int fileno
    SV * rbuf
    ssize_t len
    ssize_t offset
    double timeout
  PREINIT:
    SV * buf;
    char * d;
    ssize_t rv;
    ssize_t buf_len;
  CODE:
    if (!SvROK(rbuf)) croak("ERROR: buf must be RV");
    buf = SvRV(rbuf);
    if (!SvOK(buf)) {
      sv_setpvn(buf,"",0);
    }
    SvUPGRADE(buf, SVt_PV);
    SvPV_nolen(buf);
    buf_len = SvCUR(buf);
    if ( len > READ_BUFSZ ) {
      len = READ_BUFSZ;
    }
    d = SvGROW(buf, buf_len + len + 1);
    rv = _read_timeout(fileno, timeout, &d[offset], len);
    SvCUR_set(buf, (rv > 0) ? rv + buf_len : buf_len);
    *SvEND(buf) = '\0';
    SvPOK_only(buf);
    if (rv < 0) XSRETURN_UNDEF;
    RETVAL = (unsigned long)rv;
  OUTPUT:
    RETVAL

unsigned long
write_timeout(fileno, buf, len, offset, timeout)
    int fileno
    SV * buf
    ssize_t len
    ssize_t offset
    double timeout
  PREINIT:
    char * d;
    ssize_t rv;
  CODE:
    SvUPGRADE(buf, SVt_PV);
    d = SvPV_nolen(buf);
    rv = _write_timeout(fileno, timeout, &d[offset], len);
    if (rv < 0) XSRETURN_UNDEF;
    RETVAL = (unsigned long)rv;
  OUTPUT:
    RETVAL

unsigned long
write_chunk(fileno, buf, offset, timeout)
    int fileno
    SV * buf
    ssize_t offset
    double timeout
  PREINIT:
    char *d;
    ssize_t buf_len;
    ssize_t rv = 0;
    ssize_t written = 0;
    ssize_t vec_offset = 0;
    int count =0;
    int remain;
    ssize_t iovcnt = 3;
    char chunked_header_buf[18];
  CODE:
    if ( !SvOK(buf) ) {
      RETVAL = 0;
      return;
    }
    SvUPGRADE(buf, SVt_PV);
    d = SvPV_nolen(buf);
    buf_len = SvCUR(buf);
    if ( buf_len == 0 ){
      RETVAL = 0;
      return;
    }

    {
      struct iovec v[iovcnt]; // Needs C99 compiler
      v[0].iov_len = _chunked_header(chunked_header_buf,buf_len);
      v[0].iov_base = chunked_header_buf;
      v[1].iov_len = buf_len;
      v[1].iov_base = d;
      v[2].iov_base = "\r\n";
      v[2].iov_len = sizeof("\r\n") -1;

      vec_offset = 0;
      written = 0;
      remain = iovcnt;
      while ( remain > 0 ) {
        count = (remain > IOV_MAX) ? IOV_MAX : remain;
        rv = _writev_timeout(fileno, timeout,  &v[vec_offset], count, (vec_offset == 0) ? 0 : 1);
        if ( rv <= 0 ) {
          warn("failed to writev: %zd errno:%d", rv, errno);
          // error or disconnected
          break;
        }
        written += rv;
        while ( rv > 0 ) {
          if ( (unsigned int)rv >= v[vec_offset].iov_len ) {
            rv -= v[vec_offset].iov_len;
            vec_offset++;
            remain--;
          }
          else {
            v[vec_offset].iov_base = (char*)v[vec_offset].iov_base + rv;
            v[vec_offset].iov_len -= rv;
            rv = 0;
          }
        }
      }
    }

    if (rv < 0) XSRETURN_UNDEF;
    RETVAL = (unsigned long)written;
  OUTPUT:
    RETVAL

unsigned long
write_all(fileno, buf, offset, timeout)
    int fileno
    SV * buf
    ssize_t offset
    double timeout
  PREINIT:
    char * d;
    ssize_t buf_len;
    ssize_t rv;
    ssize_t written = 0;
  CODE:
    if ( !SvOK(buf) ) {
      RETVAL = 0;
      return;
    }
    SvUPGRADE(buf, SVt_PV);
    d = SvPV_nolen(buf);
    buf_len = SvCUR(buf);
    if ( buf_len == 0 ) {
      RETVAL = 0;
      return;
    }
    written = 0;
    while ( buf_len > written ) {
      rv = _write_timeout(fileno, timeout, &d[written], buf_len - written);
      if ( rv <= 0 ) {
        break;
      }
      written += rv;
    }
    if (rv < 0) XSRETURN_UNDEF;
    RETVAL = (unsigned long)written;
  OUTPUT:
    RETVAL


void
close_client(fileno)
    int fileno
  CODE:
    close(fileno);

unsigned long
write_informational_response(fileno, timeout, status_code, headers)
    int fileno
    double timeout
    int status_code
    AV * headers
  PREINIT:
    ssize_t rv;
    ssize_t iovcnt;
    ssize_t vec_offset;
    ssize_t written;
    int count;
    int remain;
    size_t i;
    struct iovec * iv;
    char status_line[512];
    char * key;
    char * val;
    STRLEN key_len = 0;
    STRLEN val_len = 0;
  CODE:
    if( (av_len(headers)+1) % 2 == 1 ) croak("ERROR: Odd number of element in header");
    iovcnt = 10 + (av_len(headers)+2)*2;
    {
      struct iovec iv[iovcnt]; // Needs C99 compiler
      /* status line */
      iovcnt = 0;
      i=0;
      status_line[i++] = 'H';
      status_line[i++] = 'T';
      status_line[i++] = 'T';
      status_line[i++] = 'P';
      status_line[i++] = '/';
      status_line[i++] = '1';
      status_line[i++] = '.';
      status_line[i++] = '1';
      status_line[i++] = ' ';
      str_i(status_line,&i,status_code,3);
      status_line[i++] = ' ';
      const char * message = status_message(status_code);
      str_s(status_line,&i, message, strlen(message));
      status_line[i++] = 13;
      status_line[i++] = 10;
      iv[iovcnt].iov_base = status_line;
      iv[iovcnt].iov_len = i;
      iovcnt++;

      i=0;
      while (i < av_len(headers) + 1 ) {
        /* key */
        key = svpv2char(aTHX_ *av_fetch(headers,i,0), &key_len);
        i++;
        val = svpv2char(aTHX_ *av_fetch(headers,i,0), &val_len);
        i++;
        iv[iovcnt].iov_base = key;
        iv[iovcnt].iov_len = key_len;
        iovcnt++;
        iv[iovcnt].iov_base = ": ";
        iv[iovcnt].iov_len = sizeof(": ") - 1;
        iovcnt++;
        /* value */
        iv[iovcnt].iov_base = val;
        iv[iovcnt].iov_len = val_len;
        iovcnt++;
        iv[iovcnt].iov_base = "\r\n";
        iv[iovcnt].iov_len = sizeof("\r\n") - 1;
        iovcnt++;
      }
      iv[iovcnt].iov_base = "\r\n";
      iv[iovcnt].iov_len = sizeof("\r\n") - 1;
      iovcnt++;

      vec_offset = 0;
      written = 0;
      remain = iovcnt;
      while ( remain > 0 ) {
        count = (remain > IOV_MAX) ? IOV_MAX : remain;
        rv = _writev_timeout(fileno, timeout,  &iv[vec_offset], count, (vec_offset == 0) ? 0 : 1);
        if ( rv <= 0 ) {
          warn("failed to writev: %zd errno:%d", rv, errno);
          // error or disconnected
          break;
        }
        written += rv;
        while ( rv > 0 ) {
          if ( (unsigned int)rv >= iv[vec_offset].iov_len ) {
            rv -= iv[vec_offset].iov_len;
            vec_offset++;
            remain--;
          }
          else {
            iv[vec_offset].iov_base = (char*)iv[vec_offset].iov_base + rv;
            iv[vec_offset].iov_len -= rv;
            rv = 0;
          }
        }
      }
    }

    if (rv < 0) XSRETURN_UNDEF;
    RETVAL = (unsigned long) written;
  OUTPUT:
    RETVAL


unsigned long
write_psgi_response(fileno, timeout, status_code, headers, body, use_chunkedv)
    int fileno
    double timeout
    int status_code
    AV * headers
    AV * body
    SV * use_chunkedv
  ALIAS:
    Plack::Handler::Gazelle::write_psgi_response = 0
    Plack::Handler::Gazelle::write_psgi_response_header = 1
  PREINIT:
    ssize_t rv;
    ssize_t iovcnt;
    ssize_t vec_offset;
    ssize_t written;
    int count;
    int remain;
    size_t i;
    struct iovec * v;
    char status_line[512];
    char date_line[512];
    char server_line[1032];
    char * key;
    char * val;
    STRLEN key_len = 0;
    STRLEN val_len;
    int date_pushed = 0;
    const char * s;
    char* d;
    ssize_t n;
    IV use_chunked;
    char * chunked_header_buf;

  CODE:
    if( (av_len(headers)+1) % 2 == 1 ) croak("ERROR: Odd number of element in header");
    use_chunked = SvIV(use_chunkedv);
    iovcnt = 10 + (av_len(headers)+2)*2 + (av_len(body) + 1);

    /* status_with_no_entity_body */
    if ( status_code < 200 || status_code == 204 || status_code == 304 ) {
      use_chunked = 0;
    }

    if ( use_chunked > 0 ) {
      iovcnt += (av_len(body)+1)*2;
    }
    Newx(chunked_header_buf, 18 * (av_len(body)+2), char);

    {
      struct iovec v[iovcnt]; // Needs C99 compiler
      /* status line */
      iovcnt = 0;
      i=0;
      status_line[i++] = 'H';
      status_line[i++] = 'T';
      status_line[i++] = 'T';
      status_line[i++] = 'P';
      status_line[i++] = '/';
      status_line[i++] = '1';
      status_line[i++] = '.';
      status_line[i++] = '1';
      status_line[i++] = ' ';
      str_i(status_line,&i,status_code,3);
      status_line[i++] = ' ';
      const char * message = status_message(status_code);
      str_s(status_line,&i, message, strlen(message));
      status_line[i++] = 13;
      status_line[i++] = 10;
      v[iovcnt].iov_base = status_line;
      v[iovcnt].iov_len = i;
      iovcnt++;

      /* for date header */
      iovcnt++;

      v[iovcnt].iov_base = "Server: gazelle\r\n";
      v[iovcnt].iov_len = sizeof("Server: gazelle\r\n")-1;
      iovcnt++;

      i=0;
      date_pushed = 0;
      while ( i < av_len(headers) + 1 ) {
        /* key */
        key = svpv2char(aTHX_ *av_fetch(headers,i,0), &key_len);
        i++;
        if ( strncasecmp(key,"Connection",key_len) == 0 ) {
          i++;
          continue;
        }

        val = svpv2char(aTHX_ *av_fetch(headers,i,0), &val_len);
        i++;

        if ( strncasecmp(key,"Date",key_len) == 0 ) {
          strcpy(date_line, "Date: ");
          for ( s=val, n = val_len, d=date_line+sizeof("Date: ")-1; n !=0; s++, --n, d++) {
            *d = *s;
          }
          date_line[sizeof("Date: ") -1 + val_len] = 13;
          date_line[sizeof("Date: ") -1 + val_len + 1] = 10;
          v[1].iov_base = date_line;
          v[1].iov_len = sizeof("Date: ") -1 + val_len + 2;
          date_pushed = 1;
          continue;
        } else if ( strncasecmp(key,"Server",key_len) == 0 ) {
          strcpy(server_line, "Server: ");
          for ( s=val, n = val_len, d=server_line+sizeof("Server: ")-1; n !=0; s++, --n, d++) {
            *d = *s;
          }
          server_line[sizeof("Server: ") -1 + val_len] = 13;
          server_line[sizeof("Server: ") -1 + val_len + 1] = 10;
          v[2].iov_base = server_line;
          v[2].iov_len = sizeof("Server: ") -1 + val_len + 2;
          continue;
        } else if ( strncasecmp(key,"Content-Length",key_len) == 0 || strncasecmp(key,"Transfer-Encoding",key_len) == 0) {
            use_chunked = 0;
        }

        v[iovcnt].iov_base = key;
        v[iovcnt].iov_len = key_len;
        iovcnt++;
        v[iovcnt].iov_base = ": ";
        v[iovcnt].iov_len = sizeof(": ") - 1;
        iovcnt++;
        /* value */
        v[iovcnt].iov_base = val;
        v[iovcnt].iov_len = val_len;
        iovcnt++;
        v[iovcnt].iov_base = "\r\n";
        v[iovcnt].iov_len = sizeof("\r\n") - 1;
        iovcnt++;
      }

      if ( date_pushed == 0 ) {
        v[1].iov_len = _date_line(date_line);
        v[1].iov_base = date_line;
      }

      if ( use_chunked > 0 ) {
          v[iovcnt].iov_base = "Transfer-Encoding: chunked\r\n";
          v[iovcnt].iov_len = sizeof("Transfer-Encoding: chunked\r\n") - 1;
          iovcnt++;
      }

      v[iovcnt].iov_base = "Connection: close\r\n\r\n";
      v[iovcnt].iov_len = sizeof("Connection: close\r\n\r\n") - 1;
      iovcnt++;

      size_t chb_offset = 0;
      for (i=0; i < av_len(body) + 1; i++ ) {
        SV **b = av_fetch(body,i,0);
        if (!SvOK(*b)) {
          continue;
        }
        d = svpv2char(aTHX_ *b, &val_len);
        if ( val_len < 1 ) {
          continue;
        }
        if ( use_chunked ) {
          v[iovcnt].iov_len = _chunked_header(&chunked_header_buf[chb_offset],val_len);
          v[iovcnt].iov_base = &chunked_header_buf[chb_offset];
          chb_offset += v[iovcnt].iov_len;
          iovcnt++;
        }
        v[iovcnt].iov_base = d;
        v[iovcnt].iov_len = val_len;
        iovcnt++;
        if ( use_chunked ) {
          v[iovcnt].iov_base = "\r\n";
          v[iovcnt].iov_len = sizeof("\r\n") -1;
          iovcnt++;
        }
      }

      if ( use_chunked && ix == 0 ) {
        v[iovcnt].iov_base = "0\r\n\r\n";
        v[iovcnt].iov_len = sizeof("0\r\n\r\n") - 1;
        iovcnt++;
      }

      vec_offset = 0;
      written = 0;
      remain = iovcnt;
      while ( remain > 0 ) {
        count = (remain > IOV_MAX) ? IOV_MAX : remain;
        rv = _writev_timeout(fileno, timeout,  &v[vec_offset], count, (vec_offset == 0) ? 0 : 1);
        if ( rv <= 0 ) {
          warn("failed to writev: %zd errno:%d", rv, errno);
          // error or disconnected
          break;
        }
        written += rv;
        while ( rv > 0 ) {
          if ( (unsigned int)rv >= v[vec_offset].iov_len ) {
            rv -= v[vec_offset].iov_len;
            vec_offset++;
            remain--;
          }
          else {
            v[vec_offset].iov_base = (char*)v[vec_offset].iov_base + rv;
            v[vec_offset].iov_len -= rv;
            rv = 0;
          }
        }
      }
    }
    sv_setiv(use_chunkedv, use_chunked);
    Safefree(chunked_header_buf);
    if (rv < 0) XSRETURN_UNDEF;
    RETVAL = (unsigned long) written;
  OUTPUT:
    RETVAL