Created
July 31, 2022 00:29
-
-
Save frodo821/20d3165505aa07243aaf3ed753781913 to your computer and use it in GitHub Desktop.
システムプログラミング 第8週 練習問題802 のコードを供養
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <sys/socket.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <netinet/in.h> | |
#include <string.h> | |
#include <ctype.h> | |
#include <stdlib.h> | |
#include <arpa/inet.h> | |
#include <unistd.h> | |
#include <signal.h> | |
#include <sys/types.h> | |
#include <sys/wait.h> | |
#define HASHTABLE_SIZE 128 | |
#define SERVER_BUFFER 1024 | |
#define SOCK_BACKLOG 10 | |
#define MAX_CLIENTS 10 | |
// ハンドラプロセスのPIDを格納する | |
pid_t handlers[MAX_CLIENTS]; | |
/** | |
* @brief ハッシュマップのノード。この構造体にキーと値を格納する。 | |
*/ | |
typedef struct hashmap_node | |
{ | |
char key[512]; | |
char value[SERVER_BUFFER]; | |
struct hashmap_node *next; | |
} hashmap_node_t; | |
/** | |
* @brief ハッシュマップ。文字列に対応する文字列値を格納する。 | |
*/ | |
typedef struct hashmap | |
{ | |
hashmap_node_t *table[HASHTABLE_SIZE]; | |
} hashmap_t; | |
/** | |
* @brief 与えられた文字列のハッシュ値を計算する | |
* | |
* @param key ハッシュ値を計算する文字列 | |
* @return int 1~HASHTABLE_SIZEまでのハッシュ値 | |
*/ | |
int calc_hash(const char *key) | |
{ | |
int hash = 0; | |
while (*key != '\0') | |
{ | |
hash = (hash * 33 + *key) % HASHTABLE_SIZE; | |
key++; | |
} | |
return hash % HASHTABLE_SIZE; | |
} | |
/** | |
* @brief 与えられた文字列をすべて小文字に変換する | |
* | |
* @param str 小文字に変換する文字列 | |
* @return char* 元の引数のポインタ | |
*/ | |
char *to_lower_case(char *str) | |
{ | |
for (char *cptr = str; *cptr; cptr++) | |
{ | |
*cptr = tolower(*cptr); | |
} | |
return str; | |
} | |
/** | |
* @brief 新しくハッシュマップを生成する | |
* | |
* @return hashmap_t* 生成したハッシュマップ | |
*/ | |
hashmap_t *new_hashmap() | |
{ | |
hashmap_t *map = malloc(sizeof(hashmap_t)); | |
memset(map, 0, sizeof(hashmap_t)); | |
return map; | |
} | |
/** | |
* @brief ハッシュマップをメモリ上から削除する | |
* | |
* @param map 削除したいハッシュマップ | |
*/ | |
void finalize_hashmap(hashmap_t *map) | |
{ | |
for (int i = 0; i < HASHTABLE_SIZE; i++) | |
{ | |
hashmap_node_t *node = map->table[i]; | |
hashmap_node_t *next; | |
while (node != NULL) | |
{ | |
next = node->next; | |
free(node); | |
node = next; | |
} | |
} | |
free(map); | |
} | |
/** | |
* @brief あるハッシュマップに対して、キーに対応する値を格納する。すでにキーが存在する場合は上書き。 | |
* | |
* @param self 格納したいハッシュマップ | |
* @param key キーとなる文字列 | |
* @param value 格納したい値 | |
* @return hashmap_t* ハッシュマップへのポインタ。正しく格納できた場合はselfと一致する。そうでなければNULL。 | |
*/ | |
hashmap_t *set_item(hashmap_t *self, const char *key, const char *value) | |
{ | |
if (strlen(key) > 512 || strlen(value) > SERVER_BUFFER) | |
{ | |
return NULL; | |
} | |
char *key_cpy = malloc(strlen(key) + 1); | |
strcpy(key_cpy, key); | |
key_cpy[strlen(key)] = '\0'; | |
to_lower_case(key_cpy); | |
int hash = calc_hash(key_cpy); | |
hashmap_node_t *node = self->table[hash]; | |
if (node == NULL) | |
{ | |
self->table[hash] = malloc(sizeof(hashmap_node_t)); | |
memset(self->table[hash], 0, sizeof(hashmap_node_t)); | |
strcpy(self->table[hash]->key, key_cpy); | |
strcpy(self->table[hash]->value, value); | |
return self; | |
} | |
while (node->next != NULL) | |
{ | |
if (strcmp(node->key, key_cpy) == 0) | |
{ | |
memset(node->value, 0, SERVER_BUFFER); | |
strcpy(node->value, value); | |
return self; | |
} | |
node = node->next; | |
} | |
node->next = malloc(sizeof(hashmap_node_t)); | |
memset(node->next, 0, sizeof(hashmap_node_t)); | |
strcpy(node->next->key, key_cpy); | |
strcpy(node->next->value, value); | |
return self; | |
} | |
/** | |
* @brief ハッシュマップから値を取得する。取得した値は読み取り専用。 | |
* | |
* @param self 値を取得したいハッシュマップ | |
* @param key 取得したい値に対応するキー。 | |
* @return char* キーに対応する値。キーが存在しない場合はNULL。 | |
*/ | |
char *get_value(hashmap_t *self, const char *key) | |
{ | |
char *key_cpy = malloc(strlen(key) + 1); | |
strcpy(key_cpy, key); | |
to_lower_case(key_cpy); | |
int hash = calc_hash(key_cpy); | |
hashmap_node_t *node = self->table[hash]; | |
if (node == NULL) | |
{ | |
return NULL; | |
} | |
while (node != NULL) | |
{ | |
if (strcmp(node->key, key_cpy) == 0) | |
{ | |
return node->value; | |
} | |
node = node->next; | |
} | |
return NULL; | |
} | |
/** | |
* @brief HTTPのレスポンスを格納する構造体 | |
*/ | |
typedef struct http_response | |
{ | |
hashmap_t *headers; | |
int status_code; | |
size_t body_length; | |
char body[]; | |
} http_response_t; | |
/** | |
* @brief HTTPのリクエストを格納する構造体 | |
*/ | |
typedef struct http_request | |
{ | |
char method[10]; | |
char path[SERVER_BUFFER]; | |
char version[10]; | |
size_t body_length; | |
hashmap_t *headers; | |
char body[]; | |
} http_request_t; | |
/** | |
* @brief 空のHTTPリクエストを生成する | |
* | |
* @return http_request_t* 生成したHTTPリクエスト | |
*/ | |
http_request_t *initialize_http_request() | |
{ | |
http_request_t *request = malloc(sizeof(http_request_t)); | |
memset(request, 0, sizeof(http_request_t)); | |
request->headers = new_hashmap(); | |
return request; | |
} | |
/** | |
* @brief HTTPリクエストをメモリ上から削除する | |
* | |
* @param request 削除したいHTTPリクエスト | |
*/ | |
void finalize_http_request(http_request_t *request) | |
{ | |
finalize_hashmap(request->headers); | |
free(request); | |
} | |
/** | |
* @brief 空のHTTPレスポンスを生成する | |
* | |
* @return http_response_t* 生成したHTTPレスポンス | |
*/ | |
http_response_t *initialize_http_response() | |
{ | |
http_response_t *response = malloc(sizeof(http_response_t)); | |
memset(response, 0, sizeof(http_response_t)); | |
response->headers = new_hashmap(); | |
return response; | |
} | |
/** | |
* @brief HTTPレスポンスをメモリ上から削除する | |
* | |
* @param response 削除したいHTTPレスポンス | |
*/ | |
void finalize_http_response(http_response_t *response) | |
{ | |
finalize_hashmap(response->headers); | |
free(response); | |
} | |
/** | |
* @brief ファイルパスからファイルのMIMEタイプを推定する | |
* | |
* @param path MIMEタイプを取得したいファイルのパス | |
* @return const char* 推定したMIMEタイプ。推定できなかった場合は application/x-octet-stream が返る。 | |
*/ | |
const char *assume_file_type(const char *path) | |
{ | |
char *ext = strrchr(path, '.'); | |
if (ext == NULL) | |
{ | |
return "application/x-octet-stream"; | |
} | |
if (strcmp(ext, ".html") == 0) | |
{ | |
return "text/html"; | |
} | |
if (strcmp(ext, ".css") == 0) | |
{ | |
return "text/css"; | |
} | |
if (strcmp(ext, ".js") == 0) | |
{ | |
return "application/javascript"; | |
} | |
if (strcmp(ext, ".png") == 0) | |
{ | |
return "image/png"; | |
} | |
if (strcmp(ext, ".jpg") == 0) | |
{ | |
return "image/jpeg"; | |
} | |
if (strcmp(ext, ".gif") == 0) | |
{ | |
return "image/gif"; | |
} | |
if (strcmp(ext, ".ico") == 0) | |
{ | |
return "image/x-icon"; | |
} | |
if (strcmp(ext, ".svg") == 0) | |
{ | |
return "image/svg+xml"; | |
} | |
if (strcmp(ext, ".json") == 0) | |
{ | |
return "application/json"; | |
} | |
if (strcmp(ext, ".txt") == 0) | |
{ | |
return "text/plain"; | |
} | |
return "application/x-octet-stream"; | |
} | |
/** | |
* @brief ステータスコードに対応するステータステキストを取得する | |
* | |
* @param status_code HTTPステータスコード | |
* @return const char* HTTPステータスコードに対応するステータステキスト。対応するものがない場合は Unknown Status が返る。 | |
*/ | |
const char *get_status_text(int status_code) | |
{ | |
switch (status_code) | |
{ | |
case 100: | |
return "Continue"; | |
case 101: | |
return "Switching Protocols"; | |
case 103: | |
return "Early Hints"; | |
case 200: | |
return "OK"; | |
case 201: | |
return "Created"; | |
case 202: | |
return "Accepted"; | |
case 203: | |
return "Non-Authoritative Information"; | |
case 204: | |
return "No Content"; | |
case 205: | |
return "Reset Content"; | |
case 206: | |
return "Partial Content"; | |
case 300: | |
return "Multiple Choices"; | |
case 301: | |
return "Moved Permanently"; | |
case 302: | |
return "Found"; | |
case 303: | |
return "See Other"; | |
case 304: | |
return "Not Modified"; | |
case 307: | |
return "Temporary Redirect"; | |
case 308: | |
return "Permanent Redirect"; | |
case 400: | |
return "Bad Request"; | |
case 401: | |
return "Unauthorized"; | |
case 402: | |
return "Payment Required"; | |
case 403: | |
return "Forbidden"; | |
case 404: | |
return "Not Found"; | |
case 405: | |
return "Method Not Allowed"; | |
case 406: | |
return "Not Acceptable"; | |
case 407: | |
return "Proxy Authentication Required"; | |
case 408: | |
return "Request Timeout"; | |
case 409: | |
return "Conflict"; | |
case 410: | |
return "Gone"; | |
case 411: | |
return "Length Required"; | |
case 412: | |
return "Precondition Failed"; | |
case 413: | |
return "Payload Too Large"; | |
case 414: | |
return "URI Too Long"; | |
case 415: | |
return "Unsupported Media Type"; | |
case 416: | |
return "Range Not Satisfiable"; | |
case 417: | |
return "Expectation Failed"; | |
case 418: | |
return "I'm a teapot"; | |
case 421: | |
return "Misdirected Request"; | |
case 425: | |
return "Too Early"; | |
case 426: | |
return "Upgrade Required"; | |
case 428: | |
return "Precondition Required"; | |
case 429: | |
return "Too Many Requests"; | |
case 431: | |
return "Request Header Fields Too Large"; | |
case 451: | |
return "Unavailable For Legal Reasons"; | |
case 500: | |
return "Internal Server Error"; | |
case 501: | |
return "Not Implemented"; | |
case 502: | |
return "Bad Gateway"; | |
case 503: | |
return "Service Unavailable"; | |
case 504: | |
return "Gateway Timeout"; | |
case 505: | |
return "HTTP Version Not Supported"; | |
case 506: | |
return "Variant Also Negotiates"; | |
case 510: | |
return "Not Extended"; | |
case 511: | |
return "Network Authentication Required"; | |
default: | |
return "Unknown Status"; | |
} | |
} | |
/** | |
* @brief HTTPのレスポンスを送信する | |
* | |
* @param sock_fd 送信するファイル記述子 | |
* @param response 送信するHTTPレスポンス | |
*/ | |
void send_http_response(int sock_fd, http_response_t *response) | |
{ | |
char *status_line = malloc(SERVER_BUFFER); | |
sprintf(status_line, "HTTP/1.1 %d %s\r\n", response->status_code, get_status_text(response->status_code)); | |
send(sock_fd, status_line, strlen(status_line), 0); | |
free(status_line); | |
for (int i = 0; i < HASHTABLE_SIZE; i++) | |
{ | |
hashmap_node_t *node = response->headers->table[i]; | |
char *header = malloc(SERVER_BUFFER * 2); | |
while (node != NULL) | |
{ | |
sprintf(header, "%s: %s\r\n", node->key, node->value); | |
send(sock_fd, header, strlen(header), 0); | |
node = node->next; | |
} | |
free(header); | |
} | |
send(sock_fd, "\r\n", 2, 0); | |
send(sock_fd, response->body, response->body_length, 0); | |
} | |
/** | |
* @brief HTTPのステータスコードに対応するエラーレスポンスを送信する | |
* | |
* @param sock_fd 送信するファイル記述子 | |
* @param status_code HTTPステータスコード | |
*/ | |
void send_http_error(int sock_fd, int status_code) | |
{ | |
http_response_t *response = initialize_http_response(); | |
response->status_code = status_code; | |
set_item(response->headers, "Server", "myserver/1.0"); | |
set_item(response->headers, "Content-Type", "text/plain"); | |
set_item(response->headers, "Connection", "close"); | |
// レスポンスボディ用にステータステキストをフォーマットする | |
char err_buf[SERVER_BUFFER / 2]; | |
sprintf(err_buf, "%s\r\n", get_status_text(status_code)); | |
const int len = strlen(err_buf); | |
char len_buf[SERVER_BUFFER / 2]; | |
sprintf(len_buf, "%d", len); | |
set_item(response->headers, "Content-Length", len_buf); | |
response->body_length = len; | |
response = realloc(response, sizeof(http_response_t) + sizeof(char) * len); | |
strcpy(response->body, err_buf); | |
send_http_response(sock_fd, response); | |
finalize_http_response(response); | |
} | |
/** | |
* @brief リクエストで要求されたファイルを送信する | |
* | |
* @param sock_fd 送信するファイル記述子 | |
* @param request HTTPリクエスト | |
*/ | |
void send_file(int sock_fd, const http_request_t *request) | |
{ | |
char path[SERVER_BUFFER * 3]; | |
char cwd[SERVER_BUFFER]; | |
if (getcwd(cwd, SERVER_BUFFER) == NULL) | |
{ | |
send_http_error(sock_fd, 500); | |
return; | |
} | |
char *host = get_value(request->headers, "Host"); | |
if (host == NULL) | |
{ | |
send_http_error(sock_fd, 404); | |
return; | |
} | |
char host_buf[SERVER_BUFFER]; | |
for (int i = 0; i < strlen(host); i++) | |
{ | |
if (host[i] == ':') | |
{ | |
host_buf[i] = '\0'; | |
break; | |
} | |
host_buf[i] = host[i]; | |
} | |
sprintf(path, "%s/%s%s", cwd, host_buf, request->path); | |
struct stat st; | |
memset(&st, 0, sizeof(st)); | |
if (stat(path, &st) < 0) | |
{ | |
send_http_error(sock_fd, 404); | |
return; | |
} | |
http_response_t *response = initialize_http_response(); | |
response->status_code = 200; | |
char length_buf[SERVER_BUFFER / 2]; | |
sprintf(length_buf, "%ld", st.st_size); | |
set_item(response->headers, "Server", "myserver/1.0"); | |
set_item(response->headers, "Content-Type", assume_file_type(path)); | |
set_item(response->headers, "Content-Length", length_buf); | |
set_item(response->headers, "Connection", "close"); | |
response->body_length = st.st_size; | |
response = realloc(response, sizeof(http_response_t) + sizeof(char) * st.st_size); | |
FILE *file = fopen(path, "r"); | |
if (fread(response->body, st.st_size, 1, file) < 0) | |
{ | |
send_http_error(sock_fd, 500); | |
return; | |
} | |
fclose(file); | |
send_http_response(sock_fd, response); | |
finalize_http_response(response); | |
} | |
/** | |
* @brief HTTPのヘッダを解析する | |
* | |
* @param sock_fd 受信するファイル記述子 | |
* @param request 更新するHTTPリクエストオブジェクト | |
* @return int 成功ステータス。成功した場合のみ0が返る。 | |
*/ | |
int parse_request_header(int sock_fd, http_request_t *request) | |
{ | |
char tmp_buffer[SERVER_BUFFER]; | |
char header_buffer[SERVER_BUFFER]; | |
int buffer_consumed = 0; | |
int is_cr = 0; | |
int recv_size; | |
while (1) | |
{ | |
header: | |
{ | |
buffer_consumed = -1; | |
is_cr = 0; | |
} | |
while (1) | |
{ | |
key_loop: | |
if ((recv_size = recv(sock_fd, &tmp_buffer[++buffer_consumed], 1, MSG_WAITALL)) < 0) | |
{ | |
perror("recv"); | |
return -1; | |
} | |
char k = tmp_buffer[buffer_consumed]; | |
switch (k) | |
{ | |
case ':': | |
tmp_buffer[buffer_consumed] = '\0'; | |
goto whitespaces; | |
case '\r': | |
is_cr = 1; | |
goto key_loop; | |
case '\n': | |
if (is_cr) | |
{ | |
if (buffer_consumed != 1) | |
{ | |
fprintf(stderr, "error: server returned a malformed response. %d\n", buffer_consumed); | |
return -2; | |
} | |
goto end_headers; | |
} | |
default: | |
is_cr = 0; | |
break; | |
} | |
} | |
whitespaces: | |
{ | |
strncpy(header_buffer, tmp_buffer, buffer_consumed); | |
header_buffer[buffer_consumed] = '\0'; | |
} | |
while (1) | |
{ | |
if ((recv_size = recv(sock_fd, tmp_buffer, 1, MSG_WAITALL | MSG_PEEK)) < 0) | |
{ | |
perror("recv"); | |
return -1; | |
} | |
recv(sock_fd, tmp_buffer, 1, 0); | |
char k = tmp_buffer[0]; | |
if (!isblank(k)) | |
{ | |
break; | |
} | |
} | |
buffer_consumed = 0; | |
while (1) | |
{ | |
value_loop: | |
if ((recv_size = recv(sock_fd, &tmp_buffer[++buffer_consumed], 1, MSG_WAITALL)) < 0) | |
{ | |
perror("recv"); | |
return -1; | |
} | |
char k = tmp_buffer[buffer_consumed]; | |
switch (k) | |
{ | |
case '\r': | |
is_cr = 1; | |
buffer_consumed--; | |
goto value_loop; | |
case '\n': | |
if (is_cr) | |
{ | |
tmp_buffer[buffer_consumed] = '\0'; | |
set_item(request->headers, header_buffer, tmp_buffer); | |
goto header; | |
} | |
default: | |
is_cr = 0; | |
break; | |
} | |
} | |
} | |
end_headers: | |
{ | |
return 0; | |
} | |
} | |
/** | |
* @brief HTTPのリクエストを解析する | |
* | |
* @param sock_fd 受信するファイル記述子 | |
* @param status ステータスを返すためのint型ポインタ | |
* @return http_request_t* リクエストを解析した結果 | |
*/ | |
http_request_t *parse_http_request(int sock_fd, int *status) | |
{ | |
http_request_t *request = initialize_http_request(); | |
signed char tmp_buffer[2048]; | |
int buffer_comsumed = 0; | |
// parse request line | |
while (1) | |
{ | |
if (buffer_comsumed > SERVER_BUFFER) | |
{ | |
*status = 400; | |
return NULL; | |
} | |
if (recv(sock_fd, tmp_buffer + buffer_comsumed, 1, 0) < 1) | |
{ | |
*status = 400; | |
return NULL; | |
} | |
if (tmp_buffer[buffer_comsumed] == ' ') | |
{ | |
goto request_path; | |
} | |
if ('a' > (*tmp_buffer | 32) || (*tmp_buffer | 32) > 'z') | |
{ | |
*status = 400; | |
return NULL; | |
} | |
buffer_comsumed++; | |
} | |
request_path: | |
for (int i = 0; i < buffer_comsumed; i++) | |
{ | |
request->method[i] = tmp_buffer[i]; | |
} | |
request->method[buffer_comsumed] = '\0'; | |
buffer_comsumed = 0; | |
while (1) | |
{ | |
if (buffer_comsumed > SERVER_BUFFER) | |
{ | |
*status = 414; | |
return NULL; | |
} | |
if (recv(sock_fd, tmp_buffer + buffer_comsumed, 1, 0) < 1) | |
{ | |
*status = 400; | |
return NULL; | |
} | |
if (tmp_buffer[buffer_comsumed] == ' ') | |
{ | |
goto request_version; | |
} | |
if (tmp_buffer[buffer_comsumed] == '\r' || tmp_buffer[buffer_comsumed] == '\n') | |
{ | |
*status = 400; | |
return NULL; | |
} | |
buffer_comsumed++; | |
} | |
request_version: | |
for (int i = 0; i < buffer_comsumed; i++) | |
{ | |
request->path[i] = tmp_buffer[i]; | |
} | |
request->path[buffer_comsumed] = '\0'; | |
buffer_comsumed = 0; | |
while (1) | |
{ | |
if (recv(sock_fd, tmp_buffer + buffer_comsumed, 1, 0) < 1) | |
{ | |
*status = 400; | |
return NULL; | |
} | |
if (tmp_buffer[buffer_comsumed] == '\r') | |
{ | |
} | |
if (tmp_buffer[buffer_comsumed] == '\n') | |
{ | |
if (buffer_comsumed < 2) | |
{ | |
*status = 400; | |
return NULL; | |
} | |
if (tmp_buffer[buffer_comsumed - 1] == '\r') | |
{ | |
buffer_comsumed -= 1; | |
} | |
else | |
{ | |
buffer_comsumed -= 2; | |
} | |
goto headers; | |
} | |
buffer_comsumed++; | |
} | |
headers: | |
{ | |
if (parse_request_header(sock_fd, request) < 0) | |
{ | |
*status = 400; | |
return NULL; | |
} | |
} | |
const char *length = get_value(request->headers, "Content-Length"); | |
if (length == NULL) | |
{ | |
return request; | |
} | |
for (int i = 0; i < strlen(length); i++) | |
{ | |
if (!isdigit(length[i])) | |
{ | |
*status = 400; | |
return NULL; | |
} | |
} | |
int content_length = atoi(length); | |
request = realloc(request, sizeof(http_request_t) + sizeof(char) * content_length); | |
if (recv(sock_fd, request->body, content_length, MSG_WAITALL) < 0) | |
{ | |
*status = 400; | |
return NULL; | |
} | |
return request; | |
} | |
/** | |
* @brief 指定されたポートにbindされたファイル記述子を返す | |
* | |
* @param port 受信待ちをしたいポート番号 | |
* @return int ファイル記述子。0未満が返った場合、失敗していることを表す。 | |
*/ | |
int start_listening(int port) | |
{ | |
struct sockaddr_in saddr; | |
int sockfd; | |
saddr.sin_family = AF_INET; | |
saddr.sin_port = htons(port); | |
saddr.sin_addr.s_addr = INADDR_ANY; | |
sockfd = socket(AF_INET, SOCK_STREAM, 0); | |
if (sockfd < 0) | |
{ | |
perror("socket"); | |
return -1; | |
} | |
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) | |
{ | |
perror("bind"); | |
return -1; | |
} | |
if (listen(sockfd, SOCK_BACKLOG) < 0) | |
{ | |
perror("listen"); | |
return -1; | |
} | |
return sockfd; | |
} | |
/** | |
* @brief 受信待ち状態のファイル記述子を閉じる | |
* | |
* @param sockfd 受信待ち状態のファイル記述子 | |
* @return int 成功した場合 0 が返る。 | |
*/ | |
int finalize_connection(int sockfd) | |
{ | |
close(sockfd); | |
return 0; | |
} | |
/** | |
* @brief リクエストを処理する | |
* | |
* @param sockfd 受信待ち状態のファイル記述子 | |
* @return int 終了した場合は0が返るが、通常の場合この関数から復帰することはない | |
*/ | |
int serve_forever(int sockfd) | |
{ | |
int status = 0; | |
http_request_t *request = NULL; | |
socklen_t socklen; | |
struct sockaddr_in client_addr; | |
char addr_buffer[46]; | |
memset(&client_addr, 0, sizeof(client_addr)); | |
int client_sockfd; | |
int child_index; | |
while (1) | |
{ | |
if ((client_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &socklen)) < 0) | |
{ | |
perror("accept"); | |
continue; | |
} | |
child_index = -1; | |
for (int i = 0; i < MAX_CLIENTS; i++) | |
{ | |
if (waitpid(handlers[i], NULL, WNOHANG) > 0 || handlers[i] == 0) | |
{ | |
child_index = i; | |
pid_t pid = fork(); | |
if (pid != 0) | |
{ | |
handlers[i] = pid; | |
break; | |
} | |
goto serve_client; | |
} | |
} | |
if (child_index == -1) | |
{ | |
send_http_error(client_sockfd, 503); | |
if (close(client_sockfd) < 0) | |
{ | |
perror("close"); | |
} | |
} | |
} | |
serve_client: | |
if (inet_ntop(client_addr.sin_family, &client_addr.sin_addr, addr_buffer, sizeof(addr_buffer)) == NULL) | |
{ | |
strcpy(addr_buffer, "<unknown>"); | |
} | |
request = parse_http_request(client_sockfd, &status); | |
printf("%s %s %d -- %s\n", request->method, request->path, status, addr_buffer); | |
if (request == NULL) | |
{ | |
send_http_error(client_sockfd, status); | |
} | |
else if (strcmp(request->method, "GET") == 0) | |
{ | |
send_file(client_sockfd, request); | |
} | |
else | |
{ | |
send_http_error(client_sockfd, 405); | |
} | |
close(client_sockfd); | |
finalize_http_request(request); | |
exit(0); | |
} | |
int main(void) | |
{ | |
int sockfd; | |
void keyboard_interrupt_handler(int sig) | |
{ | |
printf("waiting for children...\n"); | |
for (int i = 0; i < MAX_CLIENTS; i++) | |
{ | |
if (handlers[i] != 0) | |
{ | |
waitpid(handlers[i], NULL, 0); | |
} | |
} | |
finalize_connection(sockfd); | |
printf("\n"); | |
exit(0); | |
} | |
if (signal(SIGINT, keyboard_interrupt_handler) == SIG_ERR) | |
{ | |
perror("signal"); | |
return -1; | |
} | |
for (int i = 0; i < MAX_CLIENTS; i++) | |
{ | |
handlers[i] = 0; | |
} | |
printf("Starting server...\n"); | |
int port = 8008; | |
sockfd = start_listening(port); | |
if (sockfd < 0) | |
{ | |
return -1; | |
} | |
printf("Server listening on port %d\n", port); | |
printf("Press Ctrl+C to stop\n"); | |
serve_forever(sockfd); | |
// ここには到達しない | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment