20200928のNode.jsに関する記事は12件です。

Qiitaでプログラミング言語トレンド

1. 概要

日本ではプログラミング言語のトレンドはどんな感じか知りたいので、Qiitaの過去データを取得して分析してみましょう。以下の写真は世界でのトレンドです(参考:PYPLサイト)。
世界のプログラミング言語トレンド.png

2. データ取得

2.1. APIの利用

APIの制限のため、問題がいくつか発生しました:

  1. 全部投稿を取得するAPIがありません(全部は580000投稿ぐらいがある)。
  2. APIによる一回の上限は1万投稿です(100ページ * 100投稿/1ページ)。
  3. 実際は5900投稿を取得すると、403エラーが発生してから、APIをアクセスできない(スパムから保護するため、1時間60リクエストだけ) → あるタグの投稿数は5900投稿以下のためフィルターを適用します(Stocksの数は30以上)。AWSのEC2インスタンスを適用して取得した時、403エラーになると、インスタンスを再起動しました(新しいIPになる)。
  4. あるタグの投稿数は5900以下なのに、全部投稿を取得できません。2017年09月の時点ぐらいには500エラーが発生しました。 → 最後取得できた投稿の作成日を保存して、新しいリクエストを作ります。

で、以上の問題を解決すると、以下のコードです。

let axios = require("axios")
let fs = require("fs")
let csvStr = require("csv-stringify/lib/sync")

let PAGE_MAX = 55

function parse_response(response, record_num, outcsv) {
    for (record_idx = 0; record_idx < record_num; record_idx++) {
        let record = []
        id = response["data"][record_idx]["id"]
        comments_count = response["data"][record_idx]["comments_count"]
        likes_count = response["data"][record_idx]["likes_count"]
        reactions_count = response["data"][record_idx]["reactions_count"]
        tags = response["data"][record_idx]["tags"]
        tags_str = ""
        for(tag_num = 0; tag_num < tags.length; tag_num++){
            tags_str = tags_str.concat(', ', tags[tag_num]["name"])
        }
        title = response["data"][record_idx]["title"]
        user_id = response["data"][record_idx]["user"]["id"]
        created_at = response["data"][record_idx]["created_at"]
        record.push(id, comments_count, likes_count, reactions_count, 
                    tags_str, title, user_id, created_at)
        outcsv.push(record)
    }
    return outcsv
}

async function getArticle(tag) {
    let outcsv = []
    let columns = ["id", "comments_count", "likes_count", "reactions_count",
                   "tags", "title", "user_id", "created_at"]
    outcsv.push(columns) 
    let last_date = "2020-09-28"
    let record_sum = 0
    for (page = 1; page <= PAGE_MAX; page++){
        let response =  await axios.get("https://qiita.com/api/v2/items?page=" + 
                        page + "&per_page=100&query=" + 
                        encodeURIComponent("stocks:>30") + "+" + 
                        encodeURIComponent("tag:" + tag))
        record_num = response["data"].length
        if(record_num != 0){
            outcsv = parse_response(response, record_num, outcsv)
            record_sum = record_sum + record_num
            last_date = response["data"][record_num-1]["created_at"].split('T')[0]
            console.log(record_sum + ":" + last_date)
        }
        if(record_num < 100){
            break
        }
    }

    for (page = 1; page <= PAGE_MAX; page++) {
        let response =  await axios.get("https://qiita.com/api/v2/items?page=" +
                        page + "&per_page=100&query=" +
                        encodeURIComponent("stocks:>30") + "+" + 
                        encodeURIComponent("tag:" + tag) + "+" + 
                        encodeURIComponent("created:<" + last_date))
        record_num = response["data"].length
        if(record_num != 0){
            outcsv = parse_response(response, record_num, outcsv)
            record_sum = record_sum + record_num
            console.log(record_sum)
        }
        if(record_num < 100){
            break
        }
    }
    fs.writeFileSync(tag + ".csv", csvStr(outcsv))
}

// それぞれのタグを変更する:C, C++, C#, Go, Java, Javascript, Kotlin, Objective-C, 
// PHP, R, Ruby, Swift, Typescript
getArticle("python") 

2.2. アウトプットデータ

結果のデータは各投稿のID,コメント数、LGTM数、絵文字リアクション数、タグ、タイトル、筆者のID、投稿の日時です。

言語 データ投稿数 全部投稿数
Python 5337 46000
Javascript 5305 37257
Ruby 2927 29307
PHP 2010 21088
Java 1568 15905
Swift 2577 15317
C# 815 10319
Go 1021 9084
C++ 630 8006
Typescript 701 5891
Kotlin 287 4100
Objective-C 935 4010
R 204 3526
C 261 3305

3. トレンド

以上の結果のデータをTableauに入れてグラフを作成しましょう!

3.1. 全体グラフ

トレンド.png

横軸は投稿の作成日です(単位:年間)。
縦軸は各言語のタグの投稿数の割合です(単位:パーセント)。

3.2. 結果を分析しよう

3.2.1. Python

python.png
投稿数に基づと、2012年のPython言語ユーザーの割合は4%でした。その後、急速な成長により、この率は2014年にPHP、2015年にRubyとSwift、2016年にJavascriptを上回りました。現在、Pythonは一番人気がある言語(35%)になりました。

3.2.2. Javascript

js.png
2012年、JSの投稿数の割合は22%でしたが、2014年までに徐々に18%減少しました。現在、その割合は26%になり、人気がある言語トップで2位にランクされています。

3.2.3. Ruby

ruby.png
2012年、Rubyの投稿数の割合は35%に達し1位になりました。その後、何年にも渡って継続的に減少しています。現在、4%になりました。

3.2.4. Typescript

typescript.png
2013年から始まり、近頃の3年間の高い成長により、現在、3位(10%)になりました。

3.2.5. Swift/Objective-C

swift_objective-c.png
Swiftの発達とともに、Objective-Cは衰退していました(0.24%)。全体として、iOSの言語は2015年,2016年に人気があり、最近、少し減少しました。

3.2.6. 他の言語

PHP
php.png

Java
java.png

C/C++
c_c++.png

C#
c#.png

Go
go.png

R
r.png

Kotlin
kotlin.png

4. よく投稿した時間、曜日

作成日.png
よく投稿した曜日は月曜日です。

作成時間.png
よく投稿した時間は0時です。特には22時から24時までですよね。

ご参考までに!

 参考

結局Qiita記事ってどれぐらい書けばいいのさ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

最短工程でubuntuにnodenvをインストールする

$ uname -a
Linux geo-functions 5.4.0-1021-gcp #21-Ubuntu SMP Fri Jul 10 06:53:47 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
git clone https://github.com/nodenv/nodenv.git ~/.nodenv
git clone git://github.com/nodenv/node-build.git ~/.nodenv/plugins/node-build
echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(nodenv init -)"' >> ~/.bash_profile
source ~/.bash_profile
nodenv install 12.18.2

以上!!!!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Qiita初心者がまずタグ付けしたらよい言葉とは?

目的

 どのタグ(ほぼ言語)が最初にフォローされやすいのかを確認する。
 先に登録するものが、Qiitaを使いたいというきっかけになるものなので、
 本当に興味があることではないかという推測に基づく。

方法

 あるグループのユーザリストを取得(プログラム外)し、
 APIを使って新しい順にユーザ別にタグを取得。
 認証情報がないとAPIをたたける数が少ない。
 新しいものの番号を古い順に変換し、登録5つ目までのタグをランキング化(プログラム外)

コード

get_qiita_data_tags.js
//package require
const axios = require("axios");
const fs = require("fs");
const csvStr = require("csv-stringify/lib/sync");
const csvParse = require('csv-parse/lib/sync');

//認証情報の取得 外から渡してる
const token = process.argv[2];

//QiitaAPIでデータ取得・csvに出力
async function getArticle(query) {
  //ユーザリストを取得
  const file = './data/input_user_list.csv';
  let inputData = fs.readFileSync(file);
  let user_list = csvParse(inputData);
  console.log(user_list);

  //csvに変換する用list
  let outcsv = [];

  //csvのヘッダー設定
  let columns = ["ユーザ番号","ユーザID","タグ登録順","タグ"];
  outcsv.push(columns);

  //あるリストに存在するユーザ数
  var user_num = user_list.length;

  //検索用パラメータ
  var PAGE_MAX = 4;
  var PER_PAGE = 100;   

  for (i = 0;i<user_num;i++){
    //あるユーザがフォローをしているタグを取得
    var user_id = user_list[i][0];
    console.log(user_id);

    for (page = 1;page<=PAGE_MAX;page++){
        //URLを作っておく
        var url = "https://qiita.com/api/v2/users/"+user_id+"/following_tags?page=" + page + "&per_page=" + PER_PAGE;
        let response
        //リクエストが失敗した時の処理
        response = await axios.get(url
          , {
            headers: {
              Authorization: `Bearer ${token}`,
            }
          }

          ).catch(err => {
            return err.response
        });
        if (response.status != 200) {
          console.log("たぶんAPIエラー")
        }

        var tags_num = response.data.length;
        var sort_num_desc;
        console.log(tags_num);
        for (j =0 ; j<tags_num ; j++) {
          //一レコードの情報格納list
          let record = [];
          //欲しい要素
          var tag_id = response.data[j].id;
          sort_num_desc = (page - 1) * PER_PAGE + (j+1);
          console.log("row: " + sort_num_desc +",user_n: " + i + ",user_id: " + user_id + ",tag_id: " + tag_id);

          //listに格納
          record.push(i,user_id,sort_num_desc,tag_id); 
          outcsv.push(record);
        }
    }
  }
//   csvとして出力
  fs.writeFileSync("./data/following_tag_list.csv", csvStr(outcsv));
}

//情報の取得
var query = "node.js";
getArticle(query);

結果

 そもそもあるグループの前提になっている授業に含まれている言語が多い。
 フロント側の技術や、必須になるソースの管理に関しても見る人が多い。
 あんまりユーザ数が約40人のグループにしては必須の技術のタグ付け数が少ない。
 タグ付けしないのだろうか。

順位 タグ ユーザ数
1 JavaScript 16
2 Node.js 12
3 Python 8
4 GitHub 6
5 Android 5
6 Chrome 4
7 CSS 4
8 HTML 4
9 HTML5 4
10 Git 3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

QiitaAPIで2020年投稿のストック数が多い記事のタグ情報を調べてみた

はじめに

ほとんどプログラミング経験のない初心者(VB.Netを少しかじった程度)ですが、最近「モノをつくる」学習の場を与えて貰えたので、自分なりに色々調べて、JavaScriptにチャレンジしてみました。記事の投稿も初で、初めて尽くしで戸惑ってます。。ソースコードがキレイじゃないのもご愛敬。

目的

Node.jsとQiitaAPI(+axios)を使って今の自分に何ができるか考えてみた。
で、今回は、「今年(2020年)に投稿された記事で、ストック数の多い記事のタグってどんなの?」を調べてみました。
(ストック数が多い記事として、今回はストック数が300以上の記事を対象としてみた。)

コード

QiitaAPI.js
// axiosモジュールの読み込み
const axios = require('axios');
axios.defaults.baseURL = 'https://qiita.com/api/v2';

let array1 = [];  // タグ名用
let array2 = [];  // カウント用

// main関数
async function main(page) {

  // QiitaAPIからレスポンス受け取り(ストック>=300 + 作成日>=2020-01 の記事一覧)
  let response = await axios.get(
      '/items?page=' + page + '&per_page=100&query=stocks:' +
      encodeURIComponent('>=300') + '+created:'+ encodeURIComponent('>=2020-01')
  );

  // 取得した記事から[tags]要素を取得
  for (let i=0; i<response.data.length; i++) {
    for (let j=0; j<response.data[i].tags.length; j++) {

        var tagname = response.data[i].tags[j].name;    // タグ名取得

        // 配列に登録があれば、カウントを+。登録が無いなら配列に追加。
        var result = array1.indexOf( tagname );         // 検索(既に配列にあるか)
        if (result < 0) {
            array1.push(tagname);
            array2.push(1);
        } else { array2[result] += 1; }
    }
  }

  // 結果出力
  if (page==5) {
      for (let i=0; i<array1.length; i++) {
        console.log(array1[i]+', '+array2[i]);
      }
  }
}

