Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all 8837 articles
Browse latest View live

MERNスタックをDockerでやっていく

$
0
0

はじめに

30代正社員経験のないクズ人間だけどSHUSHOKUできました!やったー!
うれC!!!!!!!!うれC!!!
MERNスタック使って、ポートフォリオ用にWebアプリ作ってみたけど、大変だった気がします。正直全部大変だし難しいし自分が何やってるかよくわからない感じになります。頭が悪いので整理ができない……(´・ω・`)
DockerでMERNスタック環境構築したらDocker使っちゃう気持ちもアゲアゲでいけるのでは?などと考え、あとからECRやら楽なんじゃね?とか思っていた時期が自分にもありました。

MERNスタックでなんかDockerもつかって気軽に色々できればいいなあというわけで、今後忘れないように書いておこうと思います。

Dockerについては、「何故Dockerなのか」とか、なんか公式読めば良いとかいう風潮がある気がします。
公式は字が多すぎて読む気がおきないみたいなやつはあります。でも、読め!とインターネッツの中の人たちが言ってくるので読みました。読んでもよくわからないので、脳がやばい。
脳みそやわやわな状態でやっているので、やっていける気がしない。

チラシの裏にでも書いてろ!という記事です。でもQiitaに書いたろと思いました。承認欲求モンスターなので。謝罪しておきます。申し訳ございません。

フォルダ構成

とりあえず、こんなん

.
├── README.md
├── backend
│   ├── Dockerfile-dev
│   ├── app.js
│   ├── config.env
│   ├── controllers
│   ├── middleware
│   ├── models
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json
│   ├── public
│   ├── routes
│   ├── uploads
│   └── utils
├── frontend
│   ├── Dockerfile-dev
│   ├── build
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json
│   ├── public
│   └── src
├── dev-docker-compose.yml
├── node_modules
├── package-lock.json
└── package.json

メインにフロントエンドとバックエンドそれぞれフォルダを作りその中にdev環境Dockerfileをそれぞれ置いておく。
メインフォルダにはdev環境Docker-compose.ymlをつくる。
とりあえずsudo apt install docker-composeやら色々はやってる状態。
他のフォルダなんかは、皆自由にやっていっていると思います。

基本的に↓

nanka_app_nandemo_yoi_example/
├── node-backend/
│   └── Dockerfile
├── react-frontend/
│   └── Dockerfile
└── docker-compose.yml

こういう構成で作っていきたい気持ち。

バックエンド(MERNのN担当Node.js)

とりあえず動かしたいので、こんな感じに。

Dockerfile-dev
FROM node:10.18.0-alpine3.9EXPOSE 3000RUN apk add --update curl
WORKDIR /usr/src/appCOPY package.json package-lock.json* ./RUN npm cache clean --force&& npm install&& npm install-g nodemon
COPY . .CMD ["npm", "run", "dev"]

自分にはdev環境とproduction環境をしっかり構成したいという夢がありました
というわけで、devになっていますが、別にDockerfileとしていれば良い。

フロントエンド(MERNのR担当React.js)

担当とか書くと、なんかMERNの中で誰推し?みたいな雰囲気になっていいですね。
皆JS界からきてるわけですし、でかいアイドルグループみたいなもんやろ。しらんけど。
自分は誰推しでもないです、皆むずかしい。もう誰も信じない。

とりあえずこんな感じ

Dockerfile-dev
FROM node:10.18.0-alpine3.9EXPOSE 3000RUN apk add --update curl
WORKDIR /usr/src/appCOPY package.json package-lock.json* ./COPY . .CMD [ "npm", "start" ]

ほぼバックエンド側と変わらない感じのやつ。少しでも、行数を減らしたい。行が増えると絶望感が増す。

COMPOSEファイル(MERNのM担当Mongoあたりも登場)

コンポーズファイル作ります。
こいつがないと私にはなにもできない。

dev-docker-compose.yml
version: "2.2"

services:
  mongodb:
    image: "mongo"
    ports:
      - "27017:27017"
  backend:
    build:
      context: ./backend/
      dockerfile: Dockerfile-dev
    ports:
      - "6200:6200"
    volumes:
      - ./backend:/usr/src/app
    depends_on:
      - mongodb
  frontend:
    build:
      context: ./frontend/
      dockerfile: Dockerfile-dev
    ports:
      - "3000:3000"
    volumes:
      - ./frontend:/usr/src/app
    depends_on:
      - backend

ビルドしたい

docker-compose -f dev-docker-compose.yml buildこれでビルドできる。
あとは毎回docker-compose -f dev-docker-compose.yml upとかすれば良い。

これで、なんか構築はされる感じになっている。
あとはCircleCIがどうとか、Node.js側でAPIがどうのとかReact側でSPAがどうとか、MongoDBでAtlasで某とか、そういうあれでなんかやっていきます。


Netlify Functions で API の URL を rewrite する

$
0
0

はじめに

Netlify Functionsで作成する API の URL 形式を 初期設定の /.netlify/functions/example?hoge=10のような形から /example/10形式に rewrite する方法です。

netlify.toml の指定

Netlify ではプロジェクトのルートに配置する netlify.tomlで redirect を指定することができます。また、status200に設定すると redirect ではなく rewrite として機能します。

Rewrites and proxies

When you assign an HTTP status code of 200 to a redirect rule, it becomes a rewrite. This means that the URL in the visitor's address bar remains the same, while Netlify's servers fetch the new location behind the scenes.

URL の指定にはプレースホルダーが使えるので、例えば /v1/digit/200/6/123456/のような URL を ?width=600&digit=6&number=123456に変換する記述は次のようにかけそうです。

[[redirects]]
    from = "/v1/digit/:width/:digit/:number/"
    to = "/.netlify/functions/digit/?width=:width&digit=:digit&number=:number"
    status = 200

が、残念ながらリライトはされるものの、(2020/4 現在)Lambda エンドポイントの event.queryStringParametersに値が渡ってきません。

exports.handler=async(event)=>{console.log(event.queryStringParameters);// {}}

Netlify Community にも同件の報告があがっていました。

queryStringParameters not working with redirect on production - Support - Netlify Community

event.path から取得する

Community のほうに event.pathから取得してみてはどうかとありましたので、(悲しいですが)次のように event.pathpath-parserで URL をパースすることにしました。

import{Path}from'path-parser';functionparseQueryArgs(rewriteUrl,urlpath,format){constparse=Path.createPath(rewriteUrl).test(urlpath);letresult={};for(constkeyinformat){if(parse&&parse[key]){result[key]=parse[key];}else{// default valueresult[key]=format[key]}}returnresult;}exports.handler=async(event)=>{constargs=parseQueryArgs('/v1/digit/:width<\\d+>/:digit<\\d+>/:number<\\d+>/',event.path,{width:160,digit:6,number:12345});// ..snip};

この場合の netlify.tomlは単純に次のようになります。

[[redirects]]
    from = "/v1/digit/*"
    to = "/.netlify/functions/digit"
    status = 200

rewrite が使えないローカル netlify-lambda 環境の考慮も入れた版を github においてあります。

動作サンプル

Screenshot_2020-04-02 fujilcd.png

/v1/digit/幅px/桁数/ナンバー/として動作します。

https://sample-counter.netlify.com/v1/digit/640/9/123456789/

関連

Netlify Functions で古のアクセスカウンター(アクセサリー)をつくる - Qiita

github リポジトリー

h1romas4/netlify-functions-template

よく使われるHTTPステータスコードをNode.jsの実例で勉強する

$
0
0

はじめに

簡単なアプリを作りながらNode.jsと基本的なサーバーサイドプログラミングについて勉強していました。

また、ステータスコードの扱いについて本で読んでいると、こういう場合にはこのステータスコードを返せばいいよと書かれてあったのですが、実際のコードでそれをどう表現すればいいのか本だけではイマイチ理解できませんでした。

今回はNode.jsの復習を兼ねて手を動かしながら実例を作ってみました。

対象のステータスコード

  • 200: OK
  • 301: Moved Permanently
  • 303: See Other
  • 400: Bad Request
  • 401: Unauthorized
  • 404: Not Found

これ以外のステータスコードについてはまた別の記事でやるかも...?

例題

このような勤怠表を作ります。(見た目素のHTMLですが。。。)
basic認証でのログイン機能と、ログアウト、出勤退勤ボタンを押すと、ユーザ名と勤怠のログが生成される簡単なWebアプリです。
スクリーンショット 2020-03-29 19.09.00.png
簡単に機能を下記にまとめました。

パスメソッド機能
/topGET勤怠表の表示
/top/kintaiPOST勤怠表への勤怠情報の追加
/logoutGETログアウト機能
/old-urlGET古いURLなので/topへリダイレクトさせる

準備

依存モジュールはこのようになっております。

package.json
{"dependencies":{"http":"^0.0.1-security","http-auth":"^4.1.2","pug":"^2.0.4"}}

basic認証にはhttp-auth, テンプレートエンジンはpugを使用します。
次に、node.jsの雛形を記述します。

index.js
'use strict';consthttp=require('http');constauth=require('http-auth');constpug=require('pug');constbasic=auth.basic({realm:'Enter username and password',file:'./password'});constserver=http.createServer(basic.check((req,res)=>{switch(req.url){default:break;}}));constport=8000;server.listen(port,()=>{console.info('Listening on '+port);});

ちなみにこのコードはこのままでは動きません。
また、認証に使用するユーザ名とパスワードはこのようなテキストファイルに格納しておきます。

password
guest1:2222
guest2:3333

ステータスコード : 200 OK

このステータスコードはリクエストが正常に完了したことを表します。GET,PUT,POSTメソッドで正常なリクエストが来たらまずこれでいいのではないでしょうか。
今回の例ではGETメソッドで/topにリクエストが来たらステータスコード200とHTMLファイルを返します。

index.js
'use strict';consthttp=require('http');constauth=require('http-auth');constpug=require('pug');constbasic=auth.basic({realm:'Enter username and password',file:'./password'});//勤怠履歴varhistory=[];constserver=http.createServer(basic.check((req,res)=>{switch(req.url){case'/top':if(req.method==='GET'){res.writeHead(200,{'Content-Type':'text/html; charset=utf-8'});res.end(pug.renderFile('./views/index.pug',{history:history,user:req.user}));}break;default:break;}}));constport=8000;server.listen(port,()=>{console.info('Listening on '+port);});

res.writeHeadでHTTPレスポンスヘッダに200のステータスコードを書き込んでいます。
また、res.endでpugテンプレートからHTMLに変換したものをレスポンスボディに書き込んでいます。
pugテンプレートは以下のようになります。

views/index.pug
doctype html
html(lang="ja")
  head
    meta(charset="utf-8")
    title 勤怠表
  body
    h1 勤怠表
    h2 あなたは #{user} 

node index.jsでプログラムを実行し、localhost:8000/topをブラウザで開くとページが見れます。
この時点でログインするとユーザ名だけが表示されるページができました。
index.jsでpugにhistoryuser.nameを渡していますが、historyは現段階では使用していません。

ステータスコード : 400 Bad Request

このステータスコードはリクエストの構文やパラメータが間違っている時に返します。
例えば、ユーザの入力からパスワードの設定を行う時に指定した要件を満たしていなかった場合など400を返します。
またリクエストのメソッドが指定されたURLには実装されていないときなどもこのコードを返します。
リクエストのメソッドが指定されたURLには実装されていない場合の処理をindex.jsに書き加えます。

index.js
    case '/top':
        if(req.method === 'GET'){
            res.writeHead(200, {
                'Content-Type': 'text/html; charset=utf-8'
            });
            res.end(pug.renderFile('./views/index2.pug', {
                history: history,
                user: req.user
            }));            
        }
+       else{
+           handleBadRequest(req, res);
+       }
        break;
     default:
         break;
     }
 }));


+function handleBadRequest(req, res) {
+    res.writeHead(400, {
+        'Content-Type': 'text/plain; charset=utf-8'
+    });
+    const message = "未対応のリクエストです。";
+    res.end('status code :' + res.statusCode + " " + message);
+}
+
 const port = 8000;

この例ではtop/にリクエストが来たものの、メソッドがGET以外ならばhandleBadRequest関数でレスポンスヘッダに400を書いています。
これでプログラムを再起動して、別のターミナルから

curl -X POST -u guest1:2222 -d"hoge=huga" localhost:8000/top 

などと実装されていないPOSTメソッドでリクエストを送ると、

status code :400 未対応のリクエストです。

と帰ってくるはずです。

ステータスコード : 301 Moved Permanently

こちらはリクエストで指定したURLが新しいURLに移行されている時に返します。古いURLにアクセスされると新しいURLにリダイレクトされます。新しいURLはレスポンスヘッダのLocationに絶対URLとして格納します。
ここでは古いURL('/old-url')にアクセスがくると新しいURL(/top)へリダイレクトを行います。
このコードを追加したものが以下の差分になります。

index.js
             handleBadRequest(req, res);
         }
+    case '/old-url' :
+        handleMovedPermanently(req, res);
+        break
     default:
         break;
    }
}));function handleBadRequest(req, res) {
    res.writeHead(400, {
        'Content-Type': 'text/plain; charset=utf-8'
    });
    const message = "未対応のリクエストです。";
    res.end('status code :' + res.statusCode + " " + message);
}+function handleMovedPermanently(req, res) {
+    res.writeHead(301, {
+        'Content-Type': 'text/html; charset=utf-8',
+        'Location' : '/top'
+    });
+   res.end('<!DOCTYPE html><html lang="jp"><body>' +
+            '<h1>新しいURLに移動しました</h1>' +
+            '<a href="/top">新しいほう</h1>' +
+            '</body></html>');
+}
 const port = 8000;
 server.listen(port, () => {
     console.info('Listening on ' + port);

Chromeでlocalhost:8000/old-urlを開くと/topにリダイレクトされます。
一方curlではレスポンスボディに新しいURLに移動した旨を知らせるHTMLが帰ってくると思います。

curl -X GET -u guest1:2222 localhost:8000/old-url
<!DOCTYPE html><html lang="jp"><body><h1>新しいURLに移動しました</h1><a href="/top">新しいほう</h1></body></html>%

ステータスコード : 401 Unauthorized

このステータスコードは認証に失敗したときに返します。
またユーザがログアウトするときはこのステータスコードを返せばOKです。
この例では、serverがbasic認証が通らなかったら自動的に401を返してくれるのでログアウト処理部分だけ対応することにします。

index.js
     case '/old-url' :
         handleMovedPermanently(req, res);
         break;
+    case '/logout' :
+        handleUnauthorized(req, res);
+        break;
     default:
         break;
     }
 }));
index.js
function handleMovedPermanently(req, res) {    
    res.writeHead(301, {
        'Content-Type': 'text/html; charset=utf-8',
        'Location' : '/top'
    });
    res.end('<!DOCTYPE html><html lang="jp"><body>' +
            '<h1>新しいURLに移動しました</h1>' +
            '<a href="/top">新しいほう</h1>' +
            '</body></html>');
}+function handleUnauthorized(req, res) {
+    res.writeHead(401, {
+        'Content-Type': 'text/html; charset=utf-8'
+    });
+    res.end('<!DOCTYPE html><html lang="jp"><body>' +
+            '<h1>ログアウトしました</h1>' +
+            '<a href="/top">ログイン</h1>' +
+            '</body></html>');
+}

次にログアウトボタンをviewに追加します。

views/index.pug
doctype html
html(lang="ja")
  head
    meta(charset="utf-8")
    title 勤怠表
  body
    h1 勤怠表
    h2 あなたは #{user} 
    a(href="/logout")  ログアウト

ブラウザでlocalhost:8000/topを開き、ログアウトを押すと、ログアウトした旨のHMLが表示され、ログインを押すとまたユーザ名とパスワードが要求されます。

ステータスコード : 404 Not Found

このステータスコードは指定したリソースが存在しない場合に返します。
またこのときのレスポンスボディには理由を記述します。
"status code :404 指定したURLは見つかりません" という記述をレスポンスボディに追加しています。

index.js
     case '/logout' :
         handleUnauthorized(req, res);
         break;
     default:
+        handleNotFound(req, res);
         break;
index.js
function handleUnauthorized(req, res) {
    res.writeHead(401, {
        'Content-Type': 'text/html; charset=utf-8'
    });
    res.end('<!DOCTYPE html><html lang="jp"><body>' +
             '<h1>ログアウトしました</h1>' +
            '<a href="/top">ログイン</h1>' +
            '</body></html>');
}+function handleNotFound(req, res){
+    res.writeHead(404, {
+        'Content-Type': 'text/plain; charset=utf-8'
+    });
+    const message = "指定したURLが見つかりません。";
+    res.end('status code :' + res.statusCode + " " +message);
+}

ブラウザでlocalhost:8000/hogeなどと存在しないURLを開くと、ページが存在しない旨が表示されます。

ステータスコード : 303 See Other

このステータスコードはリクエストに対する処理結果が別のURLで取得できる時に返します。例えばブラウザからPOSTで何かリソースを操作するリクエストの処理結果やそれを反映した結果をGETで取得する時に使います。

ここの例では、出勤ボタンを押すと、出勤のログを出し、退勤ボタンを押すと、退勤のログをユーザ名と一緒に流す機能をPOSTで実装します。

コードは以下の通りとなります。

index.js
             handleBadRequest(req, res);
         }
         break;
+    case '/top/kintai':
+        let data = [];
+        if(req.method === 'POST'){
+            req.on('data', (chunk) => {
+                data.push(chunk);
+            }).on('end', () => {
+                data = data.toString();
+                const decoded = decodeURIComponent(data);
+                let kintai = decoded ? decoded.split('kintai-button=')[1] : '';
+                kintai = req.user + ':' + kintai
+                history.unshift(kintai);
+                res.writeHead(303, {
+                    'Location': '/top'
+                });
+                res.end();
+            })
+        }
+        else{
+            handleBadRequest(req, res);
+        }
     case '/old-url' :
         handleMovedPermanently(req, res);
         break;

viewは以下の通りとなります。

views/index.pug
doctype html
html(lang="ja")
  head
    meta(charset="utf-8")
    title 勤怠表
  body
    h1 勤怠表
    h2 あなたは #{user} 
    a(href="/logout")  ログアウト
    form(method="post" action="/top/kintai")
      button(type="submit" name="kintai-button" value="出勤") 出勤
      button(type="submit" name="kintai-button" value="退勤") 退勤

    each element in history
      div.history
        <hr>
        p #{element}
    <hr>

コードの説明をします。
まず、localhost:8000/topをブラウザで開くと出勤ボタンと退勤ボタンが出現します。

出勤ボタンを押すとkintai-button="出勤"、退勤ボタンを押すとkintai-button="退勤"という情報(厳密には違いますが)が、index.jsのcase 'top/kintai':以下で処理されることになります。dataにはURLエンコーディングされた文字列が入ってくるので、これをdecodeすることによって人間が読める文字列をdecodedに取り出します。ここから、decodedの中(例;kintai-button="退勤")から=の右部分("退勤")だけを取り出したものとユーザネームをつなげたものをkintaiに格納します。そしてログ全体を格納する配列であるhistoryに前から追加していきます。なぜ普通のpush(後ろから追加)しないのかというと、新しいものをログの上部分に表示したかったからです。

最後に303のステータスコードと、リダイレクト先を指定('Location':'/top')をレスポンスヘッダに記述します。出勤ボタンを押されると、見かけ上はページの移動が行われていないように見え、そのページ上で次々とログが出てくるはずです。

最後に

この例題では他のステータスコードの具体例を思いつかなかったので、他のステータスコードはまた別の記事でやるかもしれません。
自分の学習ログ兼ねているので読みづらい文章だったかもしれませんがここまで読んでいただきありがとうございました。

[WIP] part1 設計 | Node.jsで在庫管理アプリを作ってみる

$
0
0
part2

注意

この記事は進捗報告のようなものです。アプリが完成するかは未定です。

在庫管理アプリがほしい

 私は物持ちです。我が家にはたくさんの食材や調味料などが貯蓄してあります。その量と言ったら私自身も把握が難しい程です。レトルトカレーでいうと、縦にして並べても0.7平方メートルくらいあります。平らに並べると多分卓球台の面積くらいあるんじゃないですかね。卓球が好きなわけじゃないですけど。

 貯蓄が多いと言ったらプラスに聞こえますが、把握できていないので偏りが出たりします。例えば未開封のチューブのワサビは2本あるのにチューブのショウガが足りないということもあります。先に言ったレトルトカレーは、賞味期限が2016年から2022年のまであります。消費と追加のバランスが平衡していないと腐ってしまうのですが、何が余っているか、何が足りないかが把握できていないので買い物がうまくいきません。
 
 買い物するときに、家に何があるか確認できるといいな、ということで在庫管理のシステムを考えました。店でスマホを開き、調味料を買おうと思ったときに「調味料」の欄を見て、何がどれだけあるかを把握できるといいです。さらに言うなら、今足りていないものもひと目でわかるといいです。

どんな感じにしよう

機能

  • 在庫を追加する機能。買ったときとかに。
  • 在庫を減らす機能。使い終えたときとかに。
  • 在庫を確認する機能。買い物のときに必要です。

機能は以上でいいでしょう。

データ

  • 分類を作ってそこに放り込めるといいと思います。粉ものとか、調味料とか、お惣菜とか。
  • 賞味期限や消費期限もわかるといいです。期限切れの食品や期限間近は腐る前に消費すべきですから。
  • 保存場所もあるといいです。冷蔵庫か冷凍庫か引き出しか知っておくと探す手間が省けます。

よし、テーブルが決まりました。レコードは(品名, 分類, 場所, 期限, 個数)です。主キーは(品名, 場所, 期限)ですかね。分類→品名の推移従属があるのでテーブルを分割して在庫(品名, 場所, 期限, 個数)品物(品名, 分類)にしましょう。いちいち「ボンカレー、レトルト食品」みたいな分類を入力するのは面倒ですし、品名を書くと勝手に埋めてくれるといいですね。非機能案件です。

テーブルはこんな感じです

在庫

品名場所期限個数
ボンカレー引き出し2016/03/285
ボンカレー引き出し2017/10/0110
ボンカレー引き出し2022/04/0110
わさびチューブ冷蔵庫2020/02/221
わさびチューブ引き出し2020/02/222

品物

品名分類
ボンカレーレトルト食品
わさびチューブ調味料

おわり

では、次回から早速作り始めたいと思います。

[WIP] part2 Express+TypeScriptを使い始める | Node.jsで在庫管理アプリを作ってみる

$
0
0
part1未定

注意

 この記事は進捗報告のようなものです。アプリが完成するかは未定です。
 記事は見栄えがよくなるように書いていますが、実際の作業には手戻りもあったりしたので、コミット履歴とちょっと食い違ったりします。

設計の続き

 前回設計した仕様を実現するために、どういう構成にすればいいか考えてみました。

  • プラットフォーム: Node.js
  • フロントエンド: React, Bootstrap
  • バックエンド: Express, MySQL

 プラットフォームはJavaScriptの経験が活かせるNode.jsにしました。実はJavaScriptの経験があると言ってもNode.jsで開発するのはこれが初めてです。お手柔らかにお願いします。
 フロントエンドはReactとBootstrapにしました。フレームワークをReactとVueで迷いましたが、シェアが多いReactに決めました。潰しが効きそうなので。レイアウトは楽な方がいいのでBootstrapを付けます。
 バックエンドにサーバとしてExpress、データベースとしてMySQLを採用します。MySQLは授業で使ったので、Expressはちょっとググって良さそうだったので決めました。

開発開始

 それでは開発をはじめます。とりあえず、サーバを立てるところから。

目標サーバが動くこと

ExpressとTypeScriptをインストール

https://expressjs.com/ja/starter/hello-world.html
https://qiita.com/pochopocho13/items/79a4735031ce11a91df7
上記を参考にしました。

# リポジトリを作成$ mkdir zaiko
$ cd zaiko
$ git init
$ git remote add 
$ npm init # entrypointはapp.jsに指定# Expressをインストール$ npm i --save express

# TypeScriptをインストール$ npm i --save-dev typescript @types/node ts-node

ここで、$ npm run tscでコンパイルできるように、package.jsonに以下を追加しておきます。

package.json
"scripts":{"tsc":"tsc"}
# TypeScript初期化$ npm run tsc ----init$ npm run tsc ---v
Version 3.8.3

helloworldプログラムを書く

ようやくプログラムまでたどり着きました。富士山で言うなら山梨県の県境です。

app.ts
importexpressfrom'express'constapp:express.Express=express()app.get("/",(req,res)=>res.send("Hello, world!"))app.listen(3000,()=>console.log("Example app listening on port 3000!"))

TypeScriptはいつかやってみたいと思っていまいた。ようやくできました。

初ビルド&実行

では実行してみます。

$ npm run tsc
$ node app.js
Example app listening on port 3000!

http://localhost:3000/にアクセスしてみると、Hello, world!と表示されました。

重複しない任意長のランダム文字列を生成する方法

$
0
0

TL;DR

高速で安全、手軽にランダム文字列を生成したいときはai/nanoidを使う。

特徴

README.mdより意訳しています。

使い方

デフォルトでは[a-zA-Z0-9_-]から21文字で生成されます。

import{nanoid}from'nanoid'nanoid()//=> "yVQk_rn0A60LXcOR-2voE"

非同期に生成する

import{nanoid}from'nanoid/async'asyncfunctioncreateUser(){user.id=awaitnanoid()}

任意の長さにする

短くするほど重複しやすくなるので注意してください。
重複しやすさを簡易計算できるサイトもあるので参考にしましょう。

nanoid(10)//=> "3juqViJh32"nanoid(3)//=> "eEh"

出現する文字を絞る

customAlphabetの引数に使いたい文字と文字長を指定すると、ランダム文字列生成関数が作れます。

import{customAlphabet}from'nanoid'constrandomColor=customAlphabet('1234567890abcdef',6)font.color=`#${randomColor()}`//=> "#a1ea8e"

ちなみに、CyberAP/nanoid-dictionaryというパッケージを利用すれば、使いたい文字に指定できる頻出パターンが入っています。

  • numbers:0〜9の数字
  • lowercase:小文字の英字
  • uppercase:大文字の英字
  • nolookalikesa-zA-Z0-9から見間違いやすい1, l, I, 0, O, o, u, v, 5, S, sを取り除いたもの

記事執筆時点のバージョン

// $ cat node_modules/nanoid/package.json | jq '{ name, version }'{"name":"nanoid","version":"3.0.2"}

Node.js Worker Threads: TypeScriptのワーカーを起動する方法 〜ts-node、ts-node-devに対応する方法〜

$
0
0

Node.jsのWorker Threadsは、本物のスレッドプログラミングができます。ワーカーの処理を記述したJavaScriptを与えて、ワーカーを起動するわけですが、TypeScriptのファイルを指定するにはどしたらいいのでしょうか?

本稿でわかること

  • ts-nodeとWorker Threadsを組み合わせて、TypeScriptのワーカーを起動する方法
  • ts-node-devでTypeScriptのワーカーを起動する方法

前提知識

本稿を理解するにあたっては、下記の技術についての基礎的な知識が必要です。

  • Worker Threads
  • ts-node
    • TypeScriptのコンパイルとJavaScriptの実行をコマンド一つでできるツール。
      • tsc && node dist/main.jsを一発でできるようにしたツール。
    • nodeコマンドの感覚でTypeScriptを実行できる。 例: ts-node src/main.ts
  • ts-node-dev
    • ts-nodeとnode-devを組み合わせた開発ツール。
    • TypeScriptのコードに変更が加わると自動的にコンパイルし、プログラムを再起動してくれる。

解決したい課題: WorkerにTypeScriptを指定することはできない

JavaScriptでWorker Threadsを実行する方法は以下のような手順になります。

まず、ワーカー側の処理を実装したJavaScriptファイルを作ります:

worker.js
console.log('Hello from worker')

次に、ワーカーを起動する処理を書くのですが、Workerコンストラクタでワーカーのファイル名を指定する必要があります:

main.js
const{Worker}=require('worker_threads')constworker=newWorker('./worker.js')

このmain.jsを実行すると、ワーカーが起動することが確認できます:

$ node main.js
Hello from worker

このコードをTypeScriptで書き直し、ts-nodeで同じように実行するとどうなるでしょうか? やってみましょう。

まず、ワーカー側の実装をTSに移植します。内容はworker.jsと全く同じです:

worker.ts
console.log('Hello from worker')

次に、main.jsをTSに移植します。大きな変更は、worker.jsではなくworker.tsを起動するように変える点です:

main.ts
import{Worker}from'worker_threads'newWorker(__dirname+'/worker.ts')// tsファイルを指定

このコードを、ts-nodeで起動してみます。すると、次のようなエラーが発生し、ワーカーが起動できないことが分かります。エラー内容は、「ワーカースクリプトの拡張子はjs, mjs, cjsじゃないとダメだよ」というものです。

$ ts-node src/main.ts
The worker script extension must be ".js", ".mjs", or ".cjs". Received ".ts"

このことから、Worker Threadsでは直接TypeScriptファイルが指定できないことが分かったと思います。

解決策: いったんJavaScriptファイルを経由するようにする

Worker Threadsで起動できるコードはJavaScriptのみという制約があるので、直接TypeScriptのワーカーを起動するのはあきらめます。迂回手段として、まずJavaScriptのワーカーを起動し、その中でTypeScriptコードをrequireするようにします。

先述した失敗作TypeScriptコードを手直ししていきましょう。

まず、main.tsはworker.tsではなく、worker.jsを起動するように直します:

main.ts
import{Worker}from'worker_threads'newWorker(__dirname+'/worker.js')// jsを起動するように直す

次に、最終的な目的地である、TypeScriptワーカーのファイルを作ります。名前はworker.jsと区別できるようtsWorker.tsにしておきます:

tsWorker.ts
console.log('tsWorker.ts started')

最後に、main.tsとtsWorker.tsを橋渡しする、worker.jsを実装します。worker.jsの重要な役割は、ts-nodeをregisterすることです。これにより、以降のコードではtsファイルをrequireして実行できるようになります:

worker.js
console.log('worker.js started')require('ts-node').register()// 重要require(__dirname+'/tsWorker.ts')

このコードを実行してみましょう。

$ ts-node src/main.ts
worker.js started
tsWorker.ts started

出力結果から、まずworker.jsが実行され、次にtsWoekr.tsが読み込まれ実行されたことがわかると思います。

ts-node-devでは、execArgvを空っぽにしてWorkerを起動する

これまでts-nodeでTypeScriptワーカーを起動する方法を説明してきましたが、類似のツールであるts-node-devでも同じ方法で対応できるのでしょうか? 結論を言うと、そのままでは対応できません。

上のmain.tsをts-node-devで実行してみると分かりますが、worker.jsは起動するものの、worker.js内のrequireが動作せず、スレッドが終了してしまいます:

$ ts-node-dev src/main.ts
Using ts-node version 8.8.1, typescript version 3.8.3
worker.js started

worker.jsでexecArgvを確認すると、ワーカー側では不要なts-node-devのフックが渡ってきているのがわかります:

worker.js
console.log('worker.js started')console.log(process.execArgv)// require('ts-node').register()// require(__dirname + '/tsWorker.ts')
実行結果
$ ts-node-dev src/main.ts
Using ts-node version 8.8.1, typescript version 3.8.3
worker.js started
[
  '-r',
  '/var/folders/4l/mrmcxh3x40lbcpwxyz29ppcw0000gn/T/ts-node-dev-hook-1629690677650566.js'
]

解決策としては、main.tsのnew WorkerのオプションでexecArgvをカラにすることです:

main.ts
import{Worker}from'worker_threads'newWorker(__dirname+'/worker.js',{execArgv:[]})

こうしておくと、まずはts-node-devでもTypeScriptのワーカーが起動できるようになります。

$ ts-node-dev src/main.ts
Using ts-node version 8.8.1, typescript version 3.8.3
worker.js started
tsWorker.ts started

しかし、この方法に問題がないわけではありません。ts-node-devの醍醐味としては、TypeScriptのコードを書き直したら、自動的に再コンパイルして、プロセスを起動しなおしてくれることです。しかし、この対処法では、スレッド側でrequireされたファイルをいくら修正しても、自動再コンパイル&再起動はされません。

この課題の解決策については、また時間を見つけて調べてみたいと思います。

Nodeでmultipart/form-dataを送る

$
0
0

form-dataを使う。

form-data/form-data: A module to create readable "multipart/form-data" streams. Can be used to submit forms and file uploads to other web applications.

Axios

constaxios=require("axios");constFormData=require("form-data");constform=newFormData();form.append("message","hello");constres=awaitaxios.post(API,form,{headers:form.getHeaders()});

node-fetch

constfetch=require("node-fetch");constFormData=require("form-data");constform=newFormData();form.append("message","helllo");constres=awaitfetch(API,{method:"POST",body:form});

なぜ node-fetchはヘッダーでgetHeaders()を利用しなくていいのか?

理由は単純で、form-dataを特別に扱っている。
https://github.com/node-fetch/node-fetch/blob/c167190c6ee21234ba41bd8430700d4720bf64ce/src/body.js#L320-L323

// Detect form data input from form-data moduleif(body&&typeofbody.getBoundary==='function'){return`multipart/form-data;boundary=${body.getBoundary()}`;}

さて、multipart/form-data;はいいにしても、boundaryとはなんだろうか。
boundaryとは、複数の情報を続けて送る際、データの仕切り線の役割を果たす。
form-dataの実装を見ると、-が26文字、ランダムな数値が24字の50文字の仕切り線を生成する。

https://github.com/form-data/form-data/blob/d7026253e728af9568503dc3dc55cd1a566605e0/lib/form_data.js#L342-L351

FormData.prototype._generateBoundary=function(){// This generates a 50 character boundary similar to those used by Firefox.// They are optimized for boyer-moore parsing.varboundary='--------------------------';for(vari=0;i<24;i++){boundary+=Math.floor(Math.random()*10).toString(16);}this._boundary=boundary;};

これに\r\nを付加したものを区切りにする。
詳しくは、以下の記事に詳しい。

いまさら聞けないHTTPマルチパートフォームデータ送信 - SATOXのシテオク日記


PHPファイルをhtml-loaderにかけたときにエラーが出るときの対策

$
0
0

PHPプロジェクトをejs(ejs-html-loader)で管理していて、共通ヘッダーのincludeなど、?>で終わらないファイルが存在する場合、productionビルドでエラーが出る

Parse Error: <?php

これは html-loader(正確にはそこから利用される html-minifier-terser)のminimizeオプションでパースエラーとなるため。

これを回避するためには html-loader に continueOnParseError: trueを指定してやれば良い。

webpack.config.js
constExtractTextPlugin=require('extract-text-webpack-plugin');module.exports=[{context:path.resolve(__dirname,'dev'),entry:{// ?> で終わらないファイルを含むindex:'./index.php'.header:'./header.php'},output:{path:path.resolve(__dirname,'public'),filename:'[name].php'},module:{rules:[{test:/\.php/,exclude:/node_modules/,use:ExtractTextPlugin.extract({fallback:"raw-loader",use:[{loader:'html-loader',options:{minimize:{continueOnParseError:true}}},{loader:'ejs-html-loader',options:{context:{// タイムスタンプをPHPファイルに渡すとかtimestamp:+newDate()}}}]})}]},plugins:[newExtractTextPlugin('[name].php')]}];

参考: 【備忘録】HTMLMinifierの全オプションについて調査した

Spotify Developer Platform: Spotify APIアクセスしてデータ取得してみてみた

$
0
0

Spotifyは2019年現在、2億3,200万人(うち有料会員数1億800万人)のユーザー音楽配信サービスとしては世界最大手で、5000万以上の音楽やコンテンツが揃っていて、ドライブ、エクササイズ、パーティやリラックスタイムなどに、気分に合った音楽、また、友達、アーティストが作ったプレイリストを聴いたりできます。

そんなSpotifyでは Developer Platformが用意されており、Web API、AndroidやiOS向けのSDKなどがあるので、Play ListやTrackに関する詳細な分析データをAPIアクセスしてでデータ取得し分析できます。
ということで、この取得したデータをDatabsaeへ入れて、Analytics, Machine Learning, BIなどData Scienceしてみてみたいので、まずはAPIアクセスしてデータ取得してみてみます。

・参考: Spotify Developer Platform

ここで必要な技術は、Node.js, JavaScript, Curl, Python, JSONなどです。
ということでやってみてみます。

■Spotify Developers アプリケーション作成

Web API Tutorialを参考に、Node.JSで作成した Spotify API へ認証アクセスするための access Tokenを 取得し,Play List情報をJSONデータで取得してみてみます

● Spotify Developers ユーザー作成とログイン

Spotify Developers ユーザー作成とログイン
① Spotify Developers ユーザー作成
まず、Spotify Developersサイトへアクセスし、Sign up for a free Spotify account here.をクリックして、ユーザーアカウントを作成

01_Login.png

② Spotify Developers ログイン
作成したユーザーでログイン

02_Login.png

● Register Your Application

① CREATE A CLIENT ID
ClientプログラムでアクセスするためのClient IDを作成
[CREATE A CLIENT ID]をクリック
03_Login.png

② CREATE AN APP OR HARDWARE INTEGRATION Step 1/3
REATE AN APP OR HARDWARE INTEGRATION画面で必要情報を入力し[NEXT]をクリック
04_Login.png

③ CREATE AN APP OR HARDWARE INTEGRATION Step 2/3
ここでは商用利用ではないので、[NON-COMMERCIAL]をクリック
05_Login.png

④ CREATE AN APP OR HARDWARE INTEGRATION Step 4/3
確認事項を確認し了承した内容にチェックを入れて、[SUBMIT]をクリック
06_Login.png

● App Settings

① Application(Spotify-demo01)画面
ApplicationをRegisterするとこの画面になり、[EDIT SETTING]をクリック
ここでは、Spotify-demo01という名前で作成しました
07_Login.png

API アクセスするための Client ID と Client Secret を確認

  • Client ID: d9ef929bnjoierg8820bsvkeoi9bo9
  • Client Secret: 18308i390dfb829ves89238erv8932

②EDIT SETTINGS画面
アクセスするには、Redirect URIが必要なので、ここでは、Node.jsサーバーのIP(100.100.100.100)でアクセスできるように Redirect URIs項に、 http://100.100.100.100:8888/callback/を入力
08_login_修正.png

リダイレクトURIに、Spotifyでホワイトリストに登録するアドレスを1つ以上入力します。
このURIにより、OAuth 2.0によるユーザー認証がされます。
ということで、このURI用のサーバーをNode.jsで作成します。

・参考:
 - Authorization Guide
 - Web API Tutorial

● Node.jsインストール

① Node.jsインストール
yumでode.jsをインストール

[opc@tokyo-inst01 〜]$sudo yum install nodejs
読み込んだプラグイン:langpacks, ulninfo
    依存性の解決をしています
    -->トランザクションの確認を実行しています。
    --->パッケージ nodejs.x86_64 1:6.17.1-1.el7 を インストール
    -->依存性の処理をしています: npm = 1:3.10.10-1.6.17.1.1.el7 のパッケージ: 1:nodejs-6.17.1-1.el7.x86_64
    -->依存性の処理をしています: libuv >= 1:1.9.1 のパッケージ: 1:nodejs-6.17.1-1.el7.x86_64
    -->依存性の処理をしています: libuv.so.1()(64bit)のパッケージ: 1:nodejs-6.17.1-1.el7.x86_64
    -->トランザクションの確認を実行しています。
    --->パッケージ libuv.x86_64 1:1.34.0-1.el7 を インストール
    --->パッケージ npm.x86_64 1:3.10.10-1.6.17.1.1.el7 を インストール
    -->依存性解決を終了しました。
依存性を解決しました

    ========================================================================================================
    Package         アーキテクチャー
                                    バージョン                           リポジトリー                 容量
    ========================================================================================================
    インストール中:
    nodejs          x86_64          1:6.17.1-1.el7                       ol7_developer_EPEL          4.7 M
    依存性関連でのインストールをします:
    libuv           x86_64          1:1.34.0-1.el7                       ol7_developer_EPEL          143 k
    npm             x86_64          1:3.10.10-1.6.17.1.1.el7             ol7_developer_EPEL          2.5 M

    ・・・

        インストール:
        git.x86_64 0:1.8.3.1-21.el7_7

        依存性関連をインストールしました:
        perl-Error.noarch 1:0.17020-2.el7                    perl-Git.noarch 0:1.8.3.1-21.el7_7
        perl-TermReadKey.x86_64 0:2.30-20.el7

        完了しました!

③ web-api-auth-examplesインストール

GitHubリポジトリからOAuthサンプルを GitHub: spotify/web-api-auth-examplesからインストール

[opc@tokyo-inst01 spotify]$git clone https://github.com/spotify/web-api-auth-examples
    Cloning into 'web-api-auth-examples'...
    remote: Enumerating objects: 112, done.
    remote: Total 112 (delta 0), reused 0 (delta 0), pack-reused 112
    Receiving objects: 100% (112/112), 27.26 KiB | 0 bytes/s, done.
    Resolving deltas: 100% (41/41), done.

・ web-api-auth-examplesディレクトリ作成確認

[opc@tokyo-inst01 spotify]$ls    web-api-auth-examples

④ npmインストール

OAuthサンプルのコードは、express、request、およびquerystringパッケージに依存します。
作成したフォルダー内に次のコマンドを実行して依存関係をインストールします!

[opc@tokyo-inst01 spotify]$cd web-api-auth-examples/
[opc@tokyo-inst01 web-api-auth-examples]$ls    LICENSE  README.md  authorization_code  client_credentials  implicit_grant  package.json
[opc@tokyo-inst01 web-api-auth-examples]$npm install        npm WARN deprecated request@2.83.0: request has been deprecated, see https://github.com/request/request/issues/3142
        npm WARN deprecated hawk@6.0.2: This module moved to @hapi/hawk. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.
        npm WARN deprecated cryptiles@3.1.4: This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).
        npm WARN deprecated boom@4.3.1: This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).
        npm WARN deprecated sntp@2.1.0: This module moved to @hapi/sntp. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.
        npm WARN deprecated hoek@4.2.1: This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).
        npm WARN deprecated boom@5.2.0: This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).
        web-api-auth-examples@0.0.2 /home/opc/spotify/web-api-auth-examples
        ├─┬ cookie-parser@1.3.2
        │ ├── cookie@0.1.2
        │ └── cookie-signature@1.0.4
        ├─┬ cors@2.8.5
        │ ├── object-assign@4.1.1
        │ └── vary@1.1.2
        ├─┬ express@4.16.4
        │ ├─┬ accepts@1.3.7
        │ │ └── negotiator@0.6.2
        │ ├── array-flatten@1.1.1
        │ ├─┬ body-parser@1.18.3
        │ │ ├── bytes@3.0.0
        │ │ ├─┬ http-errors@1.6.3
        │ │ │ └── inherits@2.0.3
        │ │ ├─┬ iconv-lite@0.4.23
        │ │ │ └── safer-buffer@2.1.2
        │ │ └── raw-body@2.3.3
        │ ├── content-disposition@0.5.2
        │ ├── content-type@1.0.4
        │ ├── cookie@0.3.1
        │ ├── cookie-signature@1.0.6
        │ ├─┬ debug@2.6.9
        │ │ └── ms@2.0.0
        │ ├── depd@1.1.2
        │ ├── encodeurl@1.0.2
        │ ├── escape-html@1.0.3
        │ ├── etag@1.8.1
        │ ├─┬ finalhandler@1.1.1
        │ │ └── unpipe@1.0.0
        │ ├── fresh@0.5.2
        │ ├── merge-descriptors@1.0.1
        │ ├── methods@1.1.2
        │ ├─┬ on-finished@2.3.0
        │ │ └── ee-first@1.1.1
        │ ├── parseurl@1.3.3
        │ ├── path-to-regexp@0.1.7
        │ ├─┬ proxy-addr@2.0.6
        │ │ ├── forwarded@0.1.2
        │ │ └── ipaddr.js@1.9.1
        │ ├── qs@6.5.2
        │ ├── range-parser@1.2.1
        │ ├── safe-buffer@5.1.2
        │ ├─┬ send@0.16.2
        │ │ ├── destroy@1.0.4
        │ │ └── mime@1.4.1
        │ ├── serve-static@1.13.2
        │ ├── setprototypeof@1.1.0
        │ ├── statuses@1.4.0
        │ ├─┬ type-is@1.6.18
        │ │ └── media-typer@0.3.0
        │ └── utils-merge@1.0.1
        ├── querystring@0.2.0
        └─┬ request@2.83.0
        ├── aws-sign2@0.7.0
        ├── aws4@1.9.1
        ├── caseless@0.12.0
        ├─┬ combined-stream@1.0.8
        │ └── delayed-stream@1.0.0
        ├── extend@3.0.2
        ├── forever-agent@0.6.1
        ├─┬ form-data@2.3.3
        │ └── asynckit@0.4.0
        ├─┬ har-validator@5.0.3
        │ ├─┬ ajv@5.5.2
        │ │ ├── co@4.6.0
        │ │ ├── fast-deep-equal@1.1.0
        │ │ ├── fast-json-stable-stringify@2.1.0
        │ │ └── json-schema-traverse@0.3.1
        │ └── har-schema@2.0.0
        ├─┬ hawk@6.0.2
        │ ├── boom@4.3.1
        │ ├─┬ cryptiles@3.1.4
        │ │ └── boom@5.2.0
        │ ├── hoek@4.2.1
        │ └── sntp@2.1.0
        ├─┬ http-signature@1.2.0
        │ ├── assert-plus@1.0.0
        │ ├─┬ jsprim@1.4.1
        │ │ ├── extsprintf@1.3.0
        │ │ ├── json-schema@0.2.3
        │ │ └─┬ verror@1.10.0
        │ │   └── core-util-is@1.0.2
        │ └─┬ sshpk@1.16.1
        │   ├── asn1@0.2.4
        │   ├── bcrypt-pbkdf@1.0.2
        │   ├── dashdash@1.14.1
        │   ├── ecc-jsbn@0.1.2
        │   ├── getpass@0.1.7
        │   ├── jsbn@0.1.1
        │   └── tweetnacl@0.14.5
        ├── is-typedarray@1.0.0
        ├── isstream@0.1.2
        ├── json-stringify-safe@5.0.1
        ├─┬ mime-types@2.1.26
        │ └── mime-db@1.43.0
        ├── oauth-sign@0.8.2
        ├── performance-now@2.1.0
        ├── stringstream@0.0.6
        ├─┬ tough-cookie@2.3.4
        │ └── punycode@1.4.1
        ├── tunnel-agent@0.6.0
        └── uuid@3.4.0

        npm WARN web-api-auth-examples@0.0.2 No repository field.
        npm WARN web-api-auth-examples@0.0.2 No license field.

●index.html作成

Web rootディレクトリ authorization_codeへindex.htmlファイルを設定

① Web rootディレクトリ authorization_codeへ移動

[opc@tokyo-inst01 web-api-auth-examples]$cd authorization_code
[opc@tokyo-inst01 authorization_code]$pwd    /home/opc/spotify/web-api-auth-examples/authorization_code
[opc@tokyo-inst01 authorization_code]$ls    app.js  app.js.org  public

② index.htmlファイル設定

Web API Tutorialにおるように以下内容を設定

[opc@tokyo-inst01 authorization_code]$vi index.html
<html><head>    ...
<style type="text/css">    #login {        display: none;    }

    #loggedin {        display: none;    }
    ...
</style><body><div><div id="login"><h1>First, log in to spotify</h1>
<a href="/login">Log in</a>
</div><div id="loggedin"></div></div><script id="loggedin-template" type="text/x-handlebars-template"><h1>Logged in as </h1>
<img id="avatar" width="200" src="" /><dl><dt>Display name</dt><dd></dd>
<dt>Username</dt><dd></dd>
<dt>Email</dt><dd></dd>
<dt>Spotify URI</dt><dd><a href=""></a></dd>
<dt>Link</dt><dd><a href=""></a></dd>
<dt>Profile Image</dt><dd></dd>
</dl><p><a href="/">Log in again</a></p>
</script></body></html>

●Provide the Application Credentials

app.jsファイルには、アプリケーションのメインコードが含まれおり、
Spotify Developersで作成したアプリケーションの'CLIENT_ID,'CLIENT_SECRET','REDIRECT_URI' 情報を設定します。

・app.jsファイル設定箇所
    var client_id = 'CLIENT_ID'; // Your client id
    var client_secret = 'CLIENT_SECRET'; // Your secret
    var redirect_uri = 'REDIRECT_URI'; // Your redirect uri

① app.jsファイル設定

ということで、'CLIENT_ID', 'CLIENT_SECRET, 'REDIRECT_URI'部分を代入変更します。

[opc@tokyo-inst01 authorization_code]$vi app.js
    /**
    * This is an example of a basic node.js script that performs
    * the Authorization Code oAuth2 flow to authenticate against
    * the Spotify Accounts.
    *
    * For more information, read
    * https://developer.spotify.com/web-api/authorization-guide/#authorization_code_flow
    */

    ・・・

    var client_id = 'e15bfd0dccb54db5a1757b';// Your client id    var client_secret = 'e17644952de94c6baebef';// Your secret
    var redirect_uri = 'http://100.100.100.100:8888/callback';// Your redirect uri
・・・

■Running the Application

設定した Node.jsアプリケーションを起動し、Spotifyへ認証アクセスするためのAccess tokenを取得します

① 設定した Node.jsアプリケーションを起動

[opc@tokyo-inst01 authorization_code]$node app.js
    Listening on 8888

② Node.jsアプリケーションのURLへアクセス

設定したURL http://100.100.100.100:8888へWebブラウザでアクセス
11_8888.png

③ ログイン

[Log in with Spotify]をクリック

④ Access Token取得

ログインすると Access tokenが出力されます。
これを使用して、Spotifyへ認証アクセスして、Play Listやアーティスト情報を取得します。
12_8888.png

■テスト:Play Listデータ取得

Play List「Spotify Japan 急上昇チャート」の情報を取得してみています。
・potify Japan 急上昇チャート: https://open.spotify.com/playlist/37i9dQZF1DX9vYRBO9gjDe
21_急上昇チャート.png

① Play list ID取得
APIリファレンス: API ENDPOINT REFERENCEを参考にPlay List情報を取得

例) エンドポイントに以下「id」を指定する必要があります
  - playlists:/v1/playlists/{playlist_id}/tracks
  - partists:/v1/artists/{id}/albums

