From dac91796ab90c8addd4f4d7e80eda01ecb2bd8da Mon Sep 17 00:00:00 2001 From: maeken4 Date: Sat, 15 Nov 2025 23:49:47 +0900 Subject: [PATCH 1/2] implement minimum server and client --- .gitignore | 2 + Makefile | 47 +++++++++++++ memo.md | 86 +++++++++++++++++++++++ src/client.c | 69 ++++++++++++++++++ src/http_request.c | 171 +++++++++++++++++++++++++++++++++++++++++++++ src/http_request.h | 17 +++++ src/http_server.c | 86 +++++++++++++++++++++++ 7 files changed, 478 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 memo.md create mode 100644 src/client.c create mode 100644 src/http_request.c create mode 100644 src/http_request.h create mode 100644 src/http_server.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c58ac0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin/* +build/* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..498f1ca --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +CC := gcc +CFLAGS := -std=c11 -Wall -Wextra -O2 -g -I src -MMD -MP +LDFLAGS := +LDLIBS := + +SRCDIR := src +OBJDIR := build +BINDIR := bin + +SRCS_SERVER := $(SRCDIR)/http_server.c $(SRCDIR)/http_request.c +SRCS_CLIENT := $(SRCDIR)/client.c + +OBJS_SERVER := $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRCS_SERVER)) +OBJS_CLIENT := $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRCS_CLIENT)) + +TARGET_SERVER := $(BINDIR)/http_server +TARGET_CLIENT := $(BINDIR)/http_client + +DEPS := $(OBJS_SERVER:.o=.d) $(OBJS_CLIENT:.o=.d) + +.PHONY: all clean + +all: $(TARGET_SERVER) $(TARGET_CLIENT) + +# create bin and build dirs as order-only prerequisites +$(BINDIR): + mkdir -p $(BINDIR) + +$(OBJDIR): + mkdir -p $(OBJDIR) + +# pattern rule to compile .c -> build/%.o +$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR) + $(CC) $(CFLAGS) -c $< -o $@ + +# link targets +$(TARGET_SERVER): $(OBJS_SERVER) | $(BINDIR) + $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@ + +$(TARGET_CLIENT): $(OBJS_CLIENT) | $(BINDIR) + $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@ + +# include dependency files if present +-include $(DEPS) + +clean: + rm -rf $(BINDIR) $(OBJDIR) diff --git a/memo.md b/memo.md new file mode 100644 index 0000000..06cb4a4 --- /dev/null +++ b/memo.md @@ -0,0 +1,86 @@ +# 📝課題: C言語 + system callでHTTP Serverを作ってみよう +``` +GET /calc?query=2+10 HTTP/1.1 +``` +に対して +``` +HTTP/1.1 200 OK +Content-Length: 2 + +12 +``` + +を返すようなもの +## ヒント +- manを使う +- system callのエラーは必ず対処する +- メモリは動的に確保しよう +- クライアントとサーバーの処理が別で必要 + +## 余力があれば挑戦するとよいこと +- IPv4+v6両対応 +- CPU性能を最大限活用できるようにする +- non-blocking化 +- マルチスレッド化 +- 通信タイムアウトの設定 +- SSL対応 +- signalを受け取ったら全コネクションが正常終了するようにする + + + +### 自分の実装計画 + +まず1プロセスで単純なHTTPサーバーを作る。 +- socket, bind, listen, acceptで通信確立 +- request lineの解析 +- headerの解析 +- bobyから計算 + +時間があったら... +CI/CDをちゃんと設定する +→次にワーカープロセスを使った実装 +→次にマルチスレッド +→次にepollによる実装 + +## 調べたものメモ + +### bind(2) +addrをsocketのfdに結び付ける +0: success +-1: error + +sockaddr_in構造体=ポート番号 + +### HTTP形式 +HTTPリクエストは、以下の3つの要素で構成される。 + +- リクエスト行 +- ヘッダーフィールド +- ボディ +ヘッダーフィールド、ボディは省略可能。 +ヘッダーフィールドとボディの間は、空行を1行挟む。 + +https://qiita.com/gunso/items/94c1ce1e53c282bc8d2f#2http%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%81%AE%E6%A7%8B%E6%88%90 + + + +- リクエスト行 + +POST /index.html HTTP/1.0 + +みたいにスペースで区切られる + + + +- headerフィールド +フィールド名:valueの形式 + +--- +HTTPレスポンスは、以下の3つの要素で構成される。 + +ステータス行 +ヘッダーフィールド +ボディ + +### C10k問題 +プロセス作れる数の限界UNIX系ではプロセス数が~32767, 16bit=2^16 diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..2bb1d19 --- /dev/null +++ b/src/client.c @@ -0,0 +1,69 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#define SERVER_ADDR "127.0.0.1" +#define SERVER_PORT "8090" + +int main() { + struct addrinfo hints, *result, *res_p; + int err, client_fd = -1; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET; // IPv4 + hints.ai_socktype = SOCK_STREAM; // TCP + + err = getaddrinfo(SERVER_ADDR, SERVER_PORT, &hints, &result); + if (err != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err)); + exit(EXIT_FAILURE); + } + + for (res_p = result; res_p; res_p = res_p->ai_next) { + client_fd = socket(res_p->ai_family, res_p->ai_socktype, res_p->ai_protocol); + if (client_fd < 0) { + fprintf(stderr, "create socket failed: %s (errno=%d)\n", strerror(errno), errno); + continue; + } + + if (connect(client_fd, res_p->ai_addr, res_p->ai_addrlen) < 0) { + fprintf(stderr, "create connection failed: %s (errno=%d)\n", strerror(errno), errno); + continue; + } + // connect成功 + break; + } + if (client_fd < 0) { + printf("create connection error"); + exit(EXIT_FAILURE); + } + freeaddrinfo(result); + + // http requestの書き込み + // + char request[512]; + int request_len = snprintf(request, sizeof(request), + "GET /calc?query=12+24 HTTP/1.1\r\n" + "Host: %s:%s\r\n" + "User-Agent: simple-c-http-client/1.0\r\n" + "Connection: close\r\n" + "\r\n", + SERVER_ADDR, SERVER_PORT); + send(client_fd, request, request_len, 0); // TODO: 0以外のflagの挙動を調べる + + // http responseの読み取り + // TODO: Status line, header, bodyをちゃんと読み取るようにする + int BUF_SIZE = 500; + char buf[BUF_SIZE]; + int response_len = recv(client_fd, buf, BUF_SIZE, 0); // BUF_SIZEより大きいと動かない + buf[response_len] = '\0'; + + close(client_fd); + printf("%s", buf); +} diff --git a/src/http_request.c b/src/http_request.c new file mode 100644 index 0000000..b2531fc --- /dev/null +++ b/src/http_request.c @@ -0,0 +1,171 @@ +#include "http_request.h" + +#include +#include +#include + +int parse_and_calc(char* calc_query, int* res) { + // calc_queryはquery=2+3の形式 + char* p = strchr(calc_query, '='); + if (strncmp(calc_query, "query", p - calc_query) != 0) { + return -1; + } + ++p; + // TODO: 2+3の部分。本当はもっとちゃんとやる必要があるが後で + char* end; + int val1 = strtol(p, &end, 10); + ++end; + int val2 = strtol(end, &end, 10); + *res = val1 + val2; + return 0; +} + +struct HTTPHeaderField* read_header_field(FILE* in) { + char buf[256]; + char* p; + p = fgets(buf, sizeof(buf), in); + if (p == NULL) { + exit(EXIT_FAILURE); + } + if (strlen(buf) <= 2) { + return NULL; + } + + struct HTTPHeaderField* ret; + ret = malloc(sizeof(*ret)); + p = strchr(buf, ':'); + *p = '\0'; + // :の後の半角スペースをスキップ + p += 2; + ret->field = malloc(strlen(buf) + 1); + strcpy(ret->field, buf); + // value + ret->value = malloc(strlen(p) + 1); + strcpy(ret->value, p); + + ret->next = NULL; + return ret; +} + +struct HTTPRequest* parse_request(FILE* in) { + // 戻り値にするためheap上に確保してポインタを返す + struct HTTPRequest* req; + req = malloc(sizeof(struct HTTPRequest)); + + // read request line + int MAX_STR_LEN = 300; + char buf[MAX_STR_LEN]; + char* err = fgets(buf, MAX_STR_LEN, in); + if (err == NULL) { + return NULL; + } + char *method_end, *uri_end; + + // read method + method_end = strchr(buf, ' '); + *method_end = '\0'; + req->method = malloc(strlen(buf) + 1); + strcpy(req->method, buf); + char* uri = ++method_end; + + // read uri + uri_end = strchr(uri, ' '); + *uri_end = '\0'; + req->uri = malloc(strlen(uri) + 1); + strcpy(req->uri, uri); + char* http_ver = ++uri_end; + + // read http_ver + req->http_ver = malloc(strlen(http_ver) + 1); + strcpy(req->http_ver, http_ver); + + // Request headerの処理 + req->header = read_header_field(in); + struct HTTPHeaderField *tail = req->header, *node; + // 後続にbodyがあるか + long long content_length = -1; + while ((node = read_header_field(in)) != NULL) { + if (strcmp(node->field, "Content-Length") == 0) { + char* end; + content_length = strtol(tail->value, &end, 10); + } + tail->next = node; + tail = node; + } + + // Request bodyの処理 + if (content_length < 0) { + req->body = NULL; + return req; + } + + req->body = malloc(content_length); + int num = fread(req->body, content_length, 1, in); + if (num < content_length * 1) { + fprintf(stderr, "parse request body faild"); + } + return req; +} + +void free_request(struct HTTPRequest* req) { + struct HTTPHeaderField *node, *tail; + + tail = req->header; + while (tail) { + node = tail; + tail = tail->next; + free(node->field); + free(node->value); + free(node); + } + free(req->method); + free(req->uri); + free(req->body); + free(req); +} + +void hundle_http_req(FILE* in, FILE* out) { + struct HTTPRequest* http_req = parse_request(in); + + // -------- debug + printf("%s%s%s", http_req->method, http_req->uri, http_req->http_ver); + struct HTTPHeaderField* node = http_req->header; + while (node != NULL) { + printf("%s: %s", node->field, node->value); + node = node->next; + } + if (http_req->body != NULL) printf("%s", http_req->body); + // -------- debug + + char* query_param; + query_param = strchr(http_req->uri, '?'); + + // pathが正しいか + if (strncmp(http_req->uri, "/calc", query_param - http_req->uri) != 0) { + // TODO: resource not found errorのresponseを返す。 + } + ++query_param; + + int calc_result; + if (parse_and_calc(query_param, &calc_result) < 0) { + // TODO: 入力が不正エラーのresponseを返す。 + } + free_request(http_req); + + char body[64]; + int body_len = snprintf(body, sizeof(body), "%d\n", calc_result); + // output streamに書き込む + fprintf(out, + "HTTP/1.1 200 OK\r\n" + "Content-Length: %d\r\n" + "\r\n" + "%s", + body_len, body); + // debug + printf( + "HTTP/1.1 200 OK\r\n" + "Content-Length: %d\r\n" + "\r\n" + "%s", + body_len, body); +} diff --git a/src/http_request.h b/src/http_request.h new file mode 100644 index 0000000..569bfaf --- /dev/null +++ b/src/http_request.h @@ -0,0 +1,17 @@ +#include + +struct HTTPRequest { + char* http_ver; + char* method; + char* uri; + struct HTTPHeaderField* header; + char* body; +}; + +struct HTTPHeaderField { + char* field; + char* value; + struct HTTPHeaderField* next; +}; + +void hundle_http_req(FILE* http_req, FILE* http_res); diff --git a/src/http_server.c b/src/http_server.c new file mode 100644 index 0000000..8f45a9b --- /dev/null +++ b/src/http_server.c @@ -0,0 +1,86 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main() { + // この段階ではhintsはスタック領域上に確保、resultどこも指していないポインタ。 + struct addrinfo hints, *result, *res_p; + int err, server_fd = -1; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET; // IPv4 + hints.ai_socktype = SOCK_STREAM; // TCP + hints.ai_flags = AI_PASSIVE; // bind用 + // result(ポインタ)へのポインタを渡す(getaddrinfoの第4引数はポインタへのポインタ) + err = getaddrinfo(NULL, "8090", &hints, &result); + if (err != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err)); + exit(EXIT_FAILURE); + } + for (res_p = result; res_p; res_p = res_p->ai_next) { + // clientと通信を確立する用のソケットを作成 + server_fd = socket(res_p->ai_family, res_p->ai_socktype, res_p->ai_protocol); + if (server_fd < 0) { + fprintf(stderr, "create socket failed: %s (errno=%d)\n", strerror(errno), errno); + continue; + } + + // bindが解放されなくなる対策 + const int one = 1; + setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)); + // socketにポート番号をバインドする + err = bind(server_fd, res_p->ai_addr, res_p->ai_addrlen); + if (err < 0) { + fprintf(stderr, "bind failed: %s (errno=%d)\n", strerror(errno), errno); + close(server_fd); + continue; + } + + // listen状態にする + int QUEUE_CONN_NUM = 10; + err = listen(server_fd, QUEUE_CONN_NUM); + if (err < 0) { + fprintf(stderr, "listen failed: %s (errno=%d)\n", strerror(errno), errno); + exit(EXIT_FAILURE); + } + break; + } + freeaddrinfo(result); + + if (server_fd < 0) { + exit(EXIT_FAILURE); + } + for (;;) { + printf("Waiting connect...\n"); + // これらの情報は使わないから要らない? + struct sockaddr addr; + socklen_t addrlen; + // listen socketは接続を受け付けるだけで、接続済みのソケットは別になる + int sock_fd = accept(server_fd, &addr, &addrlen); + if (sock_fd < 0) { + exit(EXIT_FAILURE); + } + + // fdopen: ファイルディスクリプタを扱いやすいFILE*でラップする + FILE* input_file_stream = fdopen(sock_fd, "r"); + int dup_fd = dup(sock_fd); + if (dup_fd < 0) { + perror("dup"); + fclose(input_file_stream); + continue; + } + FILE* output_file_stream = fdopen(dup_fd, "w"); + hundle_http_req(input_file_stream, output_file_stream); + fflush(output_file_stream); + close(sock_fd); + } + close(server_fd); +} From 6186da02891b6744afc235767520ab8305b9d6b6 Mon Sep 17 00:00:00 2001 From: maeken4 Date: Mon, 9 Mar 2026 08:46:15 +0900 Subject: [PATCH 2/2] apply review comments(not implemented: parse http response with dynamic memory allocation) --- Makefile | 2 +- memo.md | 45 +++++++++++++------ src/{client.c => http_client.c} | 17 ++++++- src/http_request.c | 79 +++++++++++++++++++++++++-------- src/http_request.h | 2 +- src/http_server.c | 29 +++++++++--- 6 files changed, 131 insertions(+), 43 deletions(-) rename src/{client.c => http_client.c} (80%) diff --git a/Makefile b/Makefile index 498f1ca..aa036f0 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ OBJDIR := build BINDIR := bin SRCS_SERVER := $(SRCDIR)/http_server.c $(SRCDIR)/http_request.c -SRCS_CLIENT := $(SRCDIR)/client.c +SRCS_CLIENT := $(SRCDIR)/http_client.c OBJS_SERVER := $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRCS_SERVER)) OBJS_CLIENT := $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRCS_CLIENT)) diff --git a/memo.md b/memo.md index 06cb4a4..dc53264 100644 --- a/memo.md +++ b/memo.md @@ -42,7 +42,7 @@ CI/CDをちゃんと設定する →次にマルチスレッド →次にepollによる実装 -## 調べたものメモ +## 調べたもの ### bind(2) addrをsocketのfdに結び付ける @@ -51,7 +51,7 @@ addrをsocketのfdに結び付ける sockaddr_in構造体=ポート番号 -### HTTP形式 +### HTTP リクエスト HTTPリクエストは、以下の3つの要素で構成される。 - リクエスト行 @@ -61,26 +61,43 @@ HTTPリクエストは、以下の3つの要素で構成される。 ヘッダーフィールドとボディの間は、空行を1行挟む。 https://qiita.com/gunso/items/94c1ce1e53c282bc8d2f#2http%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%81%AE%E6%A7%8B%E6%88%90 +https://www.rfc-editor.org/rfc/rfc9112.html - -- リクエスト行 - +### HTTP レスポンス +- HTTPリクエストと同様に、 +``` POST /index.html HTTP/1.0 - +``` みたいにスペースで区切られる - - - headerフィールド フィールド名:valueの形式 ---- -HTTPレスポンスは、以下の3つの要素で構成される。 - -ステータス行 -ヘッダーフィールド -ボディ +- HTTPレスポンスは、以下の3つの要素で構成される。 + - ステータス行 + - ヘッダーフィールド + - ボディ ### C10k問題 プロセス作れる数の限界UNIX系ではプロセス数が~32767, 16bit=2^16 + +### bindとTIME_WAITについて +TCPにおいて、切断後、一定時間はソケット状態を維持するため、アプリを再起動したときにすぐに同じポートを再利用できない。`SO_REUSEADDR`を設定するとうまくいく +``` +// bindが解放されなくなる対策 +const int one = 1; +// 第2引数で設定したいレイヤー、第3引数でそのレイヤーで設定したいオプション、第4引数でそのオプションの値を入れる。 +setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)); +``` +https://qiita.com/bamchoh/items/1dd44ba1fbef43b5284b +https://bombrary.github.io/blog/posts/socket01-file-tcp/ + +## 頂いたコメント +- clientからhttp_requestを送るときにconnectまで成功して、いざsend使用する直前に切断されてしまった場合、そのままsendするとSIGPIPEシグナルが飛んできてプロセスが終了してしまう。 + >サーバー側のコネクションがcloseしているとここでプロセスが落ちるのでMSG_NOSIGNALをつけてベットエラーハンドリングをした方が良いかもしれないです。 + + >MSG_NOSIGNAL (Linux 2.2 以降) + ストリーム指向のソケットで相手側が接続を切断した時に、エラーとして SIGPIPE を送信しないように要求する。 + https://ja.manpages.org/send/2 + diff --git a/src/client.c b/src/http_client.c similarity index 80% rename from src/client.c rename to src/http_client.c index 2bb1d19..a2280a6 100644 --- a/src/client.c +++ b/src/http_client.c @@ -39,6 +39,7 @@ int main() { // connect成功 break; } + // TODO: ここいらない? if (client_fd < 0) { printf("create connection error"); exit(EXIT_FAILURE); @@ -46,7 +47,6 @@ int main() { freeaddrinfo(result); // http requestの書き込み - // char request[512]; int request_len = snprintf(request, sizeof(request), "GET /calc?query=12+24 HTTP/1.1\r\n" @@ -55,12 +55,25 @@ int main() { "Connection: close\r\n" "\r\n", SERVER_ADDR, SERVER_PORT); - send(client_fd, request, request_len, 0); // TODO: 0以外のflagの挙動を調べる + // send: 送り切るまで + int sent = 0; + while (sent < request_len) { + int n = send(client_fd, request + sent, request_len - sent, MSG_NOSIGNAL); + if (n < 0) { + if (errno == EINTR) continue; // interrupted system call + perror("send"); + close(client_fd); + exit(1); + } + sent += n; + } // http responseの読み取り // TODO: Status line, header, bodyをちゃんと読み取るようにする + // TODO: 動的にメモリ確保をする int BUF_SIZE = 500; char buf[BUF_SIZE]; + // TODO: ここもBUFF_SIZE受け取れているとは限らない... int response_len = recv(client_fd, buf, BUF_SIZE, 0); // BUF_SIZEより大きいと動かない buf[response_len] = '\0'; diff --git a/src/http_request.c b/src/http_request.c index b2531fc..af03975 100644 --- a/src/http_request.c +++ b/src/http_request.c @@ -3,20 +3,48 @@ #include #include #include +#include +#include int parse_and_calc(char* calc_query, int* res) { + if (calc_query == NULL || res == NULL) { + return -1; + } // calc_queryはquery=2+3の形式 - char* p = strchr(calc_query, '='); - if (strncmp(calc_query, "query", p - calc_query) != 0) { + // // queryがqueでもよくなってしまっている。あと=がない場合にまずい + // char* p = strchr(calc_query, '='); + // if (strncmp(calc_query, "query", p - calc_query) != 0) { + // return -1; + // } + char* key = "query="; + size_t key_length = strlen(key); + if (strncmp(calc_query, key, key_length) != 0) { return -1; } - ++p; - // TODO: 2+3の部分。本当はもっとちゃんとやる必要があるが後で + char* p = calc_query + key_length; + + // 2+3の形式をチェック char* end; - int val1 = strtol(p, &end, 10); + long val1 = strtol(p, &end, 10); + if (errno == ERANGE || val1 < INT_MIN || val1 > INT_MAX) { + return -1; + } + if (*end != '+') { + return -1; + } ++end; - int val2 = strtol(end, &end, 10); - *res = val1 + val2; + long val2 = strtol(end, &end, 10); + if (errno == ERANGE || val2 < INT_MIN || val2 > INT_MAX) { + return -1; + } + if (*end != '\0') { + return -1; + } + long sum = val1 + val2; + if (sum > INT_MAX || sum < INT_MIN) { + return -1; + } + *res = (int)sum; return 0; } @@ -87,7 +115,8 @@ struct HTTPRequest* parse_request(FILE* in) { while ((node = read_header_field(in)) != NULL) { if (strcmp(node->field, "Content-Length") == 0) { char* end; - content_length = strtol(tail->value, &end, 10); + // この長さも確認したほうがよい + content_length = strtol(node->value, &end, 10); } tail->next = node; tail = node; @@ -108,15 +137,24 @@ struct HTTPRequest* parse_request(FILE* in) { } void free_request(struct HTTPRequest* req) { - struct HTTPHeaderField *node, *tail; - - tail = req->header; - while (tail) { - node = tail; - tail = tail->next; + // struct HTTPHeaderField *node, *tail; + + // tail = req->header; + // while (tail) { + // node = tail; + // tail = tail->next; + // free(node->field); + // free(node->value); + // free(node); + // } + + struct HTTPHeaderField *node = req->header, *next_node; + while (node) { + next_node = node->next; free(node->field); free(node->value); free(node); + node = next_node; } free(req->method); free(req->uri); @@ -124,7 +162,7 @@ void free_request(struct HTTPRequest* req) { free(req); } -void hundle_http_req(FILE* in, FILE* out) { +void handle_http_req(FILE* in, FILE* out) { struct HTTPRequest* http_req = parse_request(in); // -------- debug @@ -141,14 +179,19 @@ void hundle_http_req(FILE* in, FILE* out) { query_param = strchr(http_req->uri, '?'); // pathが正しいか - if (strncmp(http_req->uri, "/calc", query_param - http_req->uri) != 0) { - // TODO: resource not found errorのresponseを返す。 + // if (strncmp(http_req->uri, "/calc", query_param - http_req->uri) != 0) { + // /caでも通っちゃう!!! + // // TODO: resource not found errorのresponseを返す。 + // } + char* calc_path = "/calc"; + if (strncmp(http_req->uri, calc_path, strlen(calc_path)) != 0) { + fprintf(out, "HTTP/1.1 404 Not Found"); } ++query_param; int calc_result; if (parse_and_calc(query_param, &calc_result) < 0) { - // TODO: 入力が不正エラーのresponseを返す。 + fprintf(out, "HTTP/1.1 400 Bad Request"); } free_request(http_req); diff --git a/src/http_request.h b/src/http_request.h index 569bfaf..2997c10 100644 --- a/src/http_request.h +++ b/src/http_request.h @@ -14,4 +14,4 @@ struct HTTPHeaderField { struct HTTPHeaderField* next; }; -void hundle_http_req(FILE* http_req, FILE* http_res); +void handle_http_req(FILE* http_req, FILE* http_res); diff --git a/src/http_server.c b/src/http_server.c index 8f45a9b..3bcea9e 100644 --- a/src/http_server.c +++ b/src/http_server.c @@ -10,8 +10,11 @@ #include #include +#define HOST "localhost" +#define PORT "8090" + int main() { - // この段階ではhintsはスタック領域上に確保、resultどこも指していないポインタ。 + // この段階ではhintsはスタック領域上に確保、resultはどこも指していないポインタ struct addrinfo hints, *result, *res_p; int err, server_fd = -1; @@ -19,12 +22,15 @@ int main() { hints.ai_family = AF_INET; // IPv4 hints.ai_socktype = SOCK_STREAM; // TCP hints.ai_flags = AI_PASSIVE; // bind用 + // result(ポインタ)へのポインタを渡す(getaddrinfoの第4引数はポインタへのポインタ) - err = getaddrinfo(NULL, "8090", &hints, &result); + // resultは利用可能なアドレス情報(addrinfo)の連結リスト + err = getaddrinfo(HOST, PORT, &hints, &result); if (err != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err)); exit(EXIT_FAILURE); } + for (res_p = result; res_p; res_p = res_p->ai_next) { // clientと通信を確立する用のソケットを作成 server_fd = socket(res_p->ai_family, res_p->ai_socktype, res_p->ai_protocol); @@ -35,6 +41,7 @@ int main() { // bindが解放されなくなる対策 const int one = 1; + // 第2引数で設定したいレイヤー、第3引数でそのレイヤーで設定したいオプション、第4引数でそのオプションの値を入れる。 setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)); // socketにポート番号をバインドする err = bind(server_fd, res_p->ai_addr, res_p->ai_addrlen); @@ -63,10 +70,15 @@ int main() { // これらの情報は使わないから要らない? struct sockaddr addr; socklen_t addrlen; - // listen socketは接続を受け付けるだけで、接続済みのソケットは別になる + + // listen socketは接続を受け付けるだけで、接続済みのソケットは別になる int sock_fd = accept(server_fd, &addr, &addrlen); if (sock_fd < 0) { - exit(EXIT_FAILURE); + if (errno == EINTR) { + // EINTRはシグナルで割り込まれただけなので再試行する + continue; + } + perror("accept"); } // fdopen: ファイルディスクリプタを扱いやすいFILE*でラップする @@ -78,9 +90,12 @@ int main() { continue; } FILE* output_file_stream = fdopen(dup_fd, "w"); - hundle_http_req(input_file_stream, output_file_stream); - fflush(output_file_stream); - close(sock_fd); + handle_http_req(input_file_stream, output_file_stream); + // fcloseに統一 + // fflush(output_file_stream); + // close(sock_fd); + fclose(input_file_stream); + fclose(output_file_stream); } close(server_fd); }