webserver.c/server.c

210 lines
5.0 KiB
C

#include <errno.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include "request.h"
#include "response.h"
#include "server.h"
#define MAX_BUFFER_SIZE 2048
int sockfd;
struct Server server_constructor(const char *port, const char *static_path) {
struct Server server;
server.port = port;
server.static_path = static_path;
return server;
}
void sigchld_handler(int s) {
int saved_errno = errno;
while (waitpid(-1, NULL, WNOHANG) > 0)
;
errno = saved_errno;
}
void path_from_target(struct Server *server, char *target, char *path) {
strcpy(path, server->static_path);
strcat(path, target);
char *question = strchr(path, '?');
if (question)
*question = '\0';
if (path[strlen(path) - 1] == '/')
strcat(path, "index.html");
char *dot = strchr(path, '.');
if (!dot)
strcat(path, ".html");
}
void get_mime_type(char *path, char *mime) {
char *dot = strchr(path, '.');
if (!dot)
strcpy(mime, "text/html");
else if (strcmp(dot, ".html") == 0)
strcpy(mime, "text/html");
else if (strcmp(dot, ".css") == 0)
strcpy(mime, "text/css");
else if (strcmp(dot, ".js") == 0)
strcpy(mime, "application/js");
else if (strcmp(dot, ".jpg") == 0)
strcpy(mime, "image/jpeg");
else if (strcmp(dot, ".png") == 0)
strcpy(mime, "image/png");
else if (strcmp(dot, ".gif") == 0)
strcpy(mime, "image/gif");
else if (strcmp(dot, ".ico") == 0)
strcpy(mime, "image/x-icon");
else
strcpy(mime, "text/html");
}
int sendall(int s, char *buf, int *len)
{
int total = 0; // how many bytes we've sent
int bytesleft = *len; // how many we have left to send
int n;
while(total < *len) {
n = send(s, buf+total, bytesleft, 0);
if (n == -1) { break; }
total += n;
bytesleft -= n;
}
*len = total; // return number actually sent here
return n==-1?-1:0; // return -1 on failure, 0 on success
}
void handle(struct Server *server, int client_fd) {
char *buf = (char *)malloc(MAX_BUFFER_SIZE * sizeof(char));
recv(client_fd, buf, MAX_BUFFER_SIZE, 0);
struct Request request = request_constructor(buf);
char *path = (char *)malloc(1024 * sizeof(char));
path_from_target(server, request.target, path);
FILE *fp = fopen(path, "rb");
if (fp == NULL) {
const char response[] = "HTTP/1.1 404 Not Found\r\n\r\nNot Found";
send(client_fd, response, sizeof(response), 0);
printf("[%s] %s -> %s %s\n", request.method, request.target, path, "404");
} else {
fseek(fp, 0, SEEK_END);
size_t file_size = ftell(fp);
rewind(fp);
char mime_type[32];
get_mime_type(path, mime_type);
char res_header[1024];
sprintf(res_header, "HTTP/1.1 200 OK\r\nContent-Type: %s\r\n\r\n", mime_type);
int header_size = strlen(res_header);
char *res_buffer = (char *)malloc((file_size + header_size) * sizeof(char));
strcpy(res_buffer, res_header);
fread(res_buffer + header_size, 1, file_size, fp);
int len = file_size + header_size;
if (sendall(client_fd, res_buffer, &len) == -1) {
perror("send content");
exit(1);
}
printf("[%s] %s -> %s %s\n", request.method, request.target, path, "200");
free(res_buffer);
};
fclose(fp);
}
void launch(struct Server *server) {
struct sockaddr_storage client_addr;
socklen_t client_addr_len = sizeof client_addr;
struct addrinfo hints, *res, *p;
memset(&hints, 0, sizeof hints);
hints.ai_family = PF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
int gairv;
if ((gairv = getaddrinfo(NULL, server->port, &hints, &res)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gairv));
exit(1);
}
for (p = res; p != NULL; p = p->ai_next) {
int yes = 1;
if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
perror("server: socket");
continue;
}
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
perror("setsockopt");
continue;
}
if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
perror("server: bind");
continue;
}
break;
}
freeaddrinfo(res);
if (p == NULL) {
fprintf(stderr, "server: failed to bind to any available socket");
exit(1);
}
if (listen(sockfd, BACKLOG) == -1) {
perror("server: listen");
exit(1);
}
struct sigaction sa;
sa.sa_handler = sigchld_handler; // reap all dead processes
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}
int client_fd;
while (1) {
if ((client_fd = accept(sockfd, (struct sockaddr *)&client_addr,
&client_addr_len)) == -1) {
perror("accept");
continue;
}
if (!fork()) {
close(sockfd);
handle(server, client_fd);
close(client_fd);
exit(0);
}
close(client_fd); // parent doesnt need this
}
}