「id」は以下になります。
  ・プレイリスト例:Sporify Japan 急上昇チャート: https://open.spotify.com/playlist/37i9dQZF1DX9vYRBO9gjDe
    -->「37i9dQZF1DX9vYRBO9gjDe」がプレイリストid
  ・アーティスト例:チャイコフスキー: https://open.spotify.com/artist/3MKCzCnpzw3TjUYs2v7vDA
    -->3MKCzCnpzw3TjUYs2v7vDA 」がアーティストidになります。

② Access token確認とPlay List取得Curl作成
上記Node.jsアプリケーションで取得した、Access token: BQDQMF8bDTWt2vBFTRaI0bbIL5RIXlc-wIO5yRWtTzLyBKHsGUEbs1-668aBJ3bIRYpdZnKT...を
以下Curl文のへ代入

curl -H "Authorization: Bearer <Accesstoken>" https://api.spotify.com/v1/playlists/37i9dQZF1DX9vYRBO9gjDe/tracks

② 実行
作成したcurl実行しPlay Listデータを取得
データはJSONで出力されます

[opc@tokyo-inst01~]$curl-H"Authorization: Bearer BQDQMF8bDTWt2vBFTRaI0bbIL5RIXlc-wIO5yRWtTzLyBKHsGUEbs1-668aBJ3bIRYpdZnKT..."https://api.spotify.com/v1/playlists/37i9dQZF1DX9vYRBO9gjDe/{"collaborative":false,"description":"Spotify Japanのデイリー急上昇チャート。4月1日付。","external_urls":{"spotify":"https://open.spotify.com/playlist/37i9dQZF1DX9vYRBO9gjDe"},"followers":{"href":null,"total":148523},・・・<省略>・・・}],"limit":100,"next":null,"offset":0,"previous":null,"total":50},"type":"playlist","uri":"spotify:playlist:37i9dQZF1DX9vYRBO9gjDe"