// main関数 実行
let promise = Promise.resolve();
for (let k=1; k<6; k++) {
    promise = promise.then(() => main(k));
}

色々ハマった点

1.QiitaAPIって何がとれるのさ?

QiitaAPIの仕様書を読み込んでみた。(色々取れそうだけど、技術力が追い付かないので断念。)
最初におや?と思ったのは、1回のレスポンスで最大100件、ページ指定も最大100ページ。つまり、1万件まで。
100件以上を取ろうと思うとページを何度か呼び出すしかないのかな。ってことで、main関数をループすることにした。

  let response = await axios.get(
      '/items?page=' + page + '&per_page=100&query=stocks:' +
      encodeURIComponent('>=300') + '+created:'+ encodeURIComponent('>=2020-01')
  );

2.非同期処理ってなんぞや?

ループしようと思ったはいいけど、上手くいかない。
どうも非同期処理で走るからって理由らしい。正直良く分かってないが、以下サイトを参考に改善してみた。
PromiseによるJavaScript非同期処理レシピ集

let promise = Promise.resolve();
for (let k=1; k<6; k++) {
    promise = promise.then(() => main(k));
}

結果

結果は、EXCELで集計。
(あまり細かくなってもアレなので、表は上位10件、グラフは記事数7件以上を表示。)
※2020年に投稿された記事で、ストック数が300以上の記事についてタグを集計

タグ 記事数
JavaScript 67
Python 58
初心者 49
機械学習 24
新人プログラマ応援 24
HTML 16
DeepLearning 16
GitHub 16
Vue.js 16
React 16

image.png

考察

Qiitaトップのタグ・ランキングとはまた違った結果になった。
上位の「JavaScript」「Python」は、今年も熱い。ストック数も多いということは、興味関心が高く、役立つ情報がまだまだあるってことかなぁ。

興味深いのは、タグ・ランキングでは上位の「Ruby」がランク外。
最近の投稿が少ないか、ストック数が伸びないのか、、、??

最後に

JavaScriptの学習も兼ねて今回はチャレンジしてみましたが、もっとより良いアプローチがある気がしました。
そもそも、私の技術力の無さを痛感。(書いてませんが色々断念してます。。。)
まずは書く!調べる!やってみる!
トライアンドエラーで今後も素人ながら挑戦していきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

QiitaAPIで20年投稿のストック数が多い記事のタグ情報を調べてみた

はじめに

ほとんどプログラミング経験のない初心者(VB.Netを少しかじった程度)ですが、最近「モノをつくる」学習の場を与えて貰えたので、自分なりに色々調べて、JavaScriptにチャレンジしてみました。記事の投稿も初で、初めて尽くしで戸惑ってます。。
ソースコードがキレイじゃないのもご愛敬。

目的

Node.jsとQiitaAPI(+axios)を使って今の自分に何ができるか考えてみた。
で、今回は、「今年(2020年)に投稿された記事で、ストック数の多い記事のタグってどんなの?」を調べてみました。
(ストック数が多い記事として、今回はストック数が300以上の記事を対象としてみた。)

コード

QiitaAPI.js
// axiosモジュールの読み込み
const axios = require('axios');
axios.defaults.baseURL = 'https://qiita.com/api/v2';

let array1 = [];  // タグ名用
let array2 = [];  // カウント用

// main関数
async function main(page) {

  // QiitaAPIからレスポンス受け取り(ストック>=300 + 作成日>=2020-01 の記事一覧)
  let response = await axios.get(
      '/items?page=' + page + '&per_page=100&query=stocks:' +
      encodeURIComponent('>=300') + '+created:'+ encodeURIComponent('>=2020-01')
  );

  // 取得した記事から[tags]要素を取得
  for (let i=0; i<response.data.length; i++) {
    for (let j=0; j<response.data[i].tags.length; j++) {

        var tagname = response.data[i].tags[j].name;    // タグ名取得

        // 配列に登録があれば、カウントを+。登録が無いなら配列に追加。
        var result = array1.indexOf( tagname );         // 検索(既に配列にあるか)
        if (result < 0) {
            array1.push(tagname);
            array2.push(1);
        } else { array2[result] += 1; }
    }
  }

  // 結果出力
  if (page==5) {
      for (let i=0; i<array1.length; i++) {
        console.log(array1[i]+', '+array2[i]);
      }
  }
}

// main関数 実行
let promise = Promise.resolve();
for (let k=1; k<6; k++) {
    promise = promise.then(() => main(k));
}

色々ハマった点

1.QiitaAPIって何がとれるのさ?

QiitaAPIの仕様書を読み込んでみた。(色々取れそうだけど、技術力が追い付かないので断念。)
最初におや?と思ったのは、1回のレスポンスで最大100件、ページ指定も最大100ページ。つまり、1万件まで。
100件以上を取ろうと思うとページを何度か呼び出すしかないのかな。ってことで、main関数をループすることにした。

  let response = await axios.get(
      '/items?page=' + page + '&per_page=100&query=stocks:' +
      encodeURIComponent('>=300') + '+created:'+ encodeURIComponent('>=2020-01')
  );

2.非同期処理ってなんぞや?

ループしようと思ったはいいけど、上手くいかない。
どうも非同期処理で走るからって理由らしい。正直良く分かってないが、以下サイトを参考に改善してみた。
PromiseによるJavaScript非同期処理レシピ集

let promise = Promise.resolve();
for (let k=1; k<6; k++) {
    promise = promise.then(() => main(k));
}

結果

