void mk_header_set_http_status(struct mk_http_request *sr, int status) { mk_bug(!sr); sr->headers.status = status; MK_TRACE("Set HTTP status = %i", status); }
void cgi_req_add(struct cgi_request *r) { struct mk_list *list = pthread_getspecific(cgi_request_list); mk_bug(!list); mk_list_add(&r->_head, list); }
static inline void mk_http_status_completed(struct client_session *cs) { mk_bug(cs->status == MK_REQUEST_STATUS_COMPLETED); cs->status = MK_REQUEST_STATUS_COMPLETED; mk_list_del(&cs->request_incomplete); }
/* Create a new channel to distribute signals */ int mk_event_channel_create(struct mk_event_loop *loop, int *r_fd, int *w_fd, void *data) { struct mk_event_ctx *ctx; mk_bug(!data); ctx = loop->data; return _mk_event_channel_create(ctx, r_fd, w_fd, data); }
void serve_cache_headers(struct cache_req_t *req) { int ret = tee(req->file->cache_headers->pipe[0], req->buf->pipe[1], req->file->cache_headers->filled, SPLICE_F_NONBLOCK); if (ret < 0) { perror("cannot tee into the request buffer!!\n"); mk_bug(1); } if (ret < req->file->header_len) { PLUGIN_TRACE("teed %d data instead of headers len %ld\n", ret, req->file->header_len); mk_bug(ret < req->file->header_len); } req->buf->filled += ret; // HACK: make headers seem like file contents req->bytes_offset -= req->file->header_len; req->bytes_to_send += req->file->header_len; }
/* Register or modify an event */ int mk_event_add(struct mk_event_loop *loop, int fd, int type, uint32_t mask, void *data) { int ret; struct mk_event_ctx *ctx; mk_bug(!data); ctx = loop->data; ret = _mk_event_add(ctx, fd, type, mask, data); if (ret == -1) { return -1; } return 0; }
/* Register a timeout file descriptor */ static inline int _mk_event_timeout_create(struct mk_event_ctx *ctx, time_t sec, long nsec, void *data) { int ret; int timer_fd; struct itimerspec its; struct mk_event *event; mk_bug(!data); memset(&its, '\0', sizeof(struct itimerspec)); /* expiration interval */ its.it_interval.tv_sec = sec; its.it_interval.tv_nsec = nsec; /* initial expiration */ its.it_value.tv_sec = time(NULL) + sec; its.it_value.tv_nsec = 0; timer_fd = timerfd_create(CLOCK_REALTIME, 0); if (timer_fd == -1) { mk_libc_error("timerfd"); return -1; } ret = timerfd_settime(timer_fd, TFD_TIMER_ABSTIME, &its, NULL); if (ret < 0) { mk_libc_error("timerfd_settime"); return -1; } event = data; event->fd = timer_fd; event->type = MK_EVENT_NOTIFICATION; event->mask = MK_EVENT_EMPTY; /* register the timer into the epoll queue */ ret = _mk_event_add(ctx, timer_fd, MK_EVENT_NOTIFICATION, MK_EVENT_READ, data); if (ret != 0) { close(timer_fd); return ret; } return timer_fd; }
/* *This function given a unix time, set in a mk_ptr_t * the date in the RFC1123 format like: * * Wed, 23 Jun 2010 22:32:01 GMT * * it also adds a 'CRLF' at the end */ int mk_utils_utime2gmt(char **data, time_t date) { const int size = 31; unsigned short year, mday, hour, min, sec; char *buf=0; struct tm *gtm; if (date == 0) { if ((date = time(NULL)) == -1) { return -1; } } else { /* Maybe it's converted already? */ if (mk_utils_gmt_cache_get(data, date) == MK_TRUE) { return size; } } /* Convert unix time to struct tm */ gtm = mk_cache_get(mk_cache_utils_gmtime); /* If this function was invoked from a non-thread context it should exit */ mk_bug(!gtm); gtm = gmtime_r(&date, gtm); if (!gtm) { return -1; } /* struct tm -> tm_year counts number of years after 1900 */ year = gtm->tm_year + 1900; /* Signed division is slow, by using unsigned we gain 25% speed */ mday = gtm->tm_mday; hour = gtm->tm_hour; min = gtm->tm_min; sec = gtm->tm_sec; /* Compose template */ buf = *data; /* Week day */ memcpy(buf, mk_date_wd[gtm->tm_wday], 5); buf += 5; /* Day of the month */ *buf++ = ('0' + (mday / 10)); *buf++ = ('0' + (mday % 10)); *buf++ = ' '; /* Month */ memcpy(buf, mk_date_ym[gtm->tm_mon], 4); buf += 4; /* Year */ *buf++ = ('0' + (year / 1000) % 10); *buf++ = ('0' + (year / 100) % 10); *buf++ = ('0' + (year / 10) % 10); *buf++ = ('0' + (year % 10)); *buf++ = ' '; /* Hour */ *buf++ = ('0' + (hour / 10)); *buf++ = ('0' + (hour % 10)); *buf++ = ':'; /* Minutes */ *buf++ = ('0' + (min / 10)); *buf++ = ('0' + (min % 10)); *buf++ = ':'; /* Seconds */ *buf++ = ('0' + (sec / 10)); *buf++ = ('0' + (sec % 10)); /* GMT Time zone + CRLF */ memcpy(buf, " GMT\r\n\0", 7); /* Add new entry to the cache */ mk_utils_gmt_cache_add(*data, date); /* Set mk_ptr_t data len */ return size; }
/* Send response headers */ int mk_header_prepare(struct mk_http_session *cs, struct mk_http_request *sr, struct mk_server *server) { int i = 0; unsigned long len = 0; char *buffer = 0; mk_ptr_t response; struct response_headers *sh; struct mk_iov *iov; sh = &sr->headers; iov = &sh->headers_iov; /* HTTP Status Code */ if (sh->status == MK_CUSTOM_STATUS) { response.data = sh->custom_status.data; response.len = sh->custom_status.len; } else { for (i = 0; i < status_response_len; i++) { if (status_response[i].status == sh->status) { response.data = status_response[i].response; response.len = status_response[i].length; break; } } } /* Invalid status set */ mk_bug(i == status_response_len); mk_iov_add(iov, response.data, response.len, MK_FALSE); /* * Preset headers (mk_clock.c): * * - Server * - Date */ mk_iov_add(iov, headers_preset.data, headers_preset.len, MK_FALSE); /* Last-Modified */ if (sh->last_modified > 0) { mk_ptr_t *lm = MK_TLS_GET(mk_tls_cache_header_lm); lm->len = mk_utils_utime2gmt(&lm->data, sh->last_modified); mk_iov_add(iov, mk_header_last_modified.data, mk_header_last_modified.len, MK_FALSE); mk_iov_add(iov, lm->data, lm->len, MK_FALSE); } /* Connection */ if (sh->connection == 0) { if (cs->close_now == MK_FALSE) { if (sr->connection.len > 0) { if (sr->protocol != MK_HTTP_PROTOCOL_11) { mk_iov_add(iov, mk_header_conn_ka.data, mk_header_conn_ka.len, MK_FALSE); } } } else { mk_iov_add(iov, mk_header_conn_close.data, mk_header_conn_close.len, MK_FALSE); } } else if (sh->connection == MK_HEADER_CONN_UPGRADED) { mk_iov_add(iov, mk_header_conn_upgrade.data, mk_header_conn_upgrade.len, MK_FALSE); } /* Location */ if (sh->location != NULL) { mk_iov_add(iov, mk_header_short_location.data, mk_header_short_location.len, MK_FALSE); mk_iov_add(iov, sh->location, strlen(sh->location), MK_TRUE); } /* allowed methods */ if (sh->allow_methods.len > 0) { mk_iov_add(iov, mk_header_allow.data, mk_header_allow.len, MK_FALSE); mk_iov_add(iov, sh->allow_methods.data, sh->allow_methods.len, MK_FALSE); } /* Content type */ if (sh->content_type.len > 0) { mk_iov_add(iov, sh->content_type.data, sh->content_type.len, MK_FALSE); } /* * Transfer Encoding: the transfer encoding header is just sent when * the response has some content defined by the HTTP status response */ switch (sh->transfer_encoding) { case MK_HEADER_TE_TYPE_CHUNKED: mk_iov_add(iov, mk_header_te_chunked.data, mk_header_te_chunked.len, MK_FALSE); break; } /* E-Tag */ if (sh->etag_len > 0) { mk_iov_add(iov, sh->etag_buf, sh->etag_len, MK_FALSE); } /* Content-Encoding */ if (sh->content_encoding.len > 0) { mk_iov_add(iov, mk_header_content_encoding.data, mk_header_content_encoding.len, MK_FALSE); mk_iov_add(iov, sh->content_encoding.data, sh->content_encoding.len, MK_FALSE); } /* Content-Length */ if (sh->content_length >= 0 && sh->transfer_encoding != 0) { /* Map content length to MK_POINTER */ mk_ptr_t *cl = MK_TLS_GET(mk_tls_cache_header_cl); mk_string_itop(sh->content_length, cl); /* Set headers */ mk_iov_add(iov, mk_header_content_length.data, mk_header_content_length.len, MK_FALSE); mk_iov_add(iov, cl->data, cl->len, MK_FALSE); } if ((sh->content_length != 0 && (sh->ranges[0] >= 0 || sh->ranges[1] >= 0)) && server->resume == MK_TRUE) { buffer = 0; /* yyy- */ if (sh->ranges[0] >= 0 && sh->ranges[1] == -1) { mk_string_build(&buffer, &len, "%s bytes %d-%ld/%ld\r\n", RH_CONTENT_RANGE, sh->ranges[0], (sh->real_length - 1), sh->real_length); mk_iov_add(iov, buffer, len, MK_TRUE); } /* yyy-xxx */ if (sh->ranges[0] >= 0 && sh->ranges[1] >= 0) { mk_string_build(&buffer, &len, "%s bytes %d-%d/%ld\r\n", RH_CONTENT_RANGE, sh->ranges[0], sh->ranges[1], sh->real_length); mk_iov_add(iov, buffer, len, MK_TRUE); } /* -xxx */ if (sh->ranges[0] == -1 && sh->ranges[1] > 0) { mk_string_build(&buffer, &len, "%s bytes %ld-%ld/%ld\r\n", RH_CONTENT_RANGE, (sh->real_length - sh->ranges[1]), (sh->real_length - 1), sh->real_length); mk_iov_add(iov, buffer, len, MK_TRUE); } } if (sh->upgrade == MK_HEADER_UPGRADED_H2C) { mk_iov_add(iov, mk_header_upgrade_h2c.data, mk_header_upgrade_h2c.len, MK_FALSE); } if (sh->cgi == SH_NOCGI || sh->breakline == MK_HEADER_BREAKLINE) { if (!sr->headers._extra_rows) { mk_iov_add(iov, mk_iov_crlf.data, mk_iov_crlf.len, MK_FALSE); } else { mk_iov_add(sr->headers._extra_rows, mk_iov_crlf.data, mk_iov_crlf.len, MK_FALSE); } } /* * Configure the Stream to dispatch the headers */ /* Set the IOV input stream */ sr->in_headers.buffer = iov; sr->in_headers.bytes_total = iov->total_len; sr->in_headers.cb_finished = mk_header_cb_finished; if (sr->headers._extra_rows) { /* Our main sr->stream contains the main headers (header_iov) * and 'may' have already some linked data. If we have some * extra headers rows we need to link this IOV right after * the main header_iov. */ struct mk_stream_input *in = &sr->in_headers_extra; in->type = MK_STREAM_IOV; in->dynamic = MK_FALSE; in->cb_consumed = NULL; in->cb_finished = cb_stream_iov_extended_free; in->stream = &sr->stream; in->buffer = sr->headers._extra_rows; in->bytes_total = sr->headers._extra_rows->total_len; mk_list_add_after(&sr->in_headers_extra._head, &sr->in_headers._head, &sr->stream.inputs); } sh->sent = MK_TRUE; return 0; }
// TODO: Assuming headers are only filled once void fill_cache_headers(struct cache_file_t *file, struct client_session *cs, struct session_request *sr) { int ret = 0; if (pthread_mutex_trylock(&file->cache_headers->write_mutex) == 0) { if (!file->cache_headers->filled) { // HACK: change server request values to prevent monkey to // not add "connection: close" in any case as it messes up things // when served every request with same cached headers. int old_conn = sr->headers.connection, old_keepalive = sr->keep_alive, old_close = sr->close_now, old_conlen = sr->connection.len; sr->headers.connection= 0; sr->keep_alive = MK_TRUE; sr->close_now = MK_FALSE; if (sr->connection.len == 0) sr->connection.len = 1; mk_api->header_send(file->cache_headers->pipe[1], cs, sr); if (ioctl(file->cache_headers->pipe[0], FIONREAD, &file->header_len) != 0) { perror("cannot find size of pipe buf!"); mk_bug(1); } // restoring modified server request values sr->headers.connection = old_conn; sr->keep_alive = old_keepalive; sr->close_now = old_close; sr->connection.len = old_conlen; file->cache_headers->filled = file->header_len; // fill in the empty header pipe space with some initial file // data to send them in a single tee syscall, only in case // there is enough room which would be true for small files // which fit inside a pipe int leftover = file->cache_headers->cap - file->cache_headers->filled; struct pipe_buf_t *first_buf = mk_list_entry_first(&file->cache, struct pipe_buf_t, _head); if (leftover > first_buf->filled) { if (first_buf->filled) { ret = tee(first_buf->pipe[0], file->cache_headers->pipe[1], first_buf->filled, SPLICE_F_NONBLOCK); mk_bug(ret <= 0); file->cache_headers->filled += ret; } } else { // file too big to compltely fit in the header pipe // along with the rest of the headers } mk_bug(file->cache_headers->filled == 0); } pthread_mutex_unlock(&file->cache_headers->write_mutex); }
/* Send response headers */ int mk_header_send(int fd, struct client_session *cs, struct session_request *sr) { int i=0; unsigned long len = 0; char *buffer = 0; mk_pointer response; struct response_headers *sh; struct mk_iov *iov; sh = &sr->headers; iov = mk_header_iov_get(); /* HTTP Status Code */ if (sh->status == MK_CUSTOM_STATUS) { response.data = sh->custom_status.data; response.len = sh->custom_status.len; } else { for (i=0; i < status_response_len; i++) { if (status_response[i].status == sh->status) { response.data = status_response[i].response; response.len = status_response[i].length; break; } } } /* Invalid status set */ mk_bug(i == status_response_len); mk_header_iov_add_entry(iov, response, mk_iov_none, MK_IOV_NOT_FREE_BUF); /* Server details */ mk_iov_add_entry(iov, sr->host_conf->header_host_signature.data, sr->host_conf->header_host_signature.len, mk_iov_crlf, MK_IOV_NOT_FREE_BUF); /* Date */ mk_iov_add_entry(iov, mk_header_short_date.data, mk_header_short_date.len, header_current_time, MK_IOV_NOT_FREE_BUF); /* Last-Modified */ if (sh->last_modified > 0) { mk_pointer *lm; lm = mk_cache_get(mk_cache_header_lm); lm->len = mk_utils_utime2gmt(&lm->data, sh->last_modified); mk_iov_add_entry(iov, mk_header_last_modified.data, mk_header_last_modified.len, *lm, MK_IOV_NOT_FREE_BUF); } /* Connection */ if (sh->connection == 0) { if (mk_http_keepalive_check(cs) == 0) { if (sr->connection.len > 0) { /* Get cached mk_pointers */ mk_pointer *ka_format = mk_cache_get(mk_cache_header_ka); mk_pointer *ka_header = mk_cache_get(mk_cache_header_ka_max); /* Compose header and add entries to iov */ mk_string_itop(config->max_keep_alive_request - cs->counter_connections, ka_header); mk_iov_add_entry(iov, ka_format->data, ka_format->len, mk_iov_none, MK_IOV_NOT_FREE_BUF); mk_iov_add_entry(iov, ka_header->data, ka_header->len, mk_header_conn_ka, MK_IOV_NOT_FREE_BUF); } } else { mk_iov_add_entry(iov, mk_header_conn_close.data, mk_header_conn_close.len, mk_iov_none, MK_IOV_NOT_FREE_BUF); } } /* Location */ if (sh->location != NULL) { mk_iov_add_entry(iov, mk_header_short_location.data, mk_header_short_location.len, mk_iov_none, MK_IOV_NOT_FREE_BUF); mk_iov_add_entry(iov, sh->location, strlen(sh->location), mk_iov_crlf, MK_IOV_FREE_BUF); } /* allowed methods */ if (sh->allow_methods.len > 0) { mk_iov_add_entry(iov, mk_header_allow.data, mk_header_allow.len, sh->allow_methods, MK_IOV_NOT_FREE_BUF) ; } /* Content type */ if (sh->content_type.len > 0) { mk_iov_add_entry(iov, mk_header_short_ct.data, mk_header_short_ct.len, sh->content_type, MK_IOV_NOT_FREE_BUF); } /* * Transfer Encoding: the transfer encoding header is just sent when * the response has some content defined by the HTTP status response */ if ((sh->status < MK_REDIR_MULTIPLE) || (sh->status > MK_REDIR_USE_PROXY)) { switch (sh->transfer_encoding) { case MK_HEADER_TE_TYPE_CHUNKED: mk_iov_add_entry(iov, mk_header_te_chunked.data, mk_header_te_chunked.len, mk_iov_none, MK_IOV_NOT_FREE_BUF); break; } } /* Content-Encoding */ if (sh->content_encoding.len > 0) { mk_iov_add_entry(iov, mk_header_content_encoding.data, mk_header_content_encoding.len, mk_iov_none, MK_IOV_NOT_FREE_BUF); mk_iov_add_entry(iov, sh->content_encoding.data, sh->content_encoding.len, mk_iov_none, MK_IOV_NOT_FREE_BUF); } /* Content-Length */ if (sh->content_length >= 0) { /* Map content length to MK_POINTER */ mk_pointer *cl; cl = mk_cache_get(mk_cache_header_cl); mk_string_itop(sh->content_length, cl); /* Set headers */ mk_iov_add_entry(iov, mk_header_content_length.data, mk_header_content_length.len, *cl, MK_IOV_NOT_FREE_BUF); } if ((sh->content_length != 0 && (sh->ranges[0] >= 0 || sh->ranges[1] >= 0)) && config->resume == MK_TRUE) { buffer = 0; /* yyy- */ if (sh->ranges[0] >= 0 && sh->ranges[1] == -1) { mk_string_build(&buffer, &len, "%s bytes %d-%ld/%ld", RH_CONTENT_RANGE, sh->ranges[0], (sh->real_length - 1), sh->real_length); mk_iov_add_entry(iov, buffer, len, mk_iov_crlf, MK_IOV_FREE_BUF); } /* yyy-xxx */ if (sh->ranges[0] >= 0 && sh->ranges[1] >= 0) { mk_string_build(&buffer, &len, "%s bytes %d-%d/%ld", RH_CONTENT_RANGE, sh->ranges[0], sh->ranges[1], sh->real_length); mk_iov_add_entry(iov, buffer, len, mk_iov_crlf, MK_IOV_FREE_BUF); } /* -xxx */ if (sh->ranges[0] == -1 && sh->ranges[1] > 0) { mk_string_build(&buffer, &len, "%s bytes %ld-%ld/%ld", RH_CONTENT_RANGE, (sh->real_length - sh->ranges[1]), (sh->real_length - 1), sh->real_length); mk_iov_add_entry(iov, buffer, len, mk_iov_crlf, MK_IOV_FREE_BUF); } } mk_socket_set_cork_flag(fd, TCP_CORK_ON); if (sh->cgi == SH_NOCGI || sh->breakline == MK_HEADER_BREAKLINE) { if (!sr->headers._extra_rows) { mk_iov_add_entry(iov, mk_iov_crlf.data, mk_iov_crlf.len, mk_iov_none, MK_IOV_NOT_FREE_BUF); } else { mk_iov_add_entry(sr->headers._extra_rows, mk_iov_crlf.data, mk_iov_crlf.len, mk_iov_none, MK_IOV_NOT_FREE_BUF); } } mk_socket_sendv(fd, iov); if (sr->headers._extra_rows) { mk_socket_sendv(fd, sr->headers._extra_rows); mk_iov_free(sr->headers._extra_rows); sr->headers._extra_rows = NULL; } mk_header_iov_free(iov); sh->sent = MK_TRUE; return 0; }
/* *This function given a unix time, set in a mk_pointer * the date in the RFC1123 format like: * * Wed, 23 Jun 2010 22:32:01 GMT * * it also adds a 'CRLF' at the end */ int mk_utils_utime2gmt(char **data, time_t date) { int size = 31; unsigned int year; char *buf=0; struct tm *gtm; if (date == 0) { if ((date = time(NULL)) == -1) { return -1; } } /* Convert unix time to struct tm */ gtm = mk_cache_get(mk_cache_utils_gmtime); /* If this function was invoked from a non-thread context it should exit */ mk_bug(!gtm); gtm = gmtime_r(&date, gtm); if (!gtm) { return -1; } /* struct tm -> tm_year counts number of years after 1900 */ year = gtm->tm_year + 1900; /* Compose template */ buf = *data; /* Week day */ *buf++ = mk_date_wd[gtm->tm_wday][0]; *buf++ = mk_date_wd[gtm->tm_wday][1]; *buf++ = mk_date_wd[gtm->tm_wday][2]; *buf++ = ','; *buf++ = ' '; /* Day of the month */ *buf++ = ('0' + (gtm->tm_mday / 10)); *buf++ = ('0' + (gtm->tm_mday % 10)); *buf++ = ' '; /* Year month */ *buf++ = mk_date_ym[gtm->tm_mon][0]; *buf++ = mk_date_ym[gtm->tm_mon][1]; *buf++ = mk_date_ym[gtm->tm_mon][2]; *buf++ = ' '; /* Year */ *buf++ = ('0' + (year / 1000) % 10); *buf++ = ('0' + (year / 100) % 10); *buf++ = ('0' + (year / 10) % 10); *buf++ = ('0' + (year % 10)); *buf++ = ' '; /* Hour */ *buf++ = ('0' + (gtm->tm_hour / 10)); *buf++ = ('0' + (gtm->tm_hour % 10)); *buf++ = ':'; /* Minutes */ *buf++ = ('0' + (gtm->tm_min / 10)); *buf++ = ('0' + (gtm->tm_min % 10)); *buf++ = ':'; /* Seconds */ *buf++ = ('0' + (gtm->tm_sec / 10)); *buf++ = ('0' + (gtm->tm_sec % 10)); *buf++ = ' '; /* GMT Time zone + CRLF */ *buf++ = 'G'; *buf++ = 'M'; *buf++ = 'T'; *buf++ = '\r'; *buf++ = '\n'; *buf++ = '\0'; /* Set mk_pointer data len */ return size; }