\( ^o^ )/

ということで、今度はこの一連の処理を Python3で作成して実行してみてみます。
つづく・・・

■参考

● Spotify Developer

 ・Spotify Developer
 ・Web API Reference
 ・Web API Tutorial
 ・Authorization Guide

● Git

  - spotify-web-api-node
  - Spotify Charts

● Google Chormeプラグイン

  - Talend API Tester - Free Edition

[Node.JS] StreamAPIを使ったCSVの書き読み出し

$
0
0

今回は、Stream使って配列をCSVにして保存したり、CSVから配列を読み込んだりとかを業務で使ったのでそれの共有です。

今回やること

  • data.write(['a','b'])みたいに書いたらファイルにa,b\nみたいに追加されて欲しい。
  • 逆にファイルにa,b\nって書いてあったら、['a','b']みたいに1行づつの情報が欲しい。

そもそもStremAPIとは?

streamAPIの素晴らしさを知らない人のために少し解説します。
すでに使ってる人は飛ばしても大丈夫です。

公式リンク
Stream API
曰く

streamはデータストリームをするための抽象的インターフェースです。

A stream is an abstract interface for working with streaming data in Node.js.

YoutTubeとかでは動画一気に読み込むとクソ重くなるからちょっとづつ読み込むようになっていますが、これがデータストリーミング。
でもこれは動画だけの話ではありません。
例えば、配列をtxtファイルに1列に保存したいとかってよくあると思います。
その時に、配列を文字列に変換して一気にファイルに保存しちゃうとデカイ配列を変換すると変換と保存に時間とメモリを食われてあまりいい手ではありません。
そこでStreamAPIです。
これを使うと、1要素ごとに保存させたりができるのですごく楽です。しかもPromiseとかコールバックは使わない点も良き
例えば以下のコードのようになります。