結果は、EXCELで集計。
(あまり細かくなってもアレなので、表は上位10件、グラフは記事数7件以上を表示。)
※2020年に投稿された記事で、ストック数が300以上の記事についてタグを集計

タグ 記事数
JavaScript 67
Python 58
初心者 49
機械学習 24
新人プログラマ応援 24
HTML 16
DeepLearning 16
GitHub 16
Vue.js 16
React 16

image.png

考察

Qiitaトップのタグ・ランキングとはまた違った結果になった。
上位の「JavaScript」「Python」は、今年も熱い。ストック数も多いということは、興味関心が高く、役立つ情報がまだまだあるってことかなぁ。

興味深いのは、タグ・ランキングでは上位の「Ruby」がランク外。
最近の投稿が少ないか、ストック数が伸びないのか、、、??

最後に

JavaScriptの学習も兼ねて今回はチャレンジしてみましたが、もっとより良いアプローチがある気がしました。
そもそも、私の技術力の無さを痛感。(書いてませんが色々断念してます。。。)
まずは書く!調べる!やってみる!
トライアンドエラーで今後も素人ながら挑戦していきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Qiitaって誰向けなの?レベル別投稿を可視化してみた

はじめに

 とある理由からQiitaを利用することとなったが、正直Qiitaの読み方も知らない(たぶんキータ)、どんな人がどういった目的で利用するツールなのか正直何だかわかってない、アカウント作りたてほやほやの初心者が投稿する。

目的

Qiitaのレベル別利用者の分布を可視化し、どのような人にとって便利なツールであるかを考察する。

コード

初級者、中級者、上級者タグをフォローしている人数を取得する

const axios = require("axios");

async function main() {
  let response = await axios.get(
      "https://qiita.com/api/v2/tags/%E5%88%9D%E5%BF%83%E8%80%85"
      );
  console.log(response.data.followers_count);
}

main();

実行結果

初級者:4476件
中級者:0件
上級者:0件

考察

 中級者、上級者タグが存在していなかったため、タグに紐づくフォロー人数では利用者属性を推察することができなかった。
 一方で、「初級」と検索した際の記事投稿数は1,836件、「中級」と検索した際の記事投稿数は1,572件、「上級」と検索した際の記事投稿数は1,404件だったたため、検索結果からすると幅広いレベルの利用者向けの技術サイトであると推測できる。

最後に

 ・本来は「投稿記事のタイトルや本文から、記事をレベル分けした分布」や「投稿者のプロフィールをもとにした利用者レベル分析」などといったことも考察してみたかったが、抽出方法がわからず、諦めました..
 ・Qiitaは、初級者である私にも便利なツールであることがわかった。※実際この記事を投稿するためにいくつかの記事を参考にした。
今後も記事投稿を続ける予定のため、継続して利用しようと思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsでDurable Functionsを使うなら、入出力はObjectにした方が安心かもという話

注: 本記事は2020/09/28時点のものであり
https://www.npmjs.com/package/durable-functions の v1.4.3
https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.DurableTask/ のv2.2.2
https://github.com/Azure/azure-functions-nodejs-worker のv1.2.2
で検証及び再現した事象です。

今後の変更及び修正により挙動が変わる場合があるため、バージョンが変わっていた場合各プロダクトのIssueなどを参照してください。

結論だけ知りたい人向け

  • アクティビティ関数の入出力バインディングに文字列やbooleanなどの値を渡すと意図しない変換が行われる
  • オブジェクトでラップして渡せば基本的には問題ない

とりあえずこれを守っておけばハマることは少ないと思います。

起こる原因

Durable Functionsでは入出力バインディングのデータをJSON文字列としてやり取りしています(ByteArrayを除く)。
ref: Durable Functions のバインド - Azure | Microsoft Docs

関数でバインディングが行われた入力を参照する時は呼び出し元の入力が JSON.stringify されて、更に JSON.parse された値が渡ってくる認識して良いでしょう(厳密には異なります)。

Durable Functionsなどの実行環境とも言えるAzure Functions NodeJS Worker では渡ってきた値に全てに対して JSON.parse を適用します(ByteArrayを除く)。
ref: https://github.com/Azure/azure-functions-nodejs-worker/blob/11303c0dcf2ddcbf876e0cc453dbe3a731b769d9/src/Context.ts#L22

………とここまで内部でだいたいどんな事が行われているか記載しましたが、正直自分も処理を追いきれておらず、各文章の末尾に「っぽいことが行われています」が付きそうな具合です。
どういうこととお思いかと思うので、実際の例を挙げてみます。

不思議な挙動のバインディング

ここでは公式サンプルのSayHelloを例にとってみます。
https://github.com/Azure/azure-functions-durable-js/blob/32f442cd5fc3fe5d79451bc3daf91d5540f21d1d/samples/E1_HelloSequence/index.js
https://github.com/Azure/azure-functions-durable-js/blob/32f442cd5fc3fe5d79451bc3daf91d5540f21d1d/samples/E1_SayHello/index.js

SayHelloを一部改変して

module.exports = function (context) {
  context.log(typeof context.bindings.name);
  context.log(context.bindings.name);
  return context.bindings.name;
};

みたいにして、HelloSequenceからSayHelloに渡す値を変えてみましょう。

まず手始めに正常系で

yield context.df.callActivity("E1_SayHello", "Tokyo")

を実行してみます。
ログには

string
Tokyo

と表示されたと思います。
問題ないですね。

では次に数字を渡してみましょう。

yield context.df.callActivity("E1_SayHello", 123)

を実行してみます。
ログには

number
123

これも想定通りですね。

では数字を文字列型として渡してみます。

yield context.df.callActivity("E1_SayHello", '123')

を実行してみるとログには

number
123

:thinking:
私は文字列を渡したはずだが…?となりますが、これが上記で述べた

渡ってきた値に全てに対して JSON.parse を適用します(ByteArrayを除く)。

