OGK KAMUI-3 (L) は MajestyS のメットインに収まらない

OGK KAMUI-3 Lサイズ(頭囲59-60cm) はマジェスティS(2014)のメットインに収まんないです(たぶん現行モデルでも同じかと)。

 

メットインのくぼみにメットの顎部をあわせれば後頭部のフィンがメットイン後部に干渉して収まりきらず、後頭部を収めれば顎の部分がくぼみに収まらずでどちらにしろ収まらないorz

 

シートを無理矢理押し込めばロックはかかるものの、かなり無理がかかる感じなので数分程度ならともかく常用しないほうがよさげ。

 

 どこかに各社メットイン-メット適合情報みたいなのまとまってりゃいいんだけどねぇ……

 

x264 UTF-8になったとかそんなところのメモ

H265 のご時世なのに、端末やら何やらの都合で x264 にしがみついてるわけですが、昨秋に入ったコミット 7ab4c928 で、 x264 がいろいろエラーを出すようになったのでメモ。

commit 7ab4c928ef4511ea5753a36a57c3506d9fd5086b
Author: Henrik Gramner <henrik@gramner.com>
Date:   Sat Sep 12 19:24:00 2020 +0200

    Add support for long filenames on Windows 10

何があったかというと、manifest ファイルが追加されてその中で activeCodePage UTF-8 という指定が付いたので、x264 プロセスのデフォルト文字コードUTF-8 になったというそういうことです。

正道の対応策は「x264 が読むファイルは全てUTF-8にする」です。
直前のコミット d198931a63 でも「GPAC が UTF-8 サポートしてるから~」とかあるので UTF-8 統一が方針に則った方向と思います。

この変更で出るようになったエラーがこんなのです↓ (現行の master fa264466 のビルドで SJIS なファイルを読んだ場合のエラー)
中身にSJIS(非ASCII)を含む avs ファイルを入力にした場合。例えば日本語パスから Import した場合のエラーメッセージ。

avs [error]: Import: couldn't open "文字化け化け"
(Enc.avs, line 11)
x264 [error]: could not open input file `Enc.avs'

avs の中で (DGIndex で Demux した) d2v を読んでいて、d2v の中に日本語パスがあった場合のエラーメッセージ。

avs [error]: Avisynth open failure:MPEG2Source: Could not open one of the input files. (Enc.trim.avs, line 4)

(Enc.avs, line 13)
x264 [error]: could not open input file `Enc.avs'


対策としては以下のどれかで、上から順におすすめです(上で書いたとおりUTF-8化路線なので)。

  • x264 が読むファイルは全て UTF-8 にする。
    • バッチの途中とかでファイルの文字コード変換かけるようにする。など。
  • x264 が読むファイルをASCIIに統一する。
    • 上の亜種。日本語パスなど使わず全てASCIIで通せば自動的に UTF-8 対応に。
  • 7ab4c928 を含まないビルドを作る。
    • rebase -i で d 7ab4c928 したビルドで試したところ、ビルド時エラーなし。実行時も従来通りSJISファイルを与えてもエラーなしでした。ただし最後までエンコードしてないのでエンコード結果が正しいかは不明です。


エンコードチェーン見直さないと。。

いまさら Office2019 のインストールに戸惑ったのでメモ

Amazon で購入した Office Home and Business 2019 を2台目にインストールしようとして、出てきたメッセージなどに戸惑ったのでメモ。

 

Amazon なので、「アカウント&リスト」→「ゲーム&PCソフトダウンロードライブラリ」とたどると、結局「Office.comへ」と setup.office.com に飛ばされます。

 

そこで MS アカウントでログインするわけですが、その後に出てくるのがいかにもエラー然とした「このプロダクトキーはすでに使用されています。」というメッセージがなので、ここでドパっといやな汗が出るわけです。

f:id:naga_sawa:20210211103501p:plain

まぁMSだし。と気を取り直し、そこに出ている「Microsoft アカウントからインストールする」リンクをたどると今度は「サブスクリプションが見つかりません。別アカウントを使用して購入した場合。 foobar@example.com でサインインしています。」と、またいかにも機械翻訳なメッセージを吐き出してくれるのでいやな汗の粘度も増すわけです。

f:id:naga_sawa:20210211103759p:plain

