Files
cpp-httplib/docs-src/pages/ja/cookbook/w03-websocket-close.md
yhirose 254e576b50 Add Server::CustomRoute() for HTTP methods outside the built-in set (#2553)
* Add Server::CustomRoute() for HTTP methods outside the built-in set

parse_request_line validates the request method against a fixed whitelist and
rejects anything else with 400 before routing runs. That blocks WebDAV, where
PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK and UNLOCK are ordinary methods
defined by RFC 4918, and it blocks extension methods such as UPnP's SUBSCRIBE.
The need has been open since #847.

Registering a handler is now what makes the server accept a method:

    svr.CustomRoute("PROPFIND", "/dav/:id", handler);

Because custom methods go through the normal dispatch path, patterns work the
way they do for Get() and friends, and the request body is available in
req.body. Serving these methods through set_pre_routing_handler was never
enough: the body has not been read at that point, so PROPPATCH and LOCK, which
require one, could not be implemented at all.

A HandlerWithContentReader overload is available too. The content reader gate
in routing() also fires when a custom method carries no body, matching what
expect_content() does unconditionally for POST/PUT/PATCH/DELETE, so a body-less
PROPFIND (RFC 4918 treats one as allprop) reaches its handler instead of
falling through to 404.

Method names are validated as RFC 9110 tokens, and the ten built-in methods are
refused. Seven of them are dispatched by the if/else chain in routing() before
the custom tables are consulted, so a route registered for one could never
fire; CONNECT, TRACE and PRI carry protocol-level meaning this library does not
route. A refused registration makes is_valid() return false, so listen() fails
rather than starting a server holding a handler that would never run. This is
also why SSLServer::is_valid() now chains to Server::is_valid() instead of only
checking ctx_.

Servers that never call CustomRoute() keep the previous per-request cost: the
built-in method set is checked first and short-circuits, and the custom lookup
returns early on an empty map.

* Add cookbook recipe for custom HTTP methods

The CustomRoute() docs were a section inside S01, which pushed that page to 90
lines, the longest in the cookbook, and mixed a separate feature into a page
about registering GET/POST/PUT/DELETE handlers. Move the section into its own
recipe and give it room for the part that was missing: the OPTIONS handler
returning DAV: and Allow, which WebDAV clients probe for before anything else.
S01 goes back to 68 lines and keeps a pointer to the new page.

The recipe is titled after the API rather than after WebDAV, and says outright
that generating the 207 Multi-Status XML, interpreting Depth and managing locks
are the reader's job. Routing the method is all the library does.

S23 takes order 42, so the TLS, SSE and WebSocket recipes shift to 43-57. That
only moves the sort key. Filenames, the T01/E01/W01 labels, the published URLs
and every cross-reference are untouched.
2026-08-25 19:31:42 -04:00

3.3 KiB
Raw Blame History

title, order, status
title order status
W03. 接続クローズをハンドリングする 54 draft

WebSocket接続は、クライアントかサーバーのどちらかが明示的に閉じるか、ネットワーク障害で切れると終了します。クローズ処理をきちんと書いておくと、リソースの後始末や再接続ロジックがきれいに書けます。

クローズ状態の検出

ws.read()ReadResult::Failを返したら、接続が切れたか何らかのエラーが起きたということです。ループを抜けてハンドラから戻れば、そのWebSocket接続の処理は終わります。

svr.WebSocket("/chat", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
  std::string msg;
  while (ws.is_open()) {
    auto result = ws.read(msg);
    if (result == httplib::ws::ReadResult::Fail) {
      std::cout << "disconnected" << std::endl;
      break;
    }
    handle_message(ws, msg);
  }

  // ここに到達したら後始末
  cleanup_user_session(req);
});

ws.is_open()でも接続状態を確認できます。内部的には同じことを見ています。

サーバー側から閉じる

サーバー側から明示的にクローズしたいときは、close()を呼びます。

ws.close(httplib::ws::CloseStatus::Normal, "bye");

第1引数にクローズステータス、第2引数に理由任意を渡します。クローズステータスはCloseStatus列挙値で、代表的なものはこちらです。

意味
Normal (1000) 通常終了
GoingAway (1001) サーバーが終了するため
ProtocolError (1002) プロトコル違反を検知
UnsupportedData (1003) 対応していないデータを受信
PolicyViolation (1008) ポリシー違反
MessageTooBig (1009) メッセージが大きすぎる
InternalError (1011) サーバー内部エラー

クライアント側から閉じる

クライアント側でも同じAPIが使えます。

cli.close(httplib::ws::CloseStatus::Normal);

cliを破棄したときにも自動的にクローズされますが、明示的にclose()を呼んだほうが意図が伝わりやすいです。

グレースフルシャットダウン

サーバーを停止するときに接続中のクライアントに「これから止まります」と伝えたい場合は、GoingAwayを使います。

ws.close(httplib::ws::CloseStatus::GoingAway, "server restarting");

クライアント側はこのステータスを見て、再接続を試みるかどうかを判断できます。

サンプル: 簡単なチャット終了

svr.WebSocket("/chat", [](const auto &req, auto &ws) {
  std::string msg;
  while (ws.is_open()) {
    if (ws.read(msg) == httplib::ws::ReadResult::Fail) break;

    if (msg == "/quit") {
      ws.send("goodbye");
      ws.close(httplib::ws::CloseStatus::Normal, "user quit");
      break;
    }

    ws.send("echo: " + msg);
  }
});

Note: ネットワーク障害で突然切断された場合、close()を呼ぶ暇もなくread()Failを返します。後始末はハンドラ終了時にまとめて行うようにしておくと、どちらのパターンでも対応できます。