の弊害となります。
これが大きな問題となるのが、Number型で表現できる最大最小の値を超えた時などです。
TwitterなどのAPIではidの値が非常に大きな値となっていて、JavaScriptのNumber型では表現の出来ない値になっています。
そのため id_str というidをstr型で表現した値も一緒に送信しているのですが、それをDurable Functionsで扱おうとすると上記の挙動でnumber型にparseされ、表現できない範囲の値になり正常な値が扱えなくなるというものです。

他にも不思議な挙動はいくつかあり

yield context.df.callActivity("E1_SayHello", 'null')

を実行してみるログには

object
null

文字列を渡しているのにnullが渡ってきたり(同様に'{}'を渡すと {}が返ってきたり)

yield context.df.callActivity("E1_SayHello", true)

を実行してみるログには

string
True

と、 JSON.stringify(true) したら true が返ってきているはずでは?と言った挙動が見られます。

このことから JSON.stringify っぽいことをしているけれども、そうじゃない別の何かが行われているということがわかります。

このあたりで一旦grpcでゴニョゴニョする時に何かが怒ってるのかなぁとあたりは付けているのですが、調査しきれていません)

対処法

扱う対象の値によっていくつか選択肢はありますが、入力値に文字列や数値、bool値、null値をそのまま使うのではなく、

オブジェクトの値として渡す

これで解決します。

例えば

yield context.df.callActivity("E1_SayHello", { idStr: '123' })

としてSayHello関数に値を渡して、SayHello関数では

module.exports = function (context) {
  ...
  context.log(context.bindings.name.idStr);
  ...
};

として受け取ると言った具合です。
オブジェクトは JSON.stringifyっぽいことをされるのではなく、JSON.stringifyが行われるため、数値は数値のまま、文字列は文字列のまま渡されるようになります。

かなりエッジケースな話ではありますが、ユーザからの入力値で文字列が来ると思っていたのに何か違うものが渡ってきているぞ、というバグにお悩みの方は上記の方法で対処してみることも考えてみてください。

なお上記現象については
https://github.com/Azure/azure-functions-durable-js/issues/215
にてFB済みです。
今後の対応を期待しましょう。

ByteArrayでやり取りするのも一つの手かもしれませんね

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

6. Koa2でWebページを作成しよう ~validatorで入力値を検証~

記事一覧

概要

今回は、入力値のバリデーションの実装を行います。
また、前回の続きとして進めて行くので、上手くいかない場合は以前の記事をご覧ください。

バリデーション

バリデーションは値のフォーマットが正しい形であるかの確認をする処理で、今回は入力必須や文字数制限、メールアドレス形式であるかなどの観点で値の検証を行っていきます。
一例として、ユーザーから値を受け取って処理を行うプログラムでは、ユーザーが必ずしも正しいフォーマットでデータを入力するとは限らないため、フォーマットの確認が必要になります。

*バリデーションにはこちらのパッケージを利用します。
https://github.com/skaterdav85/validatorjs

以下のコマンドをコンソールで実行してください。
npm install validatorjs

DBの作成

phpMyAdminで以下のSQLを実行してください。
別のツールでDBを操作できる場合は、そちらでも問題ありません。

create table koa2_test.validate_user
(
   user_id   int auto_increment
       primary key,
   user_name varchar(20)  not null,
   password  varchar(60) not null,
   mail      varchar(255) not null
);

DBの各列で指定されている条件をまとめると、以下のようになります。

  • user_id: 主キー制約。自動採番。
  • user_name: 最大20文字。NOTNULL。
  • password: 最大60文字。NOTNULL。
  • mail: 最大255文字。NOTNULL。

また、以下の条件も考慮します。

  • user_idは自動採番で、バリデーションの対象とする必要はない
  • mailはメールアドレスの形式である事も検証する
  • パスワードはハッシュ化前文字列の最大を128文字とする。

よって、必要となる検証項目は以下のようになります。

  • user_name: 20文字以下であること。値が空でない事。
  • password: 128文字以下であること。値が空でない事。
  • mail: 255文字以下であること。値が空でない事。メールアドレスの形式である事。

validatorで入力値の検証

/router/validator-signup.jsを作成し、以下の記述を追加してください。

validator-signup.js
const Router = require('koa-router');
const router = new Router();
const connection = require('../app/db');
const bcrypt = require('bcrypt');
const validator = require('validatorjs');

router.get('/validator-signup', async (ctx) => {
    await ctx.render('validator-signup');
});

router.post('/validator-signup', async (ctx) => {
    // POSTパラメータを取得
    let userName = ctx.request.body['name'];
    let password = ctx.request.body['password'];
    let mail = ctx.request.body['mail'];

    // 入力値の検証
    let validationResult = await validation([userName, password, mail]);

    // 入力値拒否時、登録処理を行わずにエラーメッセージを表示する
    if (validationResult.result === false) {
        await ctx.render('validator-signup', validationResult);
    } else {
        // パスワードをハッシュ化
        const salt = bcrypt.genSaltSync();
        const hashPassword = bcrypt.hashSync(password, salt);

        // DBにアカウント情報を登録
        let signupSQL = 'INSERT INTO validate_user(user_name, password, mail) VALUES (?, ?, ?)';
        await connection.query(signupSQL, [userName, hashPassword, mail]);

        await ctx.render('validator-signup');
    }
});

module.exports = router;

大部分は前回の記事で作成したアカウント登録処理と同じものですが、入力値の検証を行う処理を追加しています。
次に、入力値の検証を行うためのvalidation関数を作成します。

validation関数の作成

/router/validator-signup.jsの最後に以下の記述を追加してください。

