-
Notifications
You must be signed in to change notification settings - Fork 0
implement minimum server and client #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| bin/* | ||
| build/* |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| #define _GNU_SOURCE | ||
| #include <errno.h> | ||
| #include <netdb.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <sys/socket.h> | ||
| #include <sys/types.h> | ||
| #include <unistd.h> | ||
|
|
||
| #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" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. KeepAlive ではないのでConnection: closeをつけているのが丁寧で良いと思いました! |
||
| "\r\n", | ||
| SERVER_ADDR, SERVER_PORT); | ||
| send(client_fd, request, request_len, 0); // TODO: 0以外のflagの挙動を調べる | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. サーバー側のコネクションがcloseしているとここでプロセスが落ちるので
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
https://stackoverflow.com/questions/8900474/when-will-send-return-less-than-the-length-argument |
||
|
|
||
| // 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より大きいと動かない | ||
|
Comment on lines
+63
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mallocで動的にメモリを確保してレスポンスを受け取る書き方も試しても良いかもしれません。 |
||
| buf[response_len] = '\0'; | ||
|
|
||
| close(client_fd); | ||
| printf("%s", buf); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| #include "http_request.h" | ||
|
|
||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
|
|
||
| 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) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| 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); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
node = req->header;
while (node) {
next_node = node->next;
free(node->field);
free(node->value);
free(node);
node = next_node;
} |
||
| free(req->method); | ||
| free(req->uri); | ||
| free(req->body); | ||
|
Comment on lines
+121
to
+123
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| free(req); | ||
| } | ||
|
|
||
| void hundle_http_req(FILE* in, FILE* out) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| // 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); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| #include <stdio.h> | ||
|
|
||
| struct HTTPRequest { | ||
| char* http_ver; | ||
| char* method; | ||
| char* uri; | ||
| struct HTTPHeaderField* header; | ||
| char* body; | ||
| }; | ||
|
|
||
| struct HTTPHeaderField { | ||
| char* field; | ||
| char* value; | ||
| struct HTTPHeaderField* next; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LinkedListのような感じでヘッダーを最後の行まで見る方法は思いつかなかったので勉強になりました。 |
||
| }; | ||
|
|
||
| void hundle_http_req(FILE* http_req, FILE* http_res); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
サーバー側の名前が
http_server.cなのでhttp_client.cだとわかりやすいと思いました!