CORSを解決してローカル環境で2つのサービスを動かしたい
ブラウザのWebページから、別サーバのWebAPIを呼び出すという動作検証を両方ローカル環境でやりたい、というのが今回の趣旨です。
次のような2つのサービスを用意して、ローカル環境で動作させます。
サービス | URL |
---|---|
Webページ | localhost:5000で起動。アクセスするとWebAPIを呼び出す。 |
WebAPI | localhost:8000で起動 |
初回コード(これは失敗します)
まず最初に作ったのは次のようなコードです。
ブラウザサービス側(localhost:5000)
window.onload = function() { const data = { name: 'example' }; fetch('http://localhost:8000') .then(response => response.json()) .then(data => {console.log(data);}) .catch(function(error) { console.log("error:", error); }); }
WebAPI側(localhost:8000)
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, User{ Name: "hoge", }) }) r.Run(":8000") }
これを両方とも起動して、ブラウザサービスにアクセスするとWebAPIが呼び出されるのですが、CORSの問題で次の通りアクセスエラーが起きます。
Access to fetch at 'http://localhost:8000/' from origin 'http://localhost:5000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
最終コード(これで解決しました)
色々調べた結果、WebAPI側で「Access-Control-Allow-Origin」レスポンスヘッダを付ければいいということが大枠で分かりました。
goで実装しているWebAPIはginを使っているのですが、CORSを解決してくれるパッケージがあるようなのでそれを使って解決しました。
こちらのサイトを参考にしています。
http://psychedelicnekopunch.com/archives/820
WebAPI側(localhost:8000)を改造
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() // CORS 対応を追加 config := cors.DefaultConfig() config.AllowOrigins = []string{"http://localhost:5000"} r.Use(cors.New(config)) r.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, User{ Name: "hoge", }) }) r.Run(":8000") }
やってみたけど駄目だったこと
この先は解決に至るまでに色々試行錯誤していた内容を載せておきます。
最初、fetchを使ったCORSの解決方法を探していて、こちらのサイトmodeを指定できることを知りました。 https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch
リクエストヘッダのmodeは"no-cors", "cors", "same-origin"を指定できるのですが、"no-cors"を使えばとりあえず動くかな?と思い、設定してみました。
ブラウザサービス側(localhost:5000)を改造
window.onload = function() { const data = { name: 'example' }; fetch('http://localhost:8000', { method: 'GET', mode: "no-cors", headers: { 'Content-Type': 'application/json'}, }) .then(response => response.json()) .then(data => {console.log(data);}) .catch(function(error) { console.log("error:", error); }); }
この方法は、WebAPIは呼べるようになるのですが、受け取ったデータにjsonデータが含まれなくなります。 結局これだとレスポンスデータを受け取れなくなるようです。 さらに上のリンクのサイトにも次のように注意書きがありました。
なお、
mode: "no-cors"
はリクエスト中の限られた数のヘッダーにしか許可されていません。
- Accept
- Accept-Language
- Content-Language
- Content-Type のうち、値が application/x-www-form-urlencoded, multipart/form-data, text/plain のいずれかのもの
M5StickCでHTTP GETする
こちらの記事をみて自分もやってみました。
https://slanew.com/news/668
元記事はC言語で実装していますが、私は今回UIflowで作りました。
完成品
bitflyerのWebAPIからビットコインの現在の価格を取得して表示します。
UIflowで実装する
UIFlowを開いてBlocklyで組んでいきます。
M5ボタン(Aボタン)を押したら表示を開始して、5秒間隔で更新します。
肝になるのはHTTP GETの部分です。
Advance > Http > Http Requestを選択します。
- Method = "GET"
- URL = "https://api.bitflyer.jp/v1/ticker?product_code=BTC_JPY"
- Headers : None
- Data : None
Http Requestで受け取ったデータはGet Data
で受け取れるため、これをjsonで読み込んで"best_bid"というキーのデータを表示します。
もやもやポイント
M5StickCのパワーが足りないのか分かりませんが、M5ボタン押してもhttp requestで取得した値が表示されないことがあります。
一度動けばその後問題はないのですが、結構頻繁に発生するのでなんとか解消したいところです。
再帰呼び出しになるかどうか
次のエラーで少しハマったので調べました。
Uncaught (in promise) RangeError: Maximum call stack size exceeded
どうやら再帰呼び出しによるスタックオーバーフローのようです。
実行していたコードは以下の通り。
let drawPattern = true; function messageDraw(player_name) { document.getElementById("log_name").textContent = "Name : " + player_name if (drawPattern) { document.getElementById("message").textContent = "( ・ω・)"; } else { document.getElementById("message").textContent = " (・ω・ )"; } drawPattern = !drawPattern; setTimeout(messageDraw(player_name), 500); }
なるほど、messageDraw
の呼び出しが問題のようです。
このコードを次のように修正するとスタックオーバーフローは発生しなくなりました。
let drawPattern = true; let player_name = "hoge" function messageDraw() { document.getElementById("log_name").textContent = "Name : " + player_name if (drawPattern) { document.getElementById("message").textContent = "( ・ω・)"; } else { document.getElementById("message").textContent = " (・ω・ )"; } drawPattern = !drawPattern; setTimeout(messageDraw, 500); }
ふむふむ、なぜ?
関数を引数に与える形式で記述すると再帰呼び出しでは無くなるようですが、理屈が分からない。
おそらくスコープ内の変数が無いので、setTimeout()
のタイムアウトでmessageDraw()
を呼び出した時点で前のスタックが解消されるのだと想像します。
Javascriptの同期処理についてメモっておく
サーバからユーザ情報を取得して表示を更新する処理を作るに当たって、httpの完了を待つ処理が必要でした。
#3 クライアントでプレイヤー情報を参照する · bluemon0919/lobby@8f4b0f2 · GitHub
こちらの記事を参考に作りました。
var u1_name; // これを更新する var u2_name; function getUserInformation() { const p = new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', "/players/" + u2, true); xhr.responseType = 'json'; xhr.onload = function() { var res_data = this.response; u1_name = res_data[u1]; u2_name = res_data[u2]; resolve(); // 処理の終わりを通知する }; xhr.send(); }); return p; } function init() { ... getUserInformation().then(() => update()); // getUserInformationが終わったらupdateを行う }
上の例では使っていませんが、
resolveメソッドに結果を渡し、それをthenメソッドの中で受け取ることができます。
次の例では文字列"done"を渡してconsole.logで出力します。
function getUserInformation() { const p = new Promise((resolve, reject) => { ... resolve("done"); } } function init() { ... getUserInformation().then((result) => { console.log(result); update(); }); // getUserInformationが終わったらupdateを行う }
dockerの基本コマンド
はじめに
dockerを勉強したかったので、tutorialから手をつけてみました。
Dockerイメージの理解を目指すチュートリアル - Qiita
出てきたコマンドを忘れそうだったので、 備忘録的にdockerの基本コマンドをまとめてみた記事です。
docker pull
docker hubからイメージやリポジトリを取得するためのコマンド。
name spaceやtagは省略でき、name spaceを省略した場合はdocker公式のname space=library
になり、
tagを省略した場合はtag=latest
になる。
docker pull {<name spece>"/"}<repository>{":"<tag>}
$ docker pull hello-world or $ docker pull library/hello-world:latest
docker images
docker イメージ一覧を表示する。
$ docker images
docker inspect
dockerイメージの詳細を表示するコマンド。
name spaceやtagは省略でき、name spaceを省略した場合はdocker公式のname space=library
になり、
tagを省略した場合はtag=latest
になる。
docker inspect {<name spece>}<repository>{":"<tag>}
$ docker inspect hello-world:latest
docker rmi
dockerイメージを削除するコマンド。
docker rmi <repository>
$ docker rmi hello-world
docker run
dockerイメージからdockerコンテナを起動するコマンド。
docker run <ContainerID>|<ContainerName>{":"<tag>}
$ docker run hello-world
docker ps
dockerコンテナの状態を確認するコマンド。
option指定はこちらを参照。
docker ps [option]
$ docker ps -a
docker rm
dockerコンテナを削除するコマンド。
option指定はこちらを参照。
docker rm [option] <Container ID>|<Container Name>
$ docker rm hello-world
docker build
dockerイメージをビルドするコマンド。
パスに指定したディレクトリにあるDockerfileとコンテキストを利用して新しいDockerイメージを構築する。
コンテキストはDockerイメージ構築に必要なファイル全般のことを指す。
-t
がContainerName:tag
形式指定のオプションで、ビルド時にタグを追加することもできる。
docker build -t <ContainerName>{":"<tag>}
$ docker build -t myhello . or $ docker build -t myhello:v2 .
終わり
もう少し勉強したらdockerのライフサイクルをまとめてみようと思います。
docker for macでttyにアクセスできない
macの場合dockerがVM上で動作しているので、/var/lib/docker/
に直接アクセスできない。
$ docker inspect hello-world:latest ... "GraphDriver": { "Data": { "MergedDir": "/var/lib/docker/overlay2/a561cd4994ad3cc18999f56d565a9c2d703f3bcfb17bb30374874dd6da80e88b/merged", "UpperDir": "/var/lib/docker/overlay2/a561cd4994ad3cc18999f56d565a9c2d703f3bcfb17bb30374874dd6da80e88b/diff", "WorkDir": "/var/lib/docker/overlay2/a561cd4994ad3cc18999f56d565a9c2d703f3bcfb17bb30374874dd6da80e88b/work" }, "Name": "overlay2" },
次のコマンドでttyにアクセスできるらしいのだが、ファイルが無いというエラーが出てしまった。
$ screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty ... No such file or directory
確かにttyというファイルは無い。バージョンによる違いだろうか。
$ ls -l ~/Library/Containers/com.docker.docker/Data/vms/0 total 144 srwxr-xr-x 1 kota staff 0 1 4 10:00 00000002.000005f4 srwxr-xr-x 1 kota staff 0 1 4 10:00 00000002.00001003 srwxr-xr-x 1 kota staff 0 1 4 10:00 00000003.000005f5 srwxr-xr-x 1 kota staff 0 1 4 10:00 00000003.00000948 srwxr-xr-x 1 kota staff 0 1 4 10:00 connect -rw-r--r-- 1 kota staff 65536 1 4 10:00 console-ring drwxr-xr-x@ 3 kota staff 96 1 4 10:00 data lrwxr-xr-x 1 kota staff 17 1 4 10:00 guest.000005f5 -> 00000003.000005f5 lrwxr-xr-x 1 kota staff 17 1 4 10:00 guest.00000948 -> 00000003.00000948 -rw-r--r-- 1 kota staff 2375 1 4 10:00 hyperkit.json -rw-r--r-- 1 kota staff 4 1 4 10:00 hyperkit.pid drwxr-xr-x 2 kota staff 64 1 4 10:00 log
使っているバージョンは次の通り。何か情報あったら教えてください。
$ docker version Client: Docker Engine - Community Cloud integration: 1.0.4 Version: 20.10.0 API version: 1.41 Go version: go1.13.15 Git commit: 7287ab3 Built: Tue Dec 8 18:55:43 2020 OS/Arch: darwin/amd64 Context: default Experimental: true Server: Docker Engine - Community Engine: Version: 20.10.0 API version: 1.41 (minimum version 1.12) Go version: go1.13.15 Git commit: eeddea2 Built: Tue Dec 8 18:58:04 2020 OS/Arch: linux/amd64 Experimental: false containerd: Version: v1.4.3 GitCommit: 269548fa27e0089a8b8278fc4fc781d7f65a939b runc: Version: 1.0.0-rc92 GitCommit: ff819c7e9184c13b7c2607fe6c30ae19403a7aff docker-init: Version: 0.19.0 GitCommit: de40ad0