import*asfsfrom'fs'import*aspathfrom'path'constDATA_PATH=path.join(__dirname,'../data/data.txt')constdataWriteStream=fs.createWriteStream(DATA_PATH)constarr=['Hello','World','Hoge']arr.forEach(val=>dataWriteStream.write(val+'\n'))

fsはファイルの読み書き取りでよく使われるモジュールですが、createWriteStreamでファイルを書き出すstreamを作ります。そしてwrite関数にファイルの末尾に追加したい文字列を入れるだけです。

逆に読み取るときは以下になります。

import*asfsfrom'fs'import*aspathfrom'path'constDATA_PATH=path.join(__dirname,'../data/data.txt')constdataReadStream=fs.createReadStream(DATA_PATH,{encoding:'utf-8'})dataReadStream.on('data',chunk=>console.log(chunk))

on関数の第一引数にdataを入れて1行づつ読み取り、成功した時にコールバック関数を実行します。

配列をCSVに保存する方法

さてここからが本題です。
具体的に今回作るstreamの流れは

1行分の配列のデータを渡す(['a','bb']みたいな)

そのデータをCSV形式の文字列に変換

その文字列をファイルに保存

みたいな形です。
今回CSV文字列に変換するのにはcsv-stringifyというモジュールを使います。

これを実装するためにはまずWritableというクラスを継承したクラスを作ります。
そして、write関数が呼ばれた時にそのデータをcsv-stringifyに渡す。
そしてその結果をwriteStreamでファイルに追加みたいな感じです。

