Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions http-c/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
コンパイル・実行(Unix/Linux環境):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/potrue/leetcode/pull/81/changes#r2781804952
もし上記の処理がWindows向けであるならば、ここも変更が必要か検討が必要かと思います


`gcc -o server server.c utils.c`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utils.c が handler.c に変わった?

`./server`

動作確認:

`curl http://127.0.0.1:8080/calc\?query\=1+11` など
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これブラウザからだとどう入力するのが正しくなりますか? (url encoding)

114 changes: 114 additions & 0 deletions http-c/handler.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#include "handler.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define INITIAL_READ_BUFFER_SIZE 1024

static void respond_and_close(int connection_fd, int status_code, const char* body) {
char* status_message = NULL;
switch (status_code) {
case 200: status_message = "OK"; break;
case 400: status_message = "Bad Request"; break;
case 404: status_message = "Not Found"; break;
case 500: status_message = "Internal Server Error"; break;
default: status_message = "Unknown Status"; break;
}

char* response = NULL;
int response_len = asprintf(&response,
"HTTP/1.1 %d %s\r\n"
"Content-Length: %zu\r\n"
"\r\n"
"%s",
status_code, status_message, strlen(body), body
);

if (response_len == -1) {
perror("asprintf");
close(connection_fd);
return;
}

write(connection_fd, response, response_len);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

write が失敗したり、部分的にだけ書き込みが成功した場合に対応したい。

free(response);
close(connection_fd);
}

static char* read_request(int connection_fd) {
size_t capacity = INITIAL_READ_BUFFER_SIZE;
char* read_buffer = malloc(capacity);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

malloc が NULL を返したら?

size_t total_read_size = 0;

while (1) {
ssize_t read_size = read(connection_fd, read_buffer + total_read_size, capacity - total_read_size - 1);
if (read_size < 0) {
perror("read");
free(read_buffer);
return NULL;
}
if (read_size == 0) {
break;
}

// "\r\n\r\n" (4文字) が読み込み境界を跨ぐ可能性を考慮し、3バイト戻ったところから探索する
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここから64行目までの処理は特定のOSのための処理でしょうか?
\r\nが出てくるとWindows向けの特別な処理をやっているように見えました。(\r\nがWindowsの改行文字なので)もし特別処理であればその処理だけ関数別にしたほうがわかりやすい気がします。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(正直書いた時は詳しく把握できていませんでしたが、)これは送られてきているリクエストからヘッダーを切り離す(空行を見つける)処理で、RFC9112で改行は\r\n(CRLF)で表すように決められているみたいなので、特にOS固有の処理ではなく、ヘッダーがここまでだというのを判断するためにOS共通で使用しているロジック、という感じです。
プログラムを書いた時には全然見られていませんでしたが、本当はこちらに書いてある"The normal procedure"に従ってリクエストを読み込むのが良さそうかもしれません。

size_t str_search_offset = total_read_size > 3 ? total_read_size - 3 : 0;

total_read_size += read_size;
read_buffer[total_read_size] = '\0';

if (strstr(read_buffer + str_search_offset, "\r\n\r\n")) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

データを全部読み切らずに、改行2つで早期に止めるのはどういうケースを想定をしていますか?
(keep alive?)

break;
}

if (total_read_size == capacity - 1) {
capacity *= 2;
char* new_read_buffer = realloc(read_buffer, capacity);
if (new_read_buffer == NULL) {
perror("realloc");
free(read_buffer);
return NULL;
}
read_buffer = new_read_buffer;
}
}

return read_buffer;
}

void handle_connection(int connection_fd) {
const char* path_prefix = "GET /calc";
const char* query_prefix = "?query=";

char* read_buffer = read_request(connection_fd);
if (read_buffer == NULL) {
respond_and_close(connection_fd, 500, "Something went wrong\n");
return;
}

if (strncmp(read_buffer, path_prefix, strlen(path_prefix)) != 0) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/calc で始まれば、他の path でも区別して動いてしまう
GET /calcium?hoge=...

respond_and_close(connection_fd, 404, "The requested URL was not found on this server\n");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

しっかり追えているか不安ですが、ここでfree(read_buffer);したほうがメモリリークがないかもです

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あ、これは必要そうですね!メモリリークを起こしていそうです。L97にも必要だと思います。

return;
}

if (strncmp(read_buffer + strlen(path_prefix), query_prefix, strlen(query_prefix)) != 0) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

query 引数が第1引数だった時以外は動かない?
/calc?utm_source=github&query=1+2

respond_and_close(connection_fd, 400, "Failed to find query");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここも同様です。free(read_buffer);したほうがメモリリークがないかもです

return;
}

char* query_start = read_buffer + strlen(path_prefix) + strlen(query_prefix);
int a, b;
if (sscanf(query_start, "%d+%d", &a, &b) != 2) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

足し算以外のケースも対応したい

respond_and_close(connection_fd, 400, "Failed to parse as a valid expression\n");
free(read_buffer);
return;
}

int result = a + b;
char body[64];
snprintf(body, sizeof(body), "%d\n", result);
respond_and_close(connection_fd, 200, body);
free(read_buffer);
}
6 changes: 6 additions & 0 deletions http-c/handler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef UTILS_H
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HANDLER_H?

#define UTILS_H

void handle_connection(int connection_fd);

#endif
51 changes: 51 additions & 0 deletions http-c/server.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include "handler.h"

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>

#define PORT 8080
#define BACKLOG_SIZE 8

int main() {
int socket_fd = socket(PF_INET, SOCK_STREAM, 0);
if (socket_fd < 0) {
perror("socket");
return 1;
}

struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(PORT),
.sin_addr.s_addr = INADDR_ANY,
};

if (bind(socket_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

close(socket_fd);をしておいた方が良いと思います

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これもあったほうが良さそうです。

return 1;
}

if (listen(socket_fd, BACKLOG_SIZE) < 0) {
perror("listen");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

close(socket_fd);をしておいた方が良いと思います

return 1;
}

while (1) {
struct sockaddr_in client_addr;
socklen_t client_addr_size = sizeof(client_addr);

int connection_fd = accept(socket_fd, (struct sockaddr*)&client_addr, &client_addr_size);
if (connection_fd < 0) {
perror("accept");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EINTR などの一時的なエラーの場合はリカバリしたい。

return 1;
}

handle_connection(connection_fd);
}

close(socket_fd);

return 0;
}