Mongooseを使用したC言語のHTTPサーバー

# はじめに
業務で久しぶりにC言語を使うことになったため、復習がてらMongooseというライブラリを使用して簡易なHTTPサーバーを作成しました。
簡単な内容にはなりますが備忘録として残しておきます。

# 環境
Windows11
Microsoft Visual Studio 2022

# 事前準備
Mongooseをプロジェクトに追加します。
以下から`mongoose.h`と`mongoose.c`をダウンロードして適宜インクルードすればOKです。

https://github.com/cesanta/mongoose/tree/master

# 実装
[Mongooseのドキュメント](https://mongoose.ws/documentation/#minimal-http-server)に記載されているサンプルコードをベースに、適宜関数への分割やエラー処理の追加を行い、HTMLを返す新たなAPI`/api/html/index`を実装しました。
今回はGETリクエストのみで、他は全てエラーとなります。

### main・リクエスト受信部分
各ソースコードに対応するヘッダファイルは記述が少ないので省略しています。

<details><summary>main・Handler</summary>

```c:header.h
#ifndef __HEADER__H__
#define __HEADER__H__

#include <stdio.h>
#include <stdlib.h>
#include "../lib/mongoose/mongoose.h"

// 共通で使用するエラーコードや、URLをまとめて定義（全ファイルでインクルードする）
typedef enum
{
	OK = 200,
	INTERNAL_SERVER_ERROR = 500,
	BAD_REQUEST = 400,
	NOT_FOUND = 404,
	METHOD_NOT_ALLOWED = 405
} StatusCode;

#define HTTP_METHOD_GET "GET"

#define BASE_URL "http://0.0.0.0:8000"

#define API_HELLO_PATH "/api/hello"
#define API_HTML_INDEX_PATH "/api/html/index"

#endif // !__HEADER__H__
```

```c:main.c
#include "../Include/Header.h"
#include "../Include/Handler.h"

int main()
{
    struct mg_mgr mgr;
    mg_mgr_init(&mgr);                                      // Init manager
    mg_http_listen(&mgr, BASE_URL, HandleRequest, NULL);  // Setup listener
    for (;;) mg_mgr_poll(&mgr, 1000);                       // Infinite event loop
    mg_mgr_free(&mgr);
    return 0;
}
```

```c:Handler.c
#include "../Include/Handler.h"

void HandleRequest(struct mg_connection* c, int ev, void* ev_data, void* fn_data)
{
    if (ev == MG_EV_HTTP_MSG)
    {
        struct mg_http_message* hm = (struct mg_http_message*)ev_data;
        if (mg_http_match_uri(hm, API_HELLO_PATH)) 
        {
            // /api/hello
            HandleHello(c, hm);
        }
        else if (mg_http_match_uri(hm, API_HTML_INDEX_PATH))
        {
            // /api/html/index
            HandleHTMLIndex(c, hm);
        }
        else
        {
            // 上記以外のパス
            mg_http_reply(c, NOT_FOUND, "Content-Type: application/json\r\n", "{%m:%s}\n",
                MG_ESC("error"), "\"Not Found.\"");
        }
    }
}
```

</details>

### /api/html/index
サーバーを起動し、ブラウザでURLにアクセスするとシンプルなHTMLのページが表示されます。
GETのみ実装のため、POST等のリクエストを受信した場合は405エラーを返します。

<details><summary>/api/html/index</summary>

:::note
`html.index`ファイルはプロジェクトのルートディレクトリに作成した`html`というディレクトリの中に入れています。
:::

```html:index.html
<!DOCTYPE html>
<html>
<head>
    <title>Index</title>
</head>
<body>
    <h1>Index</h1>
    <p>Current time: <span id="time"></span></p>

    <script>
        function updateTime() {
            var now = new Date();
            var hours = now.getHours();
            var minutes = now.getMinutes();
            var seconds = now.getSeconds();

            var timeString = hours.toString().padStart(2, '0') + ':' +
                minutes.toString().padStart(2, '0') + ':' +
                seconds.toString().padStart(2, '0');

            document.getElementById('time').textContent = timeString;
        }

        updateTime();
        setInterval(updateTime, 1000);
    </script>
</body>
</html>

```

```c:html.c
#include "../Include/html.h"

#define INDEX_FILE_PATH "./html/index.html"

void HandleHTMLIndex(struct mg_connection* c, struct mg_http_message* msg)
{
	// GETのみ受け付ける
	if (mg_vcmp(&msg->method, HTTP_METHOD_GET) == 0)
	{
		GetIndex(c);
	}
	else
	{
		mg_http_reply(c, METHOD_NOT_ALLOWED, "Content-Type: application/json\r\n",
			"{%m:%s}\n", MG_ESC("error"), "\"Method not Allowed.\""); // Send dynamic JSON response
	}
}

void GetIndex(struct mg_connection* c)
{
	// HTMLファイルを開く
	FILE* file = fopen(INDEX_FILE_PATH, "rb");
	if (file == NULL)
	{
		mg_http_reply(c, NOT_FOUND, "Content-Type: application/json\r\n",
			"{%m:%s}\n", MG_ESC("error"), "\"File not Found.\"");
		return;
	}

	// ファイルサイズの取得
	fseek(file, 0, SEEK_END);
	long file_size = ftell(file);
	fseek(file, 0, SEEK_SET);

	if (file_size == -1)
	{
		mg_http_reply(c, INTERNAL_SERVER_ERROR, "Content-Type: application/json\r\n",
			"{%m:%s}\n", MG_ESC("error"), "\"Server Error.\"");
		fclose(file);
		return;
	}

	char* fileBuf;
	fileBuf = (char*)malloc(file_size + 1);
	if (fileBuf == NULL)
	{
		mg_http_reply(c, INTERNAL_SERVER_ERROR, "Content-Type: application/json\r\n",
			"{%m:%s}\n", MG_ESC("error"), "\"Server Error.\"");
		fclose(file);
		return;
	}
	
	// ファイルの読み取り・送信
	fread(fileBuf, 1, file_size, file);
	fileBuf[file_size] = '\0';
	mg_http_reply(c, OK, "Content-Type: text/html\r\n", "%s", fileBuf);

	free(fileBuf);

	fclose(file);
}
```

</details>

:::note
`/api/hello`に関しては、`/api/html/index`と同じような構成で、元のサンプルコードの処理を残したまま405エラーを追加したのみのため省略します。
:::

# 実行
`http://localhost:8000/api/html/index`にブラウザでアクセスすると以下のようなページが表示されます。

![Index](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468216/60c83fab-25d5-a1ac-ec3c-95239724cd62.png)

Current timeの部分は一秒ごとに更新されていきます。

# 最後に
Cはエンジニアになりたての時に二ヶ月程度学んだだけで、その後はもっぱらC++を使用していたためかなり書き方を忘れていて妙なミスが多発しました。mallocが得意でないのでvectorなどの動的配列を簡単に作れるクラスが恋しいです。

今回は静的なファイルを返すAPIを作ったので、DBから取得した値を返すなどの動的な値を返すAPIも作成してみたいです。