ここで出ているメッセージやら「Office 2019 インストール」やらでググったりしてしまうわけですが、ここでの正解は「サブスクリプションに戻る」をクリックすることで、そうすると「サービスとサブスクリプション」のページに移動して購入した Office 2019 が表示され、そこの「インストールする」からネットワークインストーラがダウンロードできるという流れです。
とりあえず最初からこのページを出してくれと問い詰めたい。。

f:id:naga_sawa:20210211103508p:plain


ちなみにこのインストーラ、(ちょっと知ってるパソコン先生にとっては)性格が悪く、問答無用でフルインストールするので「64bit版入れろ。WordとExcelパワポだけでいい。いらんもの入れるな。」とかいう場合はカスタムインストールを設定する XML ファイルを書いてインストーラの起動時オプション configure に渡してやらないといけません。

<Configuration>
  <Add OfficeClientEdition="64">
    <Product ID="HomeBusiness2019Retail">
      <Language ID="ja-jp"/>
      <ExcludeApp ID="Access"/>
      <ExcludeApp ID="Groove"/>    <!-- Onedrive for Business -->
      <ExcludeApp ID="Lync"/>      <!-- Skype for Business -->
      <ExcludeApp ID="OneDrive"/>
      <ExcludeApp ID="OneNote"/>
      <ExcludeApp ID="Outlook"/>
      <ExcludeApp ID="Publisher"/>
    </Product>
  </Add>
</Configuration>
OfficeSetup.exe /configure custom.xml


昨今は1TB程度のストレージが当たり前になってるので、「とりあえず全部入れときゃ文句でないだろ」な富豪的プログラミングならぬ富豪的インストールなものが多くなりましたねと小並感。


Office のカスタムインストールについては以下サイトを参考にしました。

フロント向け webpack で "regeneratorRuntime is not defined" エラーが出たときの対処

まとめ

@babel/preset-env の targets 設定を見直して、Babel のターゲット指定を適切にしてやると無駄なPolyfillも使わずエラーもなくなる。

内容

webpack@4.43.0, babel-loader@8.1.0 な環境。対象はフロント(ブラウザ)用。

エラーメッセージで検索すると先例が色々とあるので、対処そのものは難しくないと思います。
async/await を bable でトランスパイルしたはいいが、必要な regeneratorRuntime が無いのというのがエラー要因なので、

あたりが対処方法になるかと思います。

自分の場合はモダンブラウザがターゲットで IE11 は対象外なので、polyfill は放り投げてトランスパイル先をちゃんと指定してやる方向で webpack.config.js の babel-loader の設定に targets を次のように指定してやりました。

loader: 'babel-loader',
options: {
  presets: [ ['@babel/preset-env',
              {
                "targets": {
                  "browsers": "since 2019"
                }
              } ] ]
}

@babel/preset-env · Babel targets.browsers

@babel/preset-env のデフォルトだと次の通りの指定になっているので IE11 もターゲットに含まれているのですが、

> 0.5%, last 2 versions, Firefox ESR, not dead

since 2019 をターゲットにしてやると文字通り 2019 年以降リリースのブラウザのみがターゲットになるので async/await はトランスパイルされず、そのまま使われるのでエラーも無くなり無用なpolyfillもいらずで丸く収まります。


node.js にしろブラウザにしろ、最近のものは async/await には対応しているので、Babel 使うときはターゲットをちゃんと指定しましょうというオチでした。

x264 + L-SMASH を Windows 上でビルドする

MP4 muxer に L-SMASH を使った x264 のビルド手順。

基本は以下の過去トピの通り MSYS2 上で進め、 「GPAC のビルド」の部分を L-SMASH に置き換えるだけ。

L-SMASH のビルド

L-SMASH を公式GitHubリポジトリ GitHub L-SMASH's official repo から clone します。

$ git clone https://github.com/l-smash/l-smash.git
Cloning into 'l-smash'...
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 7412 (delta 0), reused 1 (delta 0), pack-reused 7407
Receiving objects: 100% (7412/7412), 7.71 MiB | 4.84 MiB/s, done.
Resolving deltas: 100% (5445/5445), done.

$

続いて configure。
64bit 版をビルドする場合は prefix 指定を mingw32 → mingw64 に変更。

$ cd l-smash/

$ ./configure --prefix=/mingw32 (32bit版)

$ ./configure --prefix=/mingw64 (64bit版はこっち)
generating config.mak ...

