210 lines
5.0 KiB
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
|
|
}
|
|
}
|