validator-signup.js
async function validation(data) {
    // ユーザーの入力値をvalidatorjsへの入力形式である連想配列に変換。
    // ここで指定しているkey値がrulesやerrorMessageの条件部分でも利用される。
    let requests = {
        userName: data[0],
        password: data[1],
        mail: data[2],
    }

    // それぞれの値に対して掛ける制約を指定している。
    // required: 文字列が0文字でないことを確認
    // max: 最大文字数以下であることを確認
    // email: メールの形式であることを確認
    let rules = {
        userName: 'required|max:20',
        password: 'required|max:128',
        mail: 'required|max:255|email',
    };

    // '条件.検証項目'の形で、検証項目が条件を満たしていない場合の
    // エラーメッセージを指定している。
    // ここで指定していないエラーについては、
    // デフォルトで設定されている英語のメッセージが返される。
    let errorMessage = {
        'required.userName': 'USER NAMEは必須項目です',
        'required.password': 'PASSWORDは必須項目です',
        'required.mail': 'MAILは必須項目です',
        'max.userName': 'USER NAMEは20文字以下で入力して下さい',
        'max.password': 'PASSWORDは128文字以下で入力して下さい',
        'max.mail': 'MAILは255文字以下で入力して下さい',
        'email.mail': 'MAILはメールアドレスの形式で入力して下さい'
    }

    // validator(検証する値, 検証する条件, 拒否時のメッセージ) の形で、
    // validatorのインスタンスを生成。
    let signupValidator = new validator(requests, rules, errorMessage);
    let result = {
        error: {},
        result: false
    };

    // 値の検証を実行して、成功時とエラー時で処理を振り分ける。
    // checkAsync() の第一引数は成功時の関数を、第二引数は失敗時の関数を指定する。
    await signupValidator.checkAsync(() => {
        // Success時の処理
        result.result = true;
    }, () => {
        // Error時の処理
        result.result = false;

        // errors.first(‘userName’)では、userNameにエラーが発生している場合、
        // 発生したエラーに応じて、validatorインスタンス生成時に
        // 第三引数で指定したメッセージを取得している。
        // 取得した値はresult変数に格納して、関数の最後でreturnしている。
        if (signupValidator.errors.first('userName')) {
            result.error.medicine_name = signupValidator.errors.first('userName');
        }
        if (signupValidator.errors.first('password')) {
            result.error.hospital_name = signupValidator.errors.first('password');
        }
        if (signupValidator.errors.first('mail')) {
            result.error.number = signupValidator.errors.first('mail');
        }
    })

    return result;
}

*上記以外の制約についてはこちらのページを参照。
https://github.com/skaterdav85/validatorjs

入力フォームを作成

/view/validator-signup.ejsを作成し、以下の記述を追加してください。

validator-signup.ejs
<h1>アカウント登録</h1>
<% if(typeof error !== 'undefined'){ %>
    <% for(let key in error) { %>
    <p style="color: red"><%= error[key] %></p>
    <% } %>
<% } %>
<form action="validator-signup" method="post">
    USER NAME <input type="text" name="name"><br>
    PASSWORD <input type="password" name="password"><br>
    MAIL <input type="email" name="mail"><br>
    <input type="submit" value="登録"><br>
</form>

ルーティングを設定

/index.jsに以下の記述を追加してください。

index.js
const validatorSignupRouter = require('./router/validator-signup');
app.use(validatorSignupRouter.routes());
app.use(validatorSignupRouter.allowedMethods());

validationの表示結果

validation-signup.png

最後に

以上でvalidatorjsを利用した入力値の検証は完成です。
次回はセッションを用いたログイン状態の管理を実装してきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

teratermで2048を遊ぶ

teratermなどのターミナルソフトを使うときは限られています。ネットワークなりサーバーなりいじる人ぐらいしか使いません。
暇なときはあります。ビルド、デプロイ、コンフィグリストア、例を挙げればでるわでるわ。

そういう時はターミナルソフトで遊びたくなります。そんなわけで2048をsshで飛ばせるようにしました。

play.gif

thanks!

使い方

ローカルで動かす場合

git clone https://github.com/misogihagi/node-ssh-2048.git
npm i
node 2048

そして
localhostにでてきたポートにsshしてください

デモ

de.gif

おまけ

実は類似のサービスがあります。
play@ascii.town

他にもターミナルで遊べそうなものがあれば教えてください

おまけのおまけ

2048RTA
https://github.com/misogihagi/node-ssh-2048/blob/master/rta.gif?raw=true

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npm-scriptsをnpm-run-allで用途や環境毎に書く際の小技

  • Web開発の際のタスク処理として、package.json内に記述して利用できるnpm-scriptsを利用しています。
  • またその際に、複数処理の直列化や並列化に便利なnpm-run-allを利用しています。
  • 今回はそれを用いた用途や環境毎への記述を、明確かつ柔軟にする方法を記録します。

結果

  • 先に結果の記述を示します。以下の通りです。
{
  "scripts": {
    "build"      : "run-s build:{sass,ts}",
    "build:sass" : "sass input.scss output.css",
    "build:ts"   : "tsc main.ts",
    "watch"      : "run-s watch:{sass,ts}",
    "watch:sass" : "sass --watch input.scss output.css",
    "watch:ts"   : "tsc -w main.ts"
  }
}

内容

  • 上記の結果例は、主にbuildwatchを行うタスクです。
  • こちらで利用している方法は、処理内容を波括弧で指定するということです。

波括弧での指定

  • コロン(:)で用途毎に分けたタスクを一回で実行する際には、以下のようなやり方があります。
{
  "scripts": {
    "build-basic" : "run-s build:sass build:ts",
    "build-glob"  : "run-s build:*",
    "build-brace" : "run-s build:{sass,ts}",
    "build:sass"  : "sass input.scss output.css",
    "build:ts"    : "tsc main.ts",
  }
}
  • これらから私はbuild-braceのような波括弧で指定した方法を利用しています。
  • そのメリットは以下の通りです。
    • 非重複
      • run-s build:sass build:tsのように言葉の重複が無く、今後同一のタスクが増えても、用途の記述だけで済む。
    • 明示化
      • run-s build:*のようなアスタリスク指定と比べて、明示的に記述されているため、処理の内容が理解しやすい。
    • 順序化
      • 波括弧内の順に処理を行ってくれるため、仮にpackage.json内でbuild:sassbuild:tsを逆に記述しても問題無い。
        • run-s build:*での指定では、処理順番がpackage.json内の並び通りに行われるため、逆に記載してしまうとその順になってしまう。

まとめ

  • 上記のことから、タスクやscript記述においても可読性・明示化・非重複性を再認識しました。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

