0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-07-14

はじめに

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

環境

Windows11
Microsoft Visual Studio 2022

事前準備

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

実装

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

main・リクエスト受信部分

各ソースコードに対応するヘッダファイルは記述が少ないので省略しています。

main・Handler
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__
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;
}
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.\"");
        }
    }
}

/api/html/index

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

/api/html/index

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>

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);
}

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

実行

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

Index

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

最後に

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

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?