はじめに
業務で久しぶりにC言語を使うことになったため、復習がてらMongooseというライブラリを使用して簡易なHTTPサーバーを作成しました。
簡単な内容にはなりますが備忘録として残しておきます。
環境
Windows11
Microsoft Visual Studio 2022
事前準備
Mongooseをプロジェクトに追加します。
以下からmongoose.h
とmongoose.c
をダウンロードして適宜インクルードすればOKです。
実装
Mongooseのドキュメントに記載されているサンプルコードをベースに、適宜関数への分割やエラー処理の追加を行い、HTMLを返す新たなAPI/api/html/index
を実装しました。
今回はGETリクエストのみで、他は全てエラーとなります。
main・リクエスト受信部分
各ソースコードに対応するヘッダファイルは記述が少ないので省略しています。
main・Handler
#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__
#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;
}
#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.\"");
}
}
}
/api/html/index
サーバーを起動し、ブラウザでURLにアクセスするとシンプルなHTMLのページが表示されます。
GETのみ実装のため、POST等のリクエストを受信した場合は405エラーを返します。
/api/html/index
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>
#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);
}
/api/hello
に関しては、/api/html/index
と同じような構成で、元のサンプルコードの処理を残したまま405エラーを追加したのみのため省略します。
実行
http://localhost:8000/api/html/index
にブラウザでアクセスすると以下のようなページが表示されます。
Current timeの部分は一秒ごとに更新されていきます。
最後に
Cはエンジニアになりたての時に二ヶ月程度学んだだけで、その後はもっぱらC++を使用していたためかなり書き方を忘れていて妙なミスが多発しました。mallocが得意でないのでvectorなどの動的配列を簡単に作れるクラスが恋しいです。
今回は静的なファイルを返すAPIを作ったので、DBから取得した値を返すなどの動的な値を返すAPIも作成してみたいです。