5. Koa2でWebページを作成しよう 〜アカウント登録〜

記事一覧

概要

今回は、アカウント登録、ログイン検証の実装を行います。
当記事ではログインは検証に留め、ログイン状態の保持は後の記事で進めていく予定です。
また、前回の続きとして進めて行くので、上手くいかない場合は以前の記事をご覧ください。

アカウントの登録やログインに必要なデータベースはMariaDB(MySQL)を使用します。

インストール

こちらのページからXAMPPをダウンロードし、インストールしてください。
https://www.apachefriends.org/jp/download.html

インストールが完了したら、MySQL DatabaseとApache Web Serverを起動してください。
ブラウザから以下のURLにアクセスすることで、MariaDBに接続・操作できます。
http://localhost/phpmyadmin/index.php

エラーが発生した場合

preg_match_all(): Allocation of JIT memory failed, PCRE JIT will be disabled. This is likely caused by security restrictions. Either grant PHP permission to allocate executable memory, or set pcre.jit=0

というエラーが表示された場合は、以下の手順を試してください。

XAMPPをインストールしたディレクトリから、/xamppfiles/etc/php.iniを開く。
[Pcre]の文字列で検索して、その次の行に以下の記述を追加。
pcre.jit=0

データベースの作成

今回使用するデータベースを作成します
以下の記述をphpMyAdminのSQLタブのテキストボックスに追加して実行してください。

CREATE DATABASE koa2_test;
USE koa2_test;
CREATE TABLE koa2_test.user
(
    user_id   INT AUTO_INCREMENT PRIMARY KEY,
    user_name VARCHAR(20)  NOT NULL,
    password  VARCHAR(255) NOT NULL
);

画面左のリストからkoa2_testデータベースをクリックすると、作成したテーブルが確認できます。

Koaでデータベースに接続する処理を記載します。
接続するにあたって、こちらのパッケージを利用します。
https://github.com/sidorares/node-mysql2

以下のコマンドをコンソールで実行してください。
npm install mysql2

DB接続にはコネクションプールを作成し、これをすべてのデータベースへのクエリで利用します。
/app/db.js を作成し、以下の記述を追加してください。

db.js
const mysql = require('mysql2/promise');
const config = require('../config.json');

const connection = mysql.createPool(config.db);

module.exports = connection;

DB接続を行う外部ファイルでは、当ファイルをrequireで読み込むことでコネクションプールを利用します。

また、設定情報を保存するためのファイルを作成します。
/config.jsonを作成し、以下の記述を追加してください。

config.json
{
  "db": {
    "host": "localhost",
    "port": 3306,
    "user": "root",
    "password": "",
    "database": "koa2_test"
  }
}

設定ファイルを別に用意することで、設定情報を一元管理できたり、Gitにパスワード等の情報が保存されてしまう事を防ぐことができるので、このようにすると良いようです。

アカウント登録

/view/signup.ejsを作成し、以下の記述を追加してください。

signup.ejs
<h1>アカウント登録</h1>
<form action="signup" method="post">
   USER NAME <input type="text" name="name"><br>
   PASSWORD <input type="password" name="password"><br>
   <input type="submit" value="登録"><br>
</form>

/router/signup.jsを作成し、以下の記述を追加してください。

signup.js
const Router = require('koa-router');
const router = new Router();
const connection = require('../app/db');

router.get('/signup', async (ctx) => {
    await ctx.render('signup');
});

router.post('/signup', async (ctx) => {
    // POSTパラメータを取得
    let userName = ctx.request.body['name'];
    let password = ctx.request.body['password'];

    // DBにアカウント情報を登録
    // SQL文中の ? はプレースホルダーというもので、ここに後から値を割り当て(バインド)します。
    // ここでは、insert文のuser_nameとpasswordに当たる箇所を ? としており、
    // query()関数の第二引数として配列の形で、バインド値を渡しています。
    let signupSQL = 'INSERT INTO user (user_name, password) VALUES (?, ?)';
    await connection.query(signupSQL, [userName, password]);

    await ctx.render('signup');
});

module.exports = router;

/index.jsに以下の記述を追加してください。

index.js
const signupRouter = require('./router/signup');
app.use(signupRouter.routes());
app.use(signupRouter.allowedMethods());

動作を確認

ブラウザで http://localhost:5000/signup にアクセスすると、アカウント登録ページが表示されます。
http://localhost/phpmyadmin/ で情報がDBに登録されていることが確認できます。

パスワードのハッシュ化

現在の状態ではパスワードが平文で登録されてしまっているので、ハッシュ化して登録するように変更します。
パスワードのハッシュ化にはこちらのパッケージを利用します。
https://github.com/kelektiv/node.bcrypt.js

以下のコマンドをコンソールで実行してください。
npm install bcrypt

/router/signup.jsを以下のように修正してください。

signup.js
const Router = require('koa-router');
const router = new Router();
const connection = require('../app/db');
const bcrypt = require('bcrypt');

router.get('/signup', async (ctx) => {
    await ctx.render('signup');
});

router.post('/signup', async (ctx) => {
    // POSTパラメータを取得
    let userName = ctx.request.body['name'];
    let password = ctx.request.body['password'];

    //パスワードをハッシュ化
    const salt = bcrypt.genSaltSync();
    const hashPassword = bcrypt.hashSync(password, salt);

    // DBにアカウント情報を登録
    let signupSQL = 'INSERT INTO user VALUES (0, ?, ?)';
    await connection.query(signupSQL, [userName, hashPassword]);

    await ctx.render('signup');
});

module.exports = router;

この状態で再びアカウント登録を実行すると、以下のような文字列がパスワードとして保存されます。
「$2b$10$pO2kkkS0FWpKMgIoCg5QbuQilL7E3nbWF6im47pvSx8…」

以上で新規登録は完了です。

ログイン

/view/login.ejsを作成し、以下の記述を追加してください。