SRCDIR = .
DESTDIR =
prefix = /mingw32
exec_prefix = ${prefix}
bindir = ${exec_prefix}/bin
libdir = ${exec_prefix}/lib
includedir = ${prefix}/include
CC = gcc
AR = ar
LD = gcc
RANLIB = ranlib
STRIP = strip
STATICLIBNAME = liblsmash.a
STATICLIB = liblsmash.a
SHAREDLIBNAME = liblsmash-2.dll
SHAREDLIB =
IMPLIB = liblsmash.dll.a
CFLAGS = -Os -ffast-math -Wshadow -Wall -std=c99 -pedantic -I. -I. -D__USE_MINGW_ANSI_STDIO=1  -fexcess-precision=fast
LDFLAGS = -L.  -Wl,--large-address-aware
SO_LDFLAGS =
LIBS = -lm
LIBARCH = i386
DEFNAME = liblsmash-2.def
SLIB_CMD = sed -i "s/ @[^ ]*//" $(DEFNAME); dlltool -m $(LIBARCH) -d $(DEFNAME) -l lsmash.lib -D $(SHAREDLIBNAME)

configure finished

  type 'make'             : compile library and tools
  type 'make install'     : install all into system
  type 'make lib'         : compile library only
  type 'make install-lib' : install library and header into system

$

ビルド。ライブラリだけでいいので、ターゲットは lib を指定します。

$ make lib -j15 j15 は環境に合わせて適当に
gcc -c -Os -ffast-math -Wshadow -Wall -std=c99 -pedantic -I. -I. -D__USE_MINGW_ANSI_STDIO=1  -fexcess-precision=fast -o common/alloc.o common/alloc.c
gcc -c -Os -ffast-math -Wshadow -Wall -std=c99 -pedantic -I. -I. -D__USE_MINGW_ANSI_STDIO=1  -fexcess-precision=fast -o common/bits.o common/bits.c
gcc -c -Os -ffast-math -Wshadow -Wall -std=c99 -pedantic -I. -I. -D__USE_MINGW_ANSI_STDIO=1  -fexcess-precision=fast -o common/bytes.o common/bytes.c
(省略)
gcc -c -Os -ffast-math -Wshadow -Wall -std=c99 -pedantic -I. -I. -D__USE_MINGW_ANSI_STDIO=1  -fexcess-precision=fast -o core/timeline.o core/timeline.c
gcc -c -Os -ffast-math -Wshadow -Wall -std=c99 -pedantic -I. -I. -D__USE_MINGW_ANSI_STDIO=1  -fexcess-precision=fast -o core/write.o core/write.c
ar rc liblsmash.a common/alloc.o common/bits.o common/bytes.o common/list.o common/multibuf.o common/osdep.o common/utils.o codecs/a52.o codecs/alac.o codecs/description.o codecs/dts.o codecs/h264.o codecs/hevc.o codecs/id.o codecs/mp4sys.o codecs/mp4a.o codecs/mp4v.o codecs/nalu.o codecs/qt_wfex.o codecs/vc1.o codecs/wma.o importer/a52_imp.o importer/adts_imp.o importer/als_imp.o importer/amr_imp.o importer/dts_imp.o importer/importer.o importer/isobm_imp.o importer/mp3_imp.o importer/nalu_imp.o importer/vc1_imp.o importer/wave_imp.o core/box.o core/box_default.o core/box_type.o core/chapter.o core/file.o core/fragment.o core/isom.o core/meta.o core/print.o core/read.o core/summary.o core/timeline.o core/write.o
ranlib liblsmash.a

$

ビルドされたライブラリのインストール。

$ make install-lib
install -d /mingw32/include
install -m 644 ./lsmash.h /mingw32/include
install -d /mingw32/lib/pkgconfig
install -m 644 liblsmash.pc /mingw32/lib/pkgconfig
install -m 644 liblsmash.a /mingw32/lib

$ cd ~
$

これで L-SMASH 入り x264 のビルド環境ができあがり。

x264 のビルド

あとは 公式の git リポジトリ から x264 のソースを clone してきてビルドするだけ。

公式 git リポジトリからソースを clone する。

$ git clone http://code.videolan.org/videolan/x264.git
Cloning into 'x264'...
warning: redirecting to https://code.videolan.org/videolan/x264.git/
remote: Enumerating objects: 22359, done.
remote: Counting objects: 100% (22359/22359), done.
remote: Compressing objects: 100% (3848/3848), done.
remote: Total 22359 (delta 18548), reused 22258 (delta 18468), pack-reused 0
Receiving objects: 100% (22359/22359), 5.20 MiB | 1.23 MiB/s, done.
Resolving deltas: 100% (18548/18548), done.

