API Reference
WebNTP は 3 つのプロトコルで時刻を配信します。いずれもレスポンスは共通の JSON フォーマット (Time over HTTPS を除く)で、クライアントは往復遅延からクロックオフセットを算出します。
公開サーバーのベース URL は https://webntp.shogo82148.com です。自前でサーバーを立てる場合は webntp -serve :8080 で起動します。エンドポイントは次の 3 つです。
| メソッド / パス | プロトコル | 用途 |
|---|---|---|
| GET /json | HTTP | JSON でサーバー時刻を返す |
| GET /websocket | WebSocket | 接続を張ったまま時刻を送受信 |
| HEAD /.well-known/time | HTTPS | ヘッダーで時刻を返す(軽量) |
このページを配信している公開サーバー webntp.shogo82148.com に対して、3 つのプロトコルを実際に呼び出せます。レスポンスと、そこから計算したオフセット・往復遅延が表示されます。
「実行」を押すと結果がここに表示されます。
/json と /websocket が返す JSON は共通です。タイムスタンプはすべて Unix エポック秒(小数で秒未満を表現)です。
| フィールド | 型 | 説明 |
|---|---|---|
| id | string | サーバーのホスト名。 |
| it | number | Initiate Time — クライアントが送信したリクエスト時刻。送らなかった場合は 0。 |
| st | number | Send Time — サーバーが応答を送信した時刻。 |
| time | number | サーバー時刻。st と同じ値(後方互換のためのフィールド)。 |
| leap | number | TAI − UTC の秒数(next より前の値)。 |
| next | number | 次回、または直近の閏秒のタイムスタンプ。 |
| step | number | 閏秒の方向。正の閏秒: 1、負の閏秒: −1。 |
GET /json?<timestamp>
クエリ文字列にクライアントの送信時刻(エポック秒)を渡すと、サーバーはそれを it に反映して返します。クエリを省略すると it は 0 になります。
curl -s 'https://webntp.shogo82148.com/json?1788697606.057'
{
"id": "webntp.shogo82148.com",
"it": 1788697606.057,
"st": 1788697671.734335,
"time": 1788697671.734335,
"leap": 36,
"next": 1483228800,
"step": 1
}
WS /websocket
サブプロトコル webntp.shogo82148.com で接続します。テキストフレームでタイムスタンプ(エポック秒)を送ると、同じ JSON フォーマットで応答が返ります。接続を維持したまま複数回問い合わせでき、最も高精度な同期が可能です。無通信が続くとサーバー側から切断されます。
$ wscat --connect wss://webntp.shogo82148.com/websocket \
--subprotocol webntp.shogo82148.com
> 1558915619.944235
< {"id":"webntp.shogo82148.com","it":1558915619.944235,"st":1558916776.363423, …}
new WebSocket(url, ["webntp.shogo82148.com"]) のようにサブプロトコルの指定が必須です。指定しないとハンドシェイクに失敗します。const conn = new WebSocket(
"wss://webntp.shogo82148.com/websocket",
["webntp.shogo82148.com"]
);
conn.onopen = () => conn.send((Date.now() / 1000).toString());
conn.onmessage = (ev) => {
const r = JSON.parse(ev.data);
const end = Date.now();
const delay = end - r.it * 1000;
const offset = r.st * 1000 - end + delay / 2;
console.log("offset", offset, "ms");
};
HEAD /.well-known/time
ボディを持たず、レスポンスヘッダー X-Httpstime にサーバー時刻(エポック秒)を入れて返します。FreeBSD の Time over HTTPS specification に基づく、最も軽量な方式です。
$ curl -I https://webntp.shogo82148.com/.well-known/time
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: X-Httpstime
Cache-Control: no-cache
X-Httpstime: 1788697967.925993
クライアントは NTP と同じ考え方で、往復遅延を測ってクロックオフセットを推定します。送信時刻 it、応答受信時刻 end、サーバー送信時刻 st から次のように計算します。
# 往復遅延(ネットワークの往復にかかった時間)
delay = end - it
# クロックオフセット(サーバー時刻 − クライアント時刻)
# 遅延の半分を片道分として補正する
offset = st - end + delay / 2
推定した offset をローカル時計に足すことで、サーバーに同期した時刻が得られます。往復遅延が小さいほど推定精度は高くなるため、WebSocket が最も有利です。
レスポンスの leap / next / step は閏秒に関するメタデータです。閏秒は将来的に廃止が予定されていますが、後方互換のため、直近に挿入された閏秒(2017-01-01 の正の閏秒、TAI − UTC = 36 秒)の情報を返し続けます。
すべてのエンドポイントは Access-Control-Allow-Origin: * を返すため、任意のオリジンのブラウザから利用できます。Time over HTTPS では Access-Control-Expose-Headers: X-Httpstime により、JavaScript からヘッダーを読み取れます。応答はいずれも Cache-Control: no-cache を返します。キャッシュへの保存は可能ですが、再利用の前にサーバーへの再検証が必要です。