int http_server_sendfile2(const char *filename, const char *additional_headers, const char *ext) { if (0 == *filename) filename = "."; struct http_server_context *ctx = http_server_get_context(); int ffd = open(filename, O_RDONLY); if (ffd < 0) return HTTP_SERVER_NOT_FOUND; struct stat st; if (0 > fstat(ffd, &st)) { LOGGER_PERROR(filename); close(ffd); return HTTP_SERVER_NOT_FOUND; } if (S_ISDIR(st.st_mode)) { close(ffd); return 1; } vmbuf_reset(&ctx->header); if (NULL != ext) http_server_header_start(HTTP_STATUS_200, mime_types_by_ext(ext)); else http_server_header_start(HTTP_STATUS_200, mime_types_by_filename(filename)); vmbuf_sprintf(&ctx->header, "%s%lu", CONTENT_LENGTH, st.st_size); if (additional_headers) vmbuf_strcpy(&ctx->header, additional_headers); http_server_header_close(); int res = http_server_sendfile_payload(ffd, st.st_size); close(ffd); if (0 > res) LOGGER_PERROR(filename); return res; }
static inline void http_server_yield(void) { struct http_server_context *ctx = http_server_get_context(); struct epoll_worker_fd_data *fd_data = epoll_worker_fd_map + ctx->fd; timeout_handler_add_fd_data(&ctx->server->timeout_handler, fd_data); yield(); TIMEOUT_HANDLER_REMOVE_FD_DATA(fd_data); }
inline void http_server_write(void) { struct http_server_context *ctx = http_server_get_context(); struct iovec iovec[2] = { { vmbuf_data(&ctx->header), vmbuf_wlocpos(&ctx->header)}, { vmbuf_data(&ctx->payload), vmbuf_wlocpos(&ctx->payload)} }; int fd = ctx->fd; ssize_t num_write; for (;;http_server_yield()) { num_write = writev(fd, iovec, iovec[1].iov_len ? 2 : 1); if (0 > num_write) { if (EAGAIN == errno) { continue; } else { ctx->persistent = 0; return; } } else { if (num_write >= (ssize_t)iovec[0].iov_len) { num_write -= iovec[0].iov_len; iovec[0].iov_len = iovec[1].iov_len - num_write; if (iovec[0].iov_len == 0) break; iovec[0].iov_base = iovec[1].iov_base + num_write; iovec[1].iov_len = 0; } else { iovec[0].iov_len -= num_write; iovec[0].iov_base += num_write; } } } }
/* * file server */ void simple_file_server(void) { /* get the current http server context */ struct http_server_context *ctx = http_server_get_context(); /* uri decode the uri in the context */ http_server_decode_uri(ctx->uri); /* remove the leading slash, we will be serving files from the current directory */ const char *file = ctx->uri; if (*file == '/') ++file; int res = http_server_sendfile(file); if (0 > res) { /* not found */ if (HTTP_SERVER_NOT_FOUND == res) http_server_response_sprintf(HTTP_STATUS_404, HTTP_CONTENT_TYPE_TEXT_PLAIN, "not found: %s", file); else http_server_response_sprintf(HTTP_STATUS_500, HTTP_CONTENT_TYPE_TEXT_PLAIN, "500 Internal Server Error: %s", file); } else if (0 < res) { /* directory */ if (0 > http_server_generate_dir_list(ctx->uri)) { http_server_response_sprintf(HTTP_STATUS_404, HTTP_CONTENT_TYPE_TEXT_PLAIN, "dir not found: %s", ctx->uri); return; } http_server_response(HTTP_STATUS_200, HTTP_CONTENT_TYPE_TEXT_HTML); } }
void http_server_response(const char *status, const char *content_type) { struct http_server_context *ctx = http_server_get_context(); vmbuf_reset(&ctx->header); http_server_header_start(status, content_type); http_server_header_content_length(); http_server_header_close(); }
struct vmbuf *http_server_end_cookie(time_t expires, const char *domain, const char *path) { struct vmbuf *buf = &http_server_get_context()->header; struct tm tm; gmtime_r(&expires, &tm); vmbuf_sprintf(buf, "\";Path=%s;Domain=%s;Expires=", path, domain); vmbuf_strftime(buf, "%a, %d-%b-%Y %H:%M:%S %Z", &tm); return buf; }
void http_server_set_cookie(const char *name, const char *value, uint32_t max_age, const char *path, const char *domain) { struct http_server_context *ctx = http_server_get_context(); vmbuf_sprintf(&ctx->header, "%s%s=\"%s\"", SET_COOKIE, name, value); if (path) vmbuf_sprintf(&ctx->header, ";Path=%s", path); if (max_age) vmbuf_sprintf(&ctx->header, ";Max-Age=%u", max_age); if (domain) vmbuf_sprintf(&ctx->header, ";Domain=%s", domain); vmbuf_sprintf(&ctx->header, ";%s", COOKIE_VERSION); }
void http_server_response_sprintf(const char *status, const char *content_type, const char *format, ...) { struct http_server_context *ctx = http_server_get_context(); vmbuf_reset(&ctx->header); vmbuf_reset(&ctx->payload); http_server_header_start(status, content_type); va_list ap; va_start(ap, format); vmbuf_vsprintf(&ctx->payload, format, ap); va_end(ap); http_server_header_content_length(); http_server_header_close(); }
static void http_server_process_request(char *uri, char *headers) { struct http_server_context *ctx = http_server_get_context(); ctx->headers = headers; char *query = strchrnul(uri, '?'); if (*query) *query++ = 0; ctx->query = query; static const char HTTP[] = "http://"; if (0 == SSTRNCMP(HTTP, uri)) { uri += SSTRLEN(HTTP); uri = strchrnul(uri, '/'); } ctx->uri = uri; epoll_worker_ignore_events(ctx->fd); ctx->server->user_func(); }
int http_server_generate_dir_list(const char *URI) { struct http_server_context *ctx = http_server_get_context(); struct vmbuf *payload = &ctx->payload; const char *dir = URI; if (*dir == '/') ++dir; if (0 == *dir) dir = "."; vmbuf_sprintf(payload, "<html><head><title>Index of %s</title></head>", dir); vmbuf_strcpy(payload, "<body>"); vmbuf_sprintf(payload, "<h1>Index of %s</h1><hr>", dir); vmbuf_sprintf(payload, "<a href=\"..\">../</a><br><br>"); vmbuf_sprintf(payload, "<table width=\"100%%\" border=\"0\">"); DIR *d = opendir(dir); int error = 0; if (d) { struct dirent de, *dep; while (0 == readdir_r(d, &de, &dep) && dep) { if (de.d_name[0] == '.') continue; struct stat st; if (0 > fstatat(dirfd(d), de.d_name, &st, 0)) { vmbuf_sprintf(payload, "<tr><td>ERROR: %s</td><td>N/A</td></tr>", de.d_name); continue; } const char *slash = (S_ISDIR(st.st_mode) ? "/" : ""); struct tm t_res, *t; t = localtime_r(&st.st_mtime, &t_res); vmbuf_strcpy(payload, "<tr>"); vmbuf_sprintf(payload, "<td><a href=\"%s%s\">%s%s</a></td>", de.d_name, slash, de.d_name, slash); vmbuf_strcpy(payload, "<td>"); if (t) vmbuf_strftime(payload, "%F %T", t); vmbuf_strcpy(payload, "</td>"); vmbuf_sprintf(payload, "<td>%lu</td>", st.st_size); vmbuf_strcpy(payload, "</tr>"); } closedir(d); } vmbuf_strcpy(payload, "<tr><td colspan=3><hr></td></tr></table>"); vmbuf_sprintf(payload, "<address>RIBS 2.0 Port %hu</address></body>", ctx->server->port); vmbuf_strcpy(payload, "</html>"); return error; }
int http_server_sendfile_payload(int ffd, off_t size) { struct http_server_context *ctx = http_server_get_context(); int fd = ctx->fd; int option = 1; if (0 > setsockopt(fd, IPPROTO_TCP, TCP_CORK, &option, sizeof(option))) LOGGER_PERROR("TCP_CORK set"); epoll_worker_resume_events(ctx->fd); http_server_write(); vmbuf_reset(&ctx->header); off_t ofs = 0; for (;;http_server_yield()) { if (0 > sendfile(fd, ffd, &ofs, size - ofs) && EAGAIN != errno) return ctx->persistent = 0, -1; if (ofs >= size) break; } option = 0; if (0 > setsockopt(fd, IPPROTO_TCP, TCP_CORK, &option, sizeof(option))) LOGGER_PERROR("TCP_CORK release"); return 0; }
void http_server_fiber_main(void) { struct http_server_context *ctx = http_server_get_context(); struct http_server *server = ctx->server; int fd = ctx->fd; char *URI; char *headers; char *content; size_t content_length; int res; ctx->persistent = 0; vmbuf_init(&ctx->request, server->init_request_size); vmbuf_init(&ctx->header, server->init_header_size); vmbuf_init(&ctx->payload, server->init_payload_size); size_t max_req_size = server->max_req_size; for (;; http_server_yield()) { READ_FROM_SOCKET(); if (vmbuf_wlocpos(&ctx->request) > MIN_HTTP_REQ_SIZE) break; } do { if (0 == SSTRNCMP(GET, vmbuf_data(&ctx->request)) || 0 == SSTRNCMP(HEAD, vmbuf_data(&ctx->request))) { /* GET or HEAD */ while (0 != SSTRNCMP(CRLFCRLF, vmbuf_wloc(&ctx->request) - SSTRLEN(CRLFCRLF))) { http_server_yield(); READ_FROM_SOCKET(); } /* make sure the string is \0 terminated */ /* this will overwrite the first CR */ *(vmbuf_wloc(&ctx->request) - SSTRLEN(CRLFCRLF)) = 0; char *p = vmbuf_data(&ctx->request); ctx->persistent = check_persistent(p); URI = strchrnul(p, ' '); /* can't be NULL GET and HEAD constants have space at the end */ *URI = 0; ++URI; // skip the space p = strchrnul(URI, '\r'); /* HTTP/1.0 */ headers = p; if (0 != *headers) /* are headers present? */ headers += SSTRLEN(CRLF); /* skip the new line */ *p = 0; p = strchrnul(URI, ' '); /* truncate the version part */ *p = 0; /* \0 at the end of URI */ ctx->content = NULL; ctx->content_len = 0; /* minimal parsing and call user function */ http_server_process_request(URI, headers); } else if (0 == SSTRNCMP(POST, vmbuf_data(&ctx->request)) || 0 == SSTRNCMP(PUT, vmbuf_data(&ctx->request))) { /* POST or PUT */ for (;;) { *vmbuf_wloc(&ctx->request) = 0; /* wait until we have the header */ if (NULL != (content = strstr(vmbuf_data(&ctx->request), CRLFCRLF))) break; http_server_yield(); READ_FROM_SOCKET(); } *content = 0; /* terminate at the first CR like in GET */ content += SSTRLEN(CRLFCRLF); size_t content_ofs = content - vmbuf_data(&ctx->request); if (strstr(vmbuf_data(&ctx->request), EXPECT_100)) { vmbuf_sprintf(&ctx->header, "%s %s\r\n\r\n", HTTP_SERVER_VER, HTTP_STATUS_100); if (0 > vmbuf_write(&ctx->header, fd)) { close(fd); return; } vmbuf_reset(&ctx->header); } ctx->persistent = check_persistent(vmbuf_data(&ctx->request)); /* parse the content length */ char *p = strcasestr(vmbuf_data(&ctx->request), CONTENT_LENGTH); if (NULL == p) { http_server_response(HTTP_STATUS_411, HTTP_CONTENT_TYPE_TEXT_PLAIN); break; } p += SSTRLEN(CONTENT_LENGTH); content_length = atoi(p); for (;;) { if (content_ofs + content_length <= vmbuf_wlocpos(&ctx->request)) break; http_server_yield(); READ_FROM_SOCKET(); } p = vmbuf_data(&ctx->request); URI = strchrnul(p, ' '); /* can't be NULL PUT and POST constants have space at the end */ *URI = 0; ++URI; /* skip the space */ p = strchrnul(URI, '\r'); /* HTTP/1.0 */ headers = p; if (0 != *headers) /* are headers present? */ headers += SSTRLEN(CRLF); /* skip the new line */ *p = 0; p = strchrnul(URI, ' '); /* truncate http version */ *p = 0; /* \0 at the end of URI */ ctx->content = vmbuf_data_ofs(&ctx->request, content_ofs); *(ctx->content + content_length) = 0; ctx->content_len = content_length; /* minimal parsing and call user function */ http_server_process_request(URI, headers); } else { http_server_response(HTTP_STATUS_501, HTTP_CONTENT_TYPE_TEXT_PLAIN); break; } } while(0); if (vmbuf_wlocpos(&ctx->header) > 0) { epoll_worker_resume_events(fd); http_server_write(); } if (ctx->persistent) { struct epoll_worker_fd_data *fd_data = epoll_worker_fd_map + fd; fd_data->ctx = server->idle_ctx; timeout_handler_add_fd_data(&server->timeout_handler, fd_data); } else close(fd); }
void http_server_header_start_no_body(const char *status) { struct http_server_context *ctx = http_server_get_context(); vmbuf_sprintf(&ctx->header, "%s %s\r\nServer: %s%s%s", HTTP_SERVER_VER, status, HTTP_SERVER_NAME, CONNECTION, ctx->persistent ? CONNECTION_KEEPALIVE : CONNECTION_CLOSE); }
struct vmbuf *http_server_begin_cookie(const char *name) { struct vmbuf *buf = &http_server_get_context()->header; vmbuf_sprintf(buf, "\r\nSet-Cookie: %s=\"", name); return buf; }
void http_server_header_close(void) { struct http_server_context *ctx = http_server_get_context(); vmbuf_strcpy(&ctx->header, CRLFCRLF); }
static void http_server_fiber_main_wrapper(void) { http_server_fiber_main(); struct http_server_context *ctx = http_server_get_context(); ctx_pool_put(&ctx->server->ctx_pool, current_ctx); }
void http_server_header_content_length(void) { struct http_server_context *ctx = http_server_get_context(); vmbuf_sprintf(&ctx->header, "%s%zu", CONTENT_LENGTH, vmbuf_wlocpos(&ctx->payload)); }