コードは以下になります。

import*asfsfrom'fs'import{Writable,Readable}from'stream'import*asstringifyfrom'csv-stringify'exportclassWritableCSVStreamextendsWritable{privatestringifier:stringify.StringifierprivatewriteStream:fs.WriteStreamconstructor(path:string,options:stringify.Options={}){super({objectMode:true})this.writeStream=fs.createWriteStream(path)this.stringifier=stringify(options)this.stringifier.on('data',chunk=>{this.writeStream.write(chunk)}).on('error',err=>{this.destroy(err)})this.writeStream.on('error',err=>this.destroy(err))}_write(chunk:any,encoding:string,next:(error?:Error)=>void){this.stringifier.write(chunk,next)}_final(next:()=>void){this.writeStream.close()this.stringifier.end()next()}_destroy(err?:Error,next?:(err?:Error)=>void){this.writeStream.close()this.stringifier.end()next()}}

_writeにはwrite関数が呼ばれた時の動作、_destroyはエラー終了した時、_finalは終了時に呼ばれます。
closeend関数はストリームを終了させる役割を持ちます。
そして一番大事なのは、コンストラクターの中身です。
objectModetrueにすることでwrite関数にオブジェクトや配列を入れても大丈夫になります。
そしてオブジェクトをCSV形式に変換するstringifierとファイルに出力するwriteStreamon関数を使って繋げます。
そして_write関数でthisstringifierを繋げます。

