|
#include <netinet/in.h> |
|
#include <stdlib.h> |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include <fcntl.h> |
|
#include <errno.h> |
|
#include <err.h> |
|
#include "../include/ev.h" |
|
#include "../include/http-parser/http_parser.h" |
|
|
|
#define SERVER_PORT 8000 |
|
#define REQUEST_BUFFER_SIZE 2048 |
|
|
|
#define RESPONSE_404 \ |
|
"HTTP/1.1 404 Not Found\r\n" |
|
#define RESPONSE_TEMPLATE \ |
|
"HTTP/1.1 200 OK\r\n" \ |
|
"Connection: keep-alive\r\n" \ |
|
"Content-Type: %s\r\n" \ |
|
"Content-Length: %d\r\n" \ |
|
"\r\n" |
|
|
|
typedef struct { |
|
int fd; |
|
ev_io ev_read; |
|
} client_t; |
|
struct _ext_to_content_type { |
|
char *extnsn[6]; |
|
char *contentname; |
|
}; |
|
|
|
http_parser_settings settings; |
|
struct ev_loop *loop; |
|
static char *static_folder = NULL; |
|
|
|
static void free_res(struct ev_loop *loop, client_t *client); |
|
|
|
static struct _ext_to_content_type ext_to_content[] = { |
|
{ { "html", "htm", NULL }, "text/html" }, |
|
{ { "js", NULL } , "application/x-javascript" }, |
|
{ { "css", NULL } , "text/css" }, |
|
{ { "jpeg", "jpg", "jpe", NULL }, "image/jpeg" }, |
|
{ { "png", NULL } , "image/png" }, |
|
{ { "gif", NULL } , "image/gif" }, |
|
{ { "swf", NULL } , "application/x-shockwave-flash"}, |
|
{ { "txt", "c", "h", "asm", "cpp", NULL }, "text/plain" }, |
|
{ { "pdf", NULL }, "application/pdf" } |
|
}; |
|
|
|
static char *get_content_type(const char *extension, char *content_type) { |
|
int i, j; |
|
for (i = 0; i < sizeof(ext_to_content) / sizeof(ext_to_content[0]); i++) { |
|
for (j = 0; ext_to_content[i].extnsn[j] != NULL; j++) { |
|
if (strcmp(extension, ext_to_content[i].extnsn[j]) == 0) { |
|
sprintf(content_type, "%s", ext_to_content[i].contentname); |
|
return content_type; |
|
} |
|
} |
|
} |
|
|
|
sprintf(content_type, "%s", "application/octet-stream"); |
|
return content_type; |
|
} |
|
|
|
static char *substr(const char *str, unsigned start, unsigned end, char *file_ext) { |
|
unsigned n = end - start; |
|
char stbuf[256]; |
|
strncpy(stbuf, str + start, n); |
|
stbuf[n] = 0; |
|
strcpy(file_ext, stbuf); |
|
return file_ext; |
|
} |
|
|
|
static char *get_extension(const char *fileName, char *file_ext) { |
|
char *ptr, c = '.'; |
|
ptr = strrchr(fileName, c); |
|
int pos = ptr - fileName; |
|
substr(fileName, pos + 1, strlen(fileName), file_ext); |
|
|
|
return file_ext; |
|
} |
|
|
|
static int setnonblock(int fd) { |
|
int flags = fcntl(fd, F_GETFL); |
|
if (flags < 0) |
|
return flags; |
|
|
|
flags |= O_NONBLOCK; |
|
if (fcntl(fd, F_SETFL, flags) < 0) |
|
return -1; |
|
|
|
return 0; |
|
} |
|
|
|
static void read_cb(struct ev_loop *loop, ev_io *w, int revents) { |
|
client_t *client = w->data; |
|
int len = 0; |
|
char rbuff[REQUEST_BUFFER_SIZE]; |
|
if (revents & EV_READ) { |
|
len = read(w->fd, &rbuff, REQUEST_BUFFER_SIZE); |
|
} |
|
|
|
if (EV_ERROR & revents) { |
|
printf("error event in read\n"); |
|
free_res(loop, client); |
|
return ; |
|
} |
|
|
|
if (len < 0) { |
|
printf("read error\n"); |
|
ev_io_stop(EV_A_ w); |
|
free_res(loop, client); |
|
return; |
|
} |
|
|
|
if (len == 0) { |
|
printf("client disconnected.\n"); |
|
ev_io_stop(EV_A_ w); |
|
free_res(loop, client); |
|
return; |
|
} |
|
|
|
rbuff[len] = '\0'; |
|
|
|
http_parser parser; |
|
parser.data = client; |
|
http_parser_init(&parser, HTTP_REQUEST); |
|
|
|
size_t parsed = http_parser_execute(&parser, &settings, rbuff, len); |
|
if (parsed < len) { |
|
fprintf(stderr, "parse error\n"); |
|
ev_io_stop(EV_A_ w); |
|
free_res(loop, client); |
|
} |
|
} |
|
|
|
static void accept_cb(struct ev_loop *loop, ev_io *w, int revents) { |
|
struct sockaddr_in client_addr; |
|
socklen_t client_len = sizeof(client_addr); |
|
int client_fd = accept(w->fd, (struct sockaddr *) &client_addr, &client_len); |
|
if (client_fd == -1) { |
|
printf("the client_fd is NULL !\n"); |
|
return; |
|
} |
|
|
|
if (setnonblock(w->fd) < 0) |
|
err(1, "failed to set client socket to non-blocking\n"); |
|
|
|
client_t *client = malloc(sizeof(client_t)); |
|
client->fd = client_fd; |
|
if (setnonblock(client->fd) < 0) |
|
err(1, "failed to set client socket to non-blocking\n"); |
|
|
|
client->ev_read.data = client; |
|
|
|
ev_io_init(&client->ev_read, read_cb, client->fd, EV_READ); |
|
ev_io_start(loop, &client->ev_read); |
|
} |
|
|
|
static int handle_static(client_t *client, const char *url_str) { |
|
if (client == NULL) { |
|
fprintf(stderr, "the client is NULL !"); |
|
return -1; |
|
} |
|
|
|
char file_path[200]; |
|
sprintf(file_path, "%s%s", static_folder, url_str); |
|
|
|
int file = open(file_path, O_RDONLY); |
|
struct stat info; |
|
if (fstat(file, &info) == -1) { |
|
fprintf(stderr, "the file %s is NULL\n", file_path); |
|
write(client->fd, RESPONSE_404, strlen(RESPONSE_404)); |
|
|
|
close(file); |
|
free_res(loop, client); |
|
|
|
return 0; |
|
} |
|
|
|
char file_ext[50]; |
|
get_extension(file_path, file_ext); |
|
char content_type[50]; |
|
get_content_type(file_ext, content_type); |
|
|
|
int file_len = info.st_size; |
|
char head_msg[200] = ""; |
|
sprintf(head_msg, RESPONSE_TEMPLATE, content_type, file_len); |
|
write(client->fd, head_msg, strlen(head_msg)); |
|
|
|
int read_count; |
|
int buf_size = 8 * 1024;//8096; |
|
char buffer[buf_size + 1]; |
|
while ((read_count = read(file, buffer, buf_size)) > 0) { |
|
int bytes_left = read_count; |
|
char *ptr = buffer; |
|
int need_break = 0; |
|
while (bytes_left > 0) { |
|
ssize_t write_len = write(client->fd, ptr, bytes_left); |
|
|
|
if (write_len == -1) { |
|
fprintf(stderr, "write failed(errno = %d): %s\n", errno, strerror(errno)); |
|
switch (errno) { |
|
case EAGAIN: |
|
case EINTR: |
|
case EINPROGRESS: |
|
fprintf(stderr, "now sleep 0.2s\n"); |
|
ev_sleep(0.2); |
|
break; |
|
default: |
|
need_break = 1; |
|
break; |
|
} |
|
} else if (write_len == 0) { |
|
need_break = 1; |
|
fprintf(stderr, "write_len is zero, and break now\n"); |
|
break; |
|
} else if (write_len < bytes_left) { |
|
bytes_left -= write_len; |
|
ptr += write_len; |
|
fprintf(stderr, "write client with something wrong wtih bytes_left = %d & write_len = %d and write the left data !\n", (int)bytes_left, (int)write_len); |
|
} else { |
|
break; |
|
} |
|
} |
|
|
|
if (need_break) { |
|
break; |
|
} |
|
} |
|
|
|
close(file); |
|
free_res(loop, client); |
|
|
|
return 0; |
|
} |
|
|
|
static int on_url_cb(http_parser *parser, const char *at, size_t length) { |
|
char url_path[length]; |
|
sprintf(url_path, "%.*s", (int)length, at); |
|
|
|
client_t *client = parser->data; |
|
|
|
return handle_static(client, url_path); |
|
} |
|
|
|
int main(int argc, char const *argv[]) { |
|
if(argc == 1){ |
|
fprintf(stderr, "Error: invald path parmeter\n\n"); |
|
printf("Usage:\n ./static_server <your static path>\n\n Example:\n ./static_server ../static\n ./static_server /home/yongboy/your_static_folder\n\nEnjoy it~\n"); |
|
return -1; |
|
} |
|
|
|
static_folder = (char *)argv[1]; |
|
|
|
loop = ev_default_loop(0); |
|
|
|
settings.on_url = on_url_cb; |
|
|
|
struct sockaddr_in listen_addr; |
|
int reuseaddr_on = 1; |
|
int listen_fd = socket(AF_INET, SOCK_STREAM, 0); |
|
if (listen_fd < 0) |
|
err(1, "listen failed\n"); |
|
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on, sizeof(reuseaddr_on)) == -1) |
|
err(1, "setsockopt failed\n"); |
|
|
|
memset(&listen_addr, 0, sizeof(listen_addr)); |
|
listen_addr.sin_family = AF_INET; |
|
listen_addr.sin_addr.s_addr = INADDR_ANY; |
|
listen_addr.sin_port = htons(SERVER_PORT); |
|
|
|
if (bind(listen_fd, (struct sockaddr *) &listen_addr, sizeof(listen_addr)) < 0) |
|
err(1, "bind failed\n"); |
|
if (listen(listen_fd, 5) < 0) |
|
err(1, "listen failed\n"); |
|
if (setnonblock(listen_fd) < 0) |
|
err(1, "failed to set server socket to non-blocking\n"); |
|
|
|
ev_io ev_accept; |
|
ev_io_init(&ev_accept, accept_cb, listen_fd, EV_READ); |
|
ev_io_start(loop, &ev_accept); |
|
ev_loop(loop, 0); |
|
|
|
return 0; |
|
} |
|
|
|
static void free_res(struct ev_loop *loop, client_t *client) { |
|
if (client == NULL) { |
|
fprintf(stderr, "the client is NULL !!!!!!\n"); |
|
return; |
|
} |
|
|
|
ev_io_stop(loop, &client->ev_read); |
|
|
|
close(client->fd); |
|
free(client); |
|
client = NULL; |
|
} |