$

configure。 host オプション付けないと妙な exe ができるので注意。
Commit 71ed44c7 でビット深度 10bit, 8bit 両対応のバイナリがデフォルトになったので、ビット深度設定オプション --bit-depth は不要に。
64bit 版をビルドする場合は host 指定を i686-w64-mingw32 → x86_64-w64-mingw32 に変更する。

$ cd x264/

$ ./configure --extra-cflags="-pipe -march=native" --extra-ldflags="-static -static-libgcc" --enable-strip --host=i686-w64-mingw32 (32bit版)

$ ./configure --extra-cflags="-pipe -march=native" --extra-ldflags="-static -static-libgcc" --enable-strip --host=x86_64-w64-mingw32 (64bit版)

platform:      X86  64bit版では X86_64
byte order:    little-endian
system:        WINDOWS
cli:           yes
libx264:       internal
shared:        no
static:        no
asm:           yes
interlaced:    yes
avs:           avisynth
lavf:          no
ffms:          no
mp4:           lsmash  ここが lsmash になっているか確認
gpl:           yes
thread:        win32
opencl:        yes
filters:       crop select_every
lto:           no
debug:         no
gprof:         no
strip:         yes
PIC:           no
bit depth:     all
chroma format: all

You can run 'make' or 'make fprofiled' now.

$

make する。何かしらの動画を使って最適化する場合は make fprofiled を使う。
ここでは、そこまでこだわってないので普通に make します。