CSVファイルの読み出し

CSVファイルの読み出しも上と同じようにもできるのですが、pipe関数でもっと簡単に書けます。
pipe関数はストリームを別のストリームにデータを受け渡しをできるようにします。

import*asparsefrom'csv-parse'exportconstcreateCSVReadStream=(path:string,options:parse.Options={})=>fs.createReadStream(path).pipe(parse(options))

これでfs.createReadStreamからファイル1行づつ読み出し、それをparseでオブジェクトに変換するようになってます。

終わりに

最初はCSV書き出しの方ももっと簡単に書きたかったのですが、stringify部分がうまくいかずにクラスを実装してみました。データをjsonで保存するのもstreamでできたりするのですが、暇だったらその記事も書きます。

JavaScript (Node.js) の非同期処理とシングルスレッド

$
0
0

本記事の目的

JavaScriptNode.jsはよくシングルスレッドだ〜、と言われますが、では非同期処理はどうやって実行されているのか (Non-Blocking I/O) をざっくりと (私の身内に) 説明する為のサンプルコードです。

Node.js, V8のコードレベルでちゃんと理解したいのであれば、以下のサイトが大変参考になりました。

検証環境

  • iMac (Retina 5K, 27-inch Late 2014), 4 GHz Intel Core i7
  • Node.js v12.13.0
$ nodebrew install-binary v12.13.0
$ nodebrew use v12.13.0

ブラウザ JavaScript の Event loopはまたちょっと違います。

早速サンプルコードから

以下の様な JavaScript index.jsを、Node.jsで実行します。

  1. 【処理 1】ミリ秒で終わる処理を setTimeout()で 5 秒後に発火.
  2. 【処理 2】ミリ秒で終わる処理を setTimeout()で 0 秒後に発火.
  3. 【処理 3】10 秒かかる同期処理を実行.
  • 時間の計測には Node.js標準 API の perf_hooksモジュールを使用しています。 Node.jsプロセス実行開始からのミリ秒を得られます
  • コード中では、ミリ秒 → 秒、に変換して表示しています
index.js
const{performance}=require('perf_hooks');/**
 * @return 本スクリプトを実行してからの経過秒数.
 */constseconds=()=>performance.now()/1000;constsecondsPadded=()=>seconds().toFixed(6).padStart(10,'');// 長さ揃える.//////////////// 処理3つ /////////////////**
 * 処理 1 (非同期, 5 秒後に発火).
 */constfunc1=()=>{console.log(`${secondsPadded()} seconds --> 処理 1 (非同期, 5 秒後に発火)`);};/**
 * 処理 2 (非同期, 0 秒後に発火).
 */constfunc2=()=>{console.log(`${secondsPadded()} seconds --> 処理 2 (非同期, 0 秒後に発火)`);};/**
 * 処理 3 (同期. 10 秒かかる).
 */constfunc3=()=>{while(seconds()<10){/* consuming a single cpu for 10 seconds... */}console.log(`${secondsPadded()} seconds --> 処理 3 (同期, 10 秒かかる)`);};//////////////// 計測開始 ////////////////console.log(`${secondsPadded()} seconds --> index.js START`);// [非同期] 5 秒後に実行.setTimeout(func1,5000);// [非同期] 即時実行.setTimeout(func2);// 同期実行.func3();console.log(`${secondsPadded()} seconds --> index.js END`);//////////////// 計測終了 ////////////////

期待値?

なんとなく 「こう動作するだろう...」という気分になるのは ↓ でしょう。

$ node index.js

  0.000000 seconds --> index.js START
  0.000000 seconds --> 処理 2 (非同期, 0 秒後に発火)
  5.000000 seconds --> 処理 1 (非同期, 5 秒後に発火)
 10.000000 seconds --> 処理 3 (同期, 10 秒かかる)
 10.000000 seconds --> index.js END

実際は...

現実はこうです。何故でしょうか。

$ node index.js 

  0.175104 seconds --> index.js START
 10.000085 seconds --> 処理 3 (同期, 10 秒かかる)
 10.000210 seconds --> index.js END
 10.000955 seconds --> 処理 2 (非同期, 0 秒後に発火)
 10.001161 seconds --> 処理 1 (非同期, 5 秒後に発火)

シングルスレッドだから、順番に処理している

おおよそ、Node.jsの内部では ↓ のように処理がシングルスレッドで行われています。

  1. JavaScript コンテキストの生成時にイベントループが生成されます
  2. 最初のエントリ JavaScript index.jsがタスクとして、未実行キューに乗ります
  3. イベントループ
    1. 未実行キューから index.jsタスクが取り出され、実行が開始されます
      1. setTimeout(処理1, 5秒)が実行され、【処理 1】がタイマーキューに追加されます
      2. setTimeout(処理2, 0秒)が実行され、【処理 2】がタイマーキューに追加されます
      3. 【処理 3】が同期的に実行され、10 秒間、CPU (シングルコア) を専有します
    2. index.jsタスクの実行が終了します
  4. イベントループ
    1. タイマーキューから 有効期限が切れたタスク【処理 2】を取り出し、実行が開始されます
    2. 【処理 2】タスクの実行が終了します
  5. イベントループ
    1. タイマーキューから 有効期限が切れたタスク【処理 1】を取り出し、実行が開始されます
    2. 【処理 1】タスクの実行が終了します

実際はタイマー Phase はキューではない (FIFO でもない) ですが、説明の都合上そう表記しました。

JavaScript Non-Blocking I_O Architecture.png

要はイベントループにて、実行可能なタスクがあれば即時実行し、なければ I/O 待ち (epoll) をすることになります。

結論

つまり、setTimeout()等の非同期タイマー処理は...

  • 指定した時間が来たら即座に Callback を実行する. (OS 割り込みみたいに)

ではなく...

  • 指定した時間を 過ぎてたら Callback を できるだけ早く実行する

ですね。

それは Promiseや、Network Socket I/O 待ちである fetchでも同じで...

  • Callback が実行可能になってから (現在実行中の他の処理を待って) 順番が来たら (やっと) 実行開始する

です。

参考文献

nowのデプロイで古い書き方からのマイグレートメモ

$
0
0

now.shを使うときにv2系の書き方でも警告が出るようになってますね。
もう1系は使えないのかも。

nowで詰まった人がいたのでメモ。

今まで書いてたやり方と修正点

これまでは、これでよかったのですが......

now.json
{"version":2,"name":"mylinebot","builds":[{"src":"server.js","use":"@now/node"}],"routes":[{"src":"/","dest":"server.js"},{"src":"/webhook","dest":"server.js"}]}
now --target production

nameプロパティの注意

まずはここ。