login.ejs
<h1>ログイン</h1>
<form action="login" method="post">
    USER NAME <input type="text" name="name" placeholder="userと入力"><br>
    PASSWORD <input type="password" name="password" placeholder="userと入力"><br>
    <input type="submit" value="ログイン"><br>
</form>

<a href="signup">アカウント登録</a>

<!-- この部分でログイン結果を表示するようにjs側で処理を書いていきます。 -->
<% if(typeof loginResult !== "undefined"){ %>
    <br>
    <%= loginResult %>
<% } %>

/router/login.jsを作成し、以下の記述を追加してください。

login.js
const Router = require('koa-router');
const router = new Router();
const connection = require('../app/db');
const bcrypt = require('bcrypt')

router.get('/login', async (ctx) => {
    await ctx.render('login');
});

router.post('/login', async (ctx) => {
    // POSTパラメータを取得
    let userName = ctx.request.body['name'];
    let password = ctx.request.body['password'];

    // DBからuserNameが一致するデータを検索
    let loginSQL = 'SELECT user_id, password FROM user WHERE user_name = ?';
    let [queryResult] = await connection.query(loginSQL, [userName]);

    // 入力されたパスワードとDBに保存されているハッシュ化されたパスワードを比較
    let loginResult = 'ログイン失敗';
    for (let row of queryResult) {
        if (bcrypt.compareSync(password, row['password'])) {
            loginResult = 'ログイン成功';
        }
    }

    await ctx.render('login', {loginResult: loginResult});
});

module.exports = router;

作成したrouterを読み込む処理をindex.jsに追加します。
/index.jsに以下のコードを追加してください。

index.js
const loginRouter = require('./router/login');
app.use(loginRouter.routes());
app.use(loginRouter.allowedMethods());

動作を確認

ブラウザでhttp://localhost:5000/login にアクセスすると、ログイン画面が表示されます。
!# 画像を挿入

まとめ

以上で新規登録とログインの検証が完了です。
次回は入力値のバリデーションを書いていく予定です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Symbol testnet bootstrap v0.10.0 自動構築 shellscript

いつもながら "Tera Term" を使用してます

借りたサーバにrootでログイン

vi b

"i" を入力 編集モードにする
下記の scriptをコピー ペースト

下記をコピペ

#!/bin/bash

#new username
echo "please input new username"
read username

#new sshd port
echo "please input new sshd port"
read sshd

#ip
echo "please input ip"
read ip

#friendly_name
echo "please input friendly_name"
read friendlyname
#----------

#usernameset
adduser $username
gpasswd -a $username sudo

#sshdset
sed -i -e s/"#Port 22"/"Port 22"/ /etc/ssh/sshd_config
sed -i -e s/"Port 22"/"Port $sshd"/ /etc/ssh/sshd_config
sed -i -e s/"PermitRootLogin yes"/"PermitRootLogin no"/ /etc/ssh/sshd_config
systemctl restart sshd

#ufw set
apt-get install ufw
ufw allow $sshd/tcp

#ufw start
sudo ufw enable
sudo ufw status

#docker
curl https://get.docker.com | sh
usermod -aG docker $username
systemctl start docker
systemctl enable docker
docker --version

#docker-compose
curl -L https://github.com/docker/compose/releases/download/1.27.3/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose --version

systemctl restart docker

#node js
curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -

#git
apt-get install -y nodejs
npm install -g npm

node -v
npm -v

 #change userdir
cd /home/$username

#make workdir
mkdir -p symbol-bootstrap
cd symbol-bootstrap

#install Symbol Bootstrap
npm install -g symbol-bootstrap@alpha
symbol-bootstrap -v

#make configfile
symbol-bootstrap config -p testnet -a dual

#-------------config ip-------------
sed -i -e s/"host ="/"host = $ip"/ target/config/api-node/resources/config-node.properties

#--------config friendlyname--------
sed -i -e s/"friendlyName ="/"friendlyName = $friendlyname"/ target/config/api-node/resources/config-node.properties

#---config friendlyname at line 53---
vi target/config/api-node/resources/config-node.properties

#make docker-compose.yml
symbol-bootstrap compose

#start
symbol-bootstrap run -d
symbol-bootstrap run -d
symbol-bootstrap run -d
symbol-bootstrap run -d
symbol-bootstrap run -d

"ESC" を押して 編集モード終了
":wq" を入力 "ENTER" で保存終了

sh b

script がスタート

username(※①)
sshd port(※②)
IP(※③)
friendlyname(※④)
それぞれ入力

後は
user pw(※⑤) の設定(2回入力)
途中で "[Y/n]" は "y"

起動直前で vi 起動するので
"i" 押して 編集モードに
53行目の friendlyname(※④)を修正 (黄線の部分を削除 これ消せなかったw)
"ESC" 押して 編集モード終了
":wq" で保存終了
また自動で進みます
name1.png

最終行の "symbol-bootstrap run -d"は
一回では起動しない事が良くあるので

起動出来たかは
http://"自分のノードのIP":3000/node/info
http://"自分のノードのIP":3000/chain/info
で確認

起動に失敗していたら
一旦ログアウト
新しく作成した sshd ポート(※②) から user名(※①)とそのパスワード(※⑤)で
ログイン (この時点では port22での root ログインは出来なくなっています)

cd symbol-bootstrap
symbol-bootstrap run -d

これでも起動しなかったら
もう一回

symbol-bootstrap run -d

これを繰り返して下さい

起動出来たかは
http://"自分のノードのIP":3000/node/info
http://"自分のノードのIP":3000/chain/info
で確認

nodeを止めるには
"symbol-bootstrap" ディレクトリに入って

symbol-bootstrap stop

これで停止します

参考資料

Symbolテストネットノードを建ててみた (v0.10.x Hippo on Ubuntu Server 18.04)
https://nemlog.nem.social/blog/49345

nemtech/symbol-bootstrap
https://github.com/nemtech/symbol-bootstrap

次世代NEMブロックチェーン、Symbolに迫る(9) ~テストネット設定編~
https://note.com/daokasweep/n/n94c30a6f0d55

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む