$ make -j15 j15 オプションは環境に応じて(ry
cat common/opencl/x264-cl.h common/opencl/bidir.cl common/opencl/downscale.cl common/opencl/intra.cl common/opencl/motionsearch.cl common/opencl/subpel.cl common/opencl/weightp.cl | ./tools/cltostr.sh common/oclobj.h
dependency file generation...
windres --target=pe-i386  -I. -o x264res.o x264res.rc
(省略)
gcc-ranlib libx264.a
gcc -o x264.exe  x264res.o x264.o autocomplete.o input/input.o input/timecode.o input/raw.o input/y4m.o output/raw.o output/matroska.o output/matroska_ebml.o output/flv.o output/flv_bytestream.o filters/filters.o filters/video/video.o filters/video/source.o filters/video/internal.o filters/video/resize.o filters/video/fix_vfr_pts.o filters/video/select_every.o filters/video/crop.o input/avs.o output/mp4_lsmash.o filters/video/cache-8.o filters/video/depth-8.o input/thread-8.o filters/video/cache-10.o filters/video/depth-10.o input/thread-10.o libx264.a -L/mingw32/lib -llsmash -lm  -lshell32 -m32  -static -static-libgcc  -Wl,--large-address-aware -Wl,--dynamicbase,--nxcompat,--tsaware -s


$

動作確認する。

$ ./x264 --version
x264 0.160.3009 4c9b076
built on Jun 20 2020, gcc: 10.1.0
x264 configuration: --chroma-format=all
libx264 configuration: --chroma-format=all
x264 license: GPL version 2 or later

$

exit して msys をインストールしたフォルダの home\(ユーザ名)\x264\x264.exe を適当な場所に移動なりコピーするなりすればできあがり。


それにしても 2017年だと -j3 だったのが -j15 になって隔世感ある。

続・SameSite指定されたCookieはCORS fetch時にどう働くか

まとめ

A request is "same-site" if its target's URI's origin's registrable domain is an exact match for the request's client's "site for cookies", or if the request has no client.

  • 例えば
    • www.example.com から api.example.com を fetch → 「Registrable Domain」が合致するので「Same-Site」
    • www.foobar.com から api.example.com を fetch → 「Registrable Domain」が違うので「Cross-Site」
  • CORS fetch リクエストに Cookie が付くかどうか
SameSite判定 Set-Cookie時のSameSiteの指定 リクエストにCookieが…
Same-Site none ○付く
Same-Site lax ○付く
Same-Site strict ○付く
Cross-Site none ○付く
Cross-Site lax ×付かない
Cross-Site strict ×付かない
  • ここでの「Same-Site」「Cross-Site」はあくまでも Cookie に対しての話なので、 CORSリクエストのそれとは分けて考えること。
  • サブドメインが違うだけの CORS fetch では SameSite=lax/strict 指定の Cookie もリクエストに付加されるので、そういう構成がとれる場合は API の認証に SameSite=lax/strict な Cookie が使える。
    • ただしブラウザ設定でサードパーティーCookieをブロックするようになっている場合はSet-Cookieがブロックされてしまうので使えない。

経緯とか

昨日すっとぼけたことを書いていたトピック の続きとして、よく見かけるサイトURLが www.example.comAPI用URLが api.example.com のようなサブドメインが違うだけという構成を試してみたところ、Set-Cookie に domain=example.com 指定もしていないのに APIサーバに Cookie が送られてる……という謎に遭遇して調べ回った結果が↑のまとめの通り。


確認するには、ブラウザアクセス用のドメインとして www.example.com を hosts に追加(テストが済んだら削除すること)。

 127.0.0.1       apitest.example.com
+127.0.0.1       www.example.com

この状態で、ブラウザで http://www.example.com:5500/ を開くと、 http://www.example.com:5500/ のサイトから http://apitest.example.com:3000/ に CORS fetch リクエストをすることになるので、SameSite のセレクトボックスから strict や lax を選んで Cookie を SET すると、 GET, POST には Cookie が付かないはず……が、 msg = undefined にはならず、入力したテキストが表示される。
デベロッパーツールで確認してもちゃんとリクエストには Cookie が付いている。


「もしかしてサブドメインが無視されてる?」と考え、 hosts にちょっと違うドメインを追加。

 127.0.0.1       apitest.example.com
 127.0.0.1       www.example.com
+127.0.0.1       www.example2.com

その上で http://www.example2.com:5500/ から strict / lax で Cookie SET すると、その後の GET/POST には Cookie は付かず。
ブラウザのバグという線は限りなく低いので調べていった結果、上記のサイトに出会った次第。

SameSite指定されたCookieはCORS fetch時にどう働くか

まとめ

2020/05/17 修正

fetch などを使った CORS リクエストにおいて、API サーバから SameSite 設定付きで Set-Cookie が返された場合、以降の CORS リクエストに Cookie は付くのかどうか → SameSite=none の場合のみ Cookie が付く
ただし、サブドメイン部だけが異なるドメイン間での CORS の場合、lax/strict でも Cookie が付くもうちょっと調べたトピック 参照


以下の通り、lax や strict を指定された Cookieは 別ドメインに対する CORS fetch リクエストには付かない。

SameSite Cookie
none ○付く
lax ×付かない
strict ×付かない

CORS で Cookie を使う場合、 SameSite=none にしつつ API の応答に Access-Control-Allow-Credentials: true を付けたり、 fetch 時のオプションに credentials: 'include' 付けたりと、CORSの仕様に沿う必要もある。

また、ブラウザの設定でサードパーティーCookieがブロックされている場合、 SameSite の指定に関係なく Cookie は付かない(Set-Cookieが無視されるのでブラウザに記憶されない)。


この挙動を考えると、Cookie認証なAPI サーバを別ドメインにする構成では SameSite=none の指定が必須になるわけですね。

サードパーティーCookieが拒否される風潮を鑑みると、次のような対応にしたほうがいいのやも。

  • リバースプロキシなどを使い、表向きは同一ドメインでのAPIアクセスにして裏で振り分ける構成にする
  • Cookie の代わりに JWT などの Token で認証するようにする(XSS攻撃された場合のToken漏洩問題が出てきますが…)

以下打ち消し線部分は間違い

Cookie の挙動と Same Origin Policy を混同してました。
Cookie の場合、ドメイン名のみで識別されスキーマとポート番号は無視されるので、APIサーバを別ポートで動かしただけでは CORS が必要になるけれども Cookie の挙動は同一ドメイン時と同じ、という状態でのテストになっていた。という次第。
(そりゃ全部Cookie付くわなorz)
fetch などを使った CORS リクエストにおいて、API サーバから SameSite 設定付きで Set-Cookie が返された場合、以降の CORS リクエストに Cookie は付くのかどうか → 付く。

SameSite Cookie
none
lax
strict

ただし、API サーバの応答に Access-Control-Allow-Credentials: true を付けたり、 fetch 時のオプションに credentials: 'include' 付けたりと、CORS 下で Cookie を使う仕様に沿う必要はある。

Cookieを使ったAPIの認証をする場合は、 httponly + samesite としておけばよさそう。

検証に使った適当なコード

簡易API サーバ

指定された条件で Set-Cookie を発行する API と、リクエスト中の Cookie を取得して返す API の簡易APIサーバ。
ドメインに見せかけるため、 hosts ファイルに以下を追加して apitest.example.com でアクセスする(検証が終わったら削除する)。

127.0.0.1       apitest.example.com

/api/ に { 'msg': 'hoge', 'samesite': 0} みたいな JSON を POST すると Set-Cookie 入りのレスポンスを返す。
/api/cookie/ に POST したり GET したりすると、リクエスト中の Cookie の中身を JSON で返す。

// cors_api.js
const express = require('express');
const cookieParser = require('cookie-parser');
const cors = require('cors');

const app = express();
app.use(cookieParser());
app.use(express.json()); // application/json

// see https://expressjs.com/en/resources/middleware/cors.html#configuration-options
const cors_options = {
    origin: true,       // Access-Control-Allow-Origin: <Origin>
    credentials: true   // Access-Control-Allow-Credentials: true
}

// set cookie to response
app.options('/api/', cors(cors_options));
app.post('/api/', cors(cors_options), (req, res) => {
    console.log(req.cookies);
    const msg = req.body.msg;
    const samesite = req.body.samesite == 0 ? 'none' : req.body.samesite == 1 ? 'lax' : 'strict';

    res.cookie('msg', msg, {
        httpOnly: true,
        sameSite: samesite
    });
    res.json({ msg : `msg=${msg}; samesite=${samesite}`});
});

// get cookie from request
const getCookie = (req, res) => {
    console.log(req.cookies);

    const msg = req.cookies['msg'];

    res.json({ msg : `msg=${msg}`});
};
app.options('/api/cookie/', cors(cors_options));
app.get('/api/cookie/',  cors(cors_options), getCookie);
app.post('/api/cookie/', cors(cors_options), getCookie);

// start server
app.listen(3000, 'localhost', () => console.log('Listening on port 3000'));
ドライバHTML

簡易APIサーバの API を叩くドライバHTMLを、VS Code の LiveServerなどの適当なWebサーバ介して localhost ドメインとしてブラウザで開く。
テキストボックスに Cookie に設定する文字列を入れ、その後ろのセレクトボックスは Cookie に設定する SameSite の種類を選ぶ。SET ボタンを押すと /api/ が叩かれて Set-Cookie 付きのレスポンスが得られる。
そして、GETボタンやPOSTボタンを押すと、それぞれのメソッドで /api/cookie/ が叩かれて、それらのリクエストに付いてくる Cookie の中身が表示される。
msg=undefined となったり、テキストボックスに入れた文字列と違うものが表示された場合はサーバ側で Cookie が取得できていないことを表す。

<!DOCTYPE html>
<html>
  <body>
    <input type="text" id="msg"></input>
    <select id="samesite">
      <option value="0">none</option>
      <option value="1">lax</option>
      <option value="2">strict</option>
    </select>
    <button id="s">SET</button>
    <button id="s_get">GET</button><button id="s_post">POST</button><span id="ans"></span>

    <script>
        document.getElementById('s').addEventListener('click', async (e) => {
            e.preventDefault();
            const msg = document.getElementById('msg').value;
            const samesite = document.getElementById('samesite').value * 1;
            const req = { msg, samesite };
            res = await fetch("http://apitest.example.com:3000/api/",
                { method: 'POST',
                  body: JSON.stringify(req),
                  mode: "cors",
                  credentials: 'include',
                  headers: {
                      "Content-Type": "application/json; charset=utf-8",
                  }
                });
            json = await res.json();
            document.getElementById('ans').innerText = json.msg;
        });
        document.getElementById('s_get').addEventListener('click', async (e) => {
            e.preventDefault();
            res = await fetch("http://apitest.example.com:3000/api/cookie/",
                { method: 'GET',
                  mode: "cors",
                  credentials: 'include'});
            json = await res.json();
            document.getElementById('ans').innerText = json.msg;
        });
        document.getElementById('s_post').addEventListener('click', async (e) => {
            e.preventDefault();
            res = await fetch("http://apitest.example.com:3000/api/cookie/",
                { method: 'POST',
                  mode: "cors",
                  credentials: 'include'});
            json = await res.json();
            document.getElementById('ans').innerText = json.msg;
        });
    </script>
  </body>
</html>