❗️  The `name` property in now.json is deprecated (https://zeit.ink/5F)

ここを読むと、書いてますが

NOTE: The name property has been deprecated in favor of Project Linking, which allows you to link a ZEIT Now Project to your local codebase when you run now.

nameプロパティが非推奨と言われます。

なのでnameプロパティを外します。修正版はこちら。

now.json
{"version":2,"builds":[{"src":"server.js","use":"@now/node"}],"routes":[{"src":"/","dest":"server.js"},{"src":"/webhook","dest":"server.js"}]}

デプロイコマンド

次にここです。

WARN! We recommend using the much shorter `--prod` option instead of `--target production` (deprecated) 

もともとの書き方のnow --target productionnow --prodで良いよと言われます。短い方が良いですね。

now --prod

これでOKです。

おまけ: 実行時

実際のデプロイで表示されるコンソールの紹介です。

対話的に質問されます。"deployの設定をしますか?"的な質問です。エンターかYをタイプして進みましょう。

Now CLI 17.1.1
? Set up and deploy “~/Documents/ds/playground/mylinebot”? [Y/n] ←ここでエンターもしくはY

次にデプロイ先のアカウントを選択。 たぶんチームアカウントとかあると選択肢に載ってくるんだと思いますが、たぶん最初は自分のアカウントだけなので自分のアカウントが表示されるのを確認したらエンター。

? Which scope do you want to deploy to? 
● n0bisuke ←ここでエンター

次になんて名前でデプロイするか聞かれます。package.jsonのnameプロパティに書いてある名前が表示されるので、エンターかYをタイプして進みます。

? Found project “n0bisuke/mylinebot”. Link to it? [Y/n] ←ここでエンターもしくはY

これでデプロイできるはず......!

LINE BOTで天気を返すサンプルがngrokで動いてnowで動かない件

$
0
0

こちらのサンプルがngrokで動いて、Nowだとうまく動かない件の対応。

axiosを使って別のサーバーにリクエストを出してるので非同期処理のあたりが怪しいですね。

もとのコード

これだとngrokでうまく動くけど、now上でうまく動かないというレポート

server.js
省略functionhandleEvent(event){if(event.type!=='message'||event.message.type!=='text'){returnPromise.resolve(null);}letmes=''if(event.message.text==='天気教えて!'){mes='ちょっとまってね';//待ってねってメッセージだけ先に処理getNodeVer(event.source.userId);//スクレイピング処理が終わったらプッシュメッセージ}else{mes=event.message.text;}returnclient.replyMessage(event.replyToken,{type:'text',text:mes});}constgetNodeVer=async(userId)=>{constres=awaitaxios.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=400040');constitem=res.data;awaitclient.pushMessage(userId,{type:'text',text:item.description.text,});}省略

書き換え

replyとpushのタイミングを変えてみる。

server.js
省略asyncfunctionhandleEvent(event){if(event.type!=='message'||event.message.type!=='text'){returnPromise.resolve(null);}//"天気教えて"以外の場合は反応しないif(event.message.text!=='天気教えて'){returnclient.replyMessage(event.replyToken,{type:'text',text:'"天気教えて"と言ってね'});}letmes='';mes='ちょっと待ってね';//"ちょっと待ってね"ってメッセージだけ先に処理awaitclient.replyMessage(event.replyToken,{type:'text',text:mes});//axiosを使って天気APIにアクセスconstCITY_ID=`400040`;//ライドアのAPIから取得したいシティのIDをconstURL=`http://weather.livedoor.com/forecast/webservice/json/v1?city=${CITY_ID}`;constres=awaitaxios.get(URL);constitem=res.data;returnclient.pushMessage(event.source.userId,{type:'text',text:item.description.text,});}省略

おまけ: 関数分けサンプル

関数に分けるとこんな感じ。

server.js
省略asyncfunctionhandleEvent(event){if(event.type!=='message'||event.message.type!=='text'){returnPromise.resolve(null);}//"天気教えて"以外の場合は反応しないif(event.message.text!=='天気教えて'){returnclient.replyMessage(event.replyToken,{type:'text',text:'"天気教えて"と言ってね'});}letmes='';mes='ちょっと待ってね';//"ちょっと待ってね"ってメッセージだけ先に処理awaitclient.replyMessage(event.replyToken,{type:'text',text:mes});constCITY_ID=`400040`;//ライドアのAPIから取得したいシティのIDをreturngetWeather(event.source.userId,CITY_ID);}constgetWeather=async(userId,CITY_ID)=>{//axiosを使って天気APIにアクセスconstURL=`http://weather.livedoor.com/forecast/webservice/json/v1?city=${CITY_ID}`;constres=awaitaxios.get(URL);constitem=res.data;returnclient.pushMessage(userId,{type:'text',text:item.description.text,});}省略

Raspberry Pi 4(1台)でKubernetes環境を構築 (Raspberry Pi 4 + Ubuntu 19.10 + MicroK8s) Part2 (Webアプリ編)

$
0
0

前回の記事

Raspberry Pi 4(1台)でKubernetes環境を構築 (Raspberry Pi 4 + Ubuntu 19.10 + MicroK8s)

前回の記事では「Raspberry Pi 4」に「Ubuntu 19.10」OSを入れ、「MicroK8s」をインストールし、Kubernetes環境構築と簡単な動作確認するところまで行いました。

Raspberry Pi 4 組み立て2.jpg

今回の記事

今回の記事では実際にkubernetesのマニフェストファイルを作成し、ラズパイ上でWebアプリケーション(express)を動かしてみたいと思います。
環境については前回の記事まで出来ている前提で進めています。

とりあえず何か動くもの?

試すWebアプリケーションは何でもよかったのですが、Dockerイメージ作成から行ってみたかったので
少し調べてみたところ、Node公式のexpressの記事が丁寧に書いていたのでこれをもとにdockerイメージを作成してみようと思います。

docker のインストール

docker イメージ作成のため docker build を行いたいので、まずはdockerをインストールします。

sudo apt install docker.io
sudo systemctl enable --now docker
sudo usermod -aG docker $USER

Node.js Web アプリケーション の Docker イメージ作成

Node.js公式の
Node.js Web アプリケーションを Docker 化する
を参考にDockerイメージを作成していきます。

ファイルを準備

適当なフォルダを作成し、そこにファイルを作っていきます。
※ 文字コード: UTF-8 , 改行コード: LF

$ mkdir docker_web_app
package.json
{"name":"docker_web_app","version":"1.0.0","description":"Node.js on Docker","author":"First Last <first.last@example.com>","main":"server.js","scripts":{"start":"node server.js"},"dependencies":{"express":"^4.16.1"}}
server.js
'use strict';constexpress=require('express');// ConstantsconstPORT=8080;constHOST='0.0.0.0';// Appconstapp=express();app.get('/',(req,res)=>{res.send('Hello World');});app.listen(PORT,HOST);console.log(`Running on http://${HOST}:${PORT}`);

Dockerイメージを作成するためのDockerfile

FROM node:12# アプリケーションディレクトリを作成するWORKDIR /usr/src/app# アプリケーションの依存関係をインストールする# ワイルドカードを使用して、package.json と package-lock.json の両方が確実にコピーされるようにします。# 可能であれば (npm@5+)COPY package*.json ./RUN npm install# 本番用にコードを作成している場合# RUN npm install --only=production# アプリケーションのソースをバンドルするCOPY . .EXPOSE 8080CMD [ "node", "server.js" ]

dockerイメージを作成

ubuntu@ubuntu:~/node-web-app$ docker build -t node-web-app-test .
Sending build context to Docker daemon  4.608kB
Step 1/7 : FROM node:12
12: Pulling from library/node
84582781a9f0: Already exists
2aaedfc98967: Already exists
cfa6bb03021f: Already exists
6a4110ff81d0: Already exists
e867f7ebdcf1: Already exists
2f16af5fb418: Already exists
cfc1102c13fa: Pull complete
9e6855539b2a: Pull complete
b5c194de032e: Pull complete
Digest: sha256:46f4c17e1edbde36d60a9f6362db7912cfe301bac89afef7cc92421ab3e7ca18
Status: Downloaded newer image for node:12
 ---> 694bb044bce1
Step 2/7 : WORKDIR /usr/src/app
 ---> Running in 536f08e477ba
Removing intermediate container 536f08e477ba
 ---> 12ccd9c86b57
Step 3/7 : COPY package*.json ./
 ---> ef6d92e05eba
Step 4/7 : RUN npm install
 ---> Running in ce1481d407dc
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN docker_web_app@1.0.0 No repository field.
npm WARN docker_web_app@1.0.0 No license field.

added 50 packages from 37 contributors and audited 126 packages in 4.84s
found 0 vulnerabilities

Removing intermediate container ce1481d407dc
 ---> c83c3ed164a4
Step 5/7 : COPY . .
 ---> 1d1529685c13
Step 6/7 : EXPOSE 8080
 ---> Running in 4641a7aeda76
Removing intermediate container 4641a7aeda76
 ---> 667730c72077
Step 7/7 : CMD [ "node", "server.js" ]
 ---> Running in 28e2e97e3e4b
Removing intermediate container 28e2e97e3e4b
 ---> 8a4d60b76312
Successfully built 8a4d60b76312
Successfully tagged node-web-app-test:latest

作成できたイメージを、確認のため動かしてみる

ubuntu@ubuntu:~/node-web-app$ docker run -p 49160:8080 -d node-web-app-test
1ca0cb8ecb347fdc66ce1c1d6a6e9580f5733e58fa66bb3b34da68411aa0184a

ブラウザでアクセスしてみる

私のラズパイのIPが192.168.11.20だったので
http://192.168.11.20:49160
で確認
node-web-app-test.png

アクセス出来ましたね

とりあえず一度掃除します

docker stop と docker rm を実行

ubuntu@ubuntu:~/node-web-app$ docker ps
CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS              PORTS                     NAMES
1ca0cb8ecb34        node-web-app-test       "docker-entrypoint.s…"   7 minutes ago       Up 7 minutes        0.0.0.0:49160->8080/tcp   lucid_dubinsky
ubuntu@ubuntu:~/node-web-app$ docker stop 1ca0cb8ecb34
1ca0cb8ecb34
ubuntu@ubuntu:~/node-web-app$ docker rm 1ca0cb8ecb34
1ca0cb8ecb34

これできれいになりましたね。

MicroK8sでローカルのdockerイメージを使用

MicroK8sでローカルのdockerイメージを使用するためには一工夫いるみたいです。

イメージ確認

ubuntu@ubuntu:~/node-web-app$ docker images | grep node-web-app
node-web-app-test           latest              8a4d60b76312        36 minutes ago      868MB

イメージタグを:localに変更

ubuntu@ubuntu:~/node-web-app$ docker tag node-web-app-test:latest node-web-app-test:local

イメージ確認

ubuntu@ubuntu:~/node-web-app$ docker images | grep node-web-app
node-web-app-test           latest              8a4d60b76312        37 minutes ago      868MB
node-web-app-test           local               8a4d60b76312        37 minutes ago      868MB

イメージをセーブ

ubuntu@ubuntu:~/node-web-app$ docker save node-web-app-test:local > image.tar

イメージを microk8s へロード

ubuntu@ubuntu:~/node-web-app$ microk8s ctr image import image.tar
unpacking docker.io/library/node-web-app-test:local (sha256:2e85de5a19c3cd98f07d8ae3f039ff6f47076e717a41a35b4da23ce0309d9243)...done

microk8s のイメージを確認

ubuntu@ubuntu:~/node-web-app$ microk8s ctr images ls | grep node-web-app
docker.io/library/node-web-app-test:local
application/vnd.oci.image.manifest.v1+json
sha256:2e85de5a19c3cd98f07d8ae3f039ff6f47076e717a41a35b4da23ce0309d9243 856.4 MiB linux/arm64
io.cri-containerd.image=managed

これでローカルのdockerイメージを使用できるようになりました。

kubernetesのマニフェストファイル作成

Deploymentを作成しておくと、何か障害が起きてシステムエラーでコンテナ(pod)が落ちても、コンテナ(pod)を再度起動してくれるようになります。
※ マニフェストファイル作成の単位の基準はないのですが、私はkindごとに分けるようにしています。

deployment.yaml
apiVersion:apps/v1kind:Deploymentmetadata:name:node-web-app-testspec:replicas:1selector:matchLabels:app:node-web-app-testtemplate:metadata:labels:app:node-web-app-testspec:containers:-name:node-web-app-testimage:node-web-app-test:localimagePullPolicy:Never# ローカルのイメージ使用の場合のみrestartPolicy:Always
service.yaml
apiVersion:v1kind:Servicemetadata:name:node-web-app-testspec:selector:app:node-web-app-testports:-protocol:TCPport:8080nodePort:31200type:NodePort

デプロイ実行

ubuntu@ubuntu:~/node-web-app$ kubectl apply -f deployment.yaml
deployment.apps/node-web-app-test created

ポッドの確認

ubuntu@ubuntu:~/node-web-app$ kubectl get po
NAME                                 READY   STATUS    RESTARTS   AGE
node-web-app-test-69f4c59b6f-bwsrv   1/1     Running   0          56m

サービス実行

ubuntu@ubuntu:~/node-web-app$ kubectl apply -f service.yaml
service/node-web-app-test configured

サービスの確認

ubuntu@ubuntu:~/node-web-app$ kubectl get svc
NAME                TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes          ClusterIP   10.152.183.1     <none>        443/TCP          35d
node-web-app-test   NodePort    10.152.183.157   <none>        8080:31200/TCP   74m

ブラウザでアクセスしてみる

私のラズパイのIPが192.168.11.20だったので
http://192.168.11.20:31200
で確認
node-web-app-test-2.png

アクセス出来ましたね!!!

まとめ

とりあえずWebアプリケーションを起動できましたね(●´ω`●)

確認したソースもgithub/microk8s-node-web-app-testにあげています。

少し調べるところもありましたが、比較的簡単に起動できました。
Dockerイメージはローカル分を使うのではなく、DockerHub にでもPushしたほうがもっと楽に出来そうですね(´・ω・`)

気力があれば、ラズパイらしくGPIO周りも試していきたいと思います!!


LINE botから画像送信~問いかけると柴犬の画像を返してくれるLINE botを作ってみた

$
0
0

概要

 LINE botでできることを調べていたら、画像を返すことが可能とのことで試してみました。
ただ画像を返すだけでは面白くないので、柴犬APIで画像を拾ってきて表示することにしました。最後にソースコードの全体を載せています。

 柴犬APIについてはこちらをご参照ください。実際の動きは次のようになります。

【デモ】

構成

 主な構成は次の通りです。LINE botからテキストを送ると、ローカルにあるNode.jsサーバにWebhookが投げられ、LINE Messaging APIのフォーマットに従って画像URlが返されます。

画像送信の構成図.jpg

作ってみる

開発環境

OS:Windows 10
Node.js:v10.15.3

【ライブラリバージョン】
@line/bot-sdk:6.8.4
express:4.17.1
axios:0.19.2

プログラム解説

プログラムの全体は最後に載せるとして、ここでは重要なところのみ解説します。
LINE botのサーバで、画像を返すフォーマットは次の通りです。

returnclient.replyMessage(event.replyToken,{type:'image',originalContentUrl:'オリジナルサイズの画像URL',previewImageUrl:'LINEアプリのトーク画面にプレビューされるサイズの画像URL'});

 画像URLを指定するため、GoogleドライブやDropboxなどのストレージを使う必要があります。
 また、使用できる画像サイズに限度があるようです。詳しくはこちらのLINE Developerサイトを参照してください。

 今回の例では、次のように柴犬APiを利用して得られた画像URLを、固定で入力しています。

returnclient.replyMessage(event.replyToken,{type:'image',originalContentUrl:'https://cdn.shibe.online/shibes/907fed97467e36f3075211872d98f407398126c4.jpg',previewImageUrl:'https://cdn.shibe.online/shibes/907fed97467e36f3075211872d98f407398126c4.jpg'});

余談

 ここまで至るにも苦労しました(;'∀')
なぜか画像が送れないなーと色々悩んでいて、最終的にはDiscordのProtoOutStudioの技術質問チャンネルに投げかけました。嬉しいことに2期生の方が動作確認して誤り個所を教えていただきました。本当に感謝しています。
 結局は返すフォーマットが間違っていただけなんて....。次からはもっとちゃんと仕様を読んで理解せねば!

 と、いうことで画像を送ることができました。次は自宅で稼働している菜園管理システムとつなげて、画像を一定時間ごとに送れるようにしていこうと思います。

参考

LINE Messaging API でできることまとめ【送信編】

ソースコード

'use strict';constaxios=require('axios');// 追加constexpress=require('express');constline=require('@line/bot-sdk');constPORT=process.env.PORT||3000;constconfig={channelSecret:'LINE botのチャンネルシークレット',channelAccessToken:'LINE botのアクセストークン'};constapp=express();app.get('/',(req,res)=>res.send('Hello LINE BOT!(GET)'));//ブラウザ確認用(無くても問題ない)app.post('/webhook',line.middleware(config),(req,res)=>{console.log(req.body.events);//ここのif文はdeveloper consoleの"接続確認"用なので後で削除して問題ないです。if(req.body.events[0].replyToken==='00000000000000000000000000000000'&&req.body.events[1].replyToken==='ffffffffffffffffffffffffffffffff'){res.send('Hello LINE BOT!(POST)');console.log('疎通確認用');return;}Promise.all(req.body.events.map(handleEvent)).then((result)=>res.json(result));});constclient=newline.Client(config);functionhandleEvent(event){if(event.type!=='message'||event.message.type!=='text'){returnPromise.resolve(null);}returnclient.replyMessage(event.replyToken,{type:'image',originalContentUrl:'https://cdn.shibe.online/shibes/907fed97467e36f3075211872d98f407398126c4.jpg',previewImageUrl:'https://cdn.shibe.online/shibes/907fed97467e36f3075211872d98f407398126c4.jpg'});}app.listen(PORT);console.log(`Server running at ${PORT}`);

Glitchで、ERROR EACCES: permission denied がコンソールに表示され動かないときの対処法

$
0
0

対処方法

  1. Terminalを開く

コマンドライン (プロジェクトのパッケージが強制的に再インストール コマンド)

$enable-pnpm

を実行する。
2. しばらくたつと動く。
3. 以上!!
glitch.com

【knex】this.dialectに関してのエラー解決

$
0
0

エラー文

Using 'this.dialect' to identify the client is deprecated and support for it will be removed in the future. Please use configuration option 'client' instead.

解決法

varknex=require('knex')({dialect:'mysql',connection:{host:'localhost',user:'root',password:'(パスワード)',database:'(データベース名)',charset:'utf-8'}});

上の文の、dialectをclientに変更する。

typescriptを使ってjavascriptのシンタックスシュガーを理解する

$
0
0

背景

typescriptを勉強していて、アロー演算子構文の読み方がわからない。ドキュメントを読んでもよくわからなかった、ということがありました。
そんな時、tscコマンドを使うことで難解なシンタックスシュガーへの理解のヒントになることに気が付きました。
それについて紹介します。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Arrow_functions

たとえば下記アロー演算子の構文がわかりませんでした。

['bbbbb', '3'].map(({ length }) => length)
仮引数に {}使とは、、、?みたいな感じです。
出力内容から見ると、lengthプロパティを返していることを察することができましたが、いまいち腑に落ちませんでした。

tscコマンドの出番です

typescriptをjavascritに変更するコマンドがtscです。

arrow.ts
['bbbbb','3'].map(({length})=>length)

上記のようにをファイルに書き出して、tscコマンドを実行します。
tsc arrow.tsすると、arrow.jsというファイルが生成されます。中身を見るとシンタックスシュガーを使わずに実装されたjavascriptができています。

arrow.js
['bbbbb','3'].map(function(_a){varlength=_a.length;returnlength;});

arrow.jsを読むと、 ['bbbbb', '3'].map(({ length }) => length)がどのように実行されているのかがわかりました。
ローカル変数にlengthを格納しておいて、そのローカル変数をreturnしていたんですね。
すごいです。これは理解できます。

他の「初見殺し構文」を読んでみる

難解.ts
varf=([a,b]=[1,2],{x:c}={x:a+b})=>a+b+c;
難解.js
varf=function(_a,_b){var_c=_a===void0?[1,2]:_a,a=_c[0],b=_c[1];varc=(_b===void0?{x:a+b}:_b).x;returna+b+c;};

javascriptに変換したところで意味はわかりませんでしたが、頑張れば読めるような気がしていきました。

nowでアプリケーションエラーが出たとき対応したこと

$
0
0

nowでハマったメモ

状況

@n0bisukeこの記事を参考にやっていたつもりがこんなエラーと遭遇。

image.png

An error occurred with this application.
This is an error with the application itself, not the platform.

「nowは問題ないよ。お前のアプリケーション(ソースコード)が問題なんだよ。」とのこと。

対応

このnow.json内で指定しているjsファイルの一部を直したら直った。
(記事の例でいくと、server.jsってファイルの文末)

直した場所

自分にとってわかりやすいと思って書き換えてた最後のserver running のあたり。

エラー出てたとき

エラー出てたとき.js
if(process.env.NOW_REGION){module.export=app;}else{app.listen(PORT);console.log(`Server running at ${PORT}`);}

これ、記事の通り下記に直したら直った。
(初めから記事の通りやっておけばよかった話)

エラー出なくなった.js
(process.env.NOW_REGION)?module.exports=app:app.listen(PORT);console.log(`Server running at ${PORT}`);

image.png
成功!

結論

自分でアレンジしたところが悪かった。記事の通りにやっておけばよかった。
でもなんでこの書き方だとダメだったんだろう。。。
あと、nowを起動させるときにこのjsファイルも見ているんだなあ。

Viewing all 8837 articles
Browse latest View live