2022/02/21

ES6 語法 note

JavaScript ES6 的一些新的語法的筆記

var, let, const

  • var 的作用域為 function。
  • let 與 const 作用域為 block,也就是 大括號 { } 裡面
  • const 宣告的變數無法被 re-assigned

Arrow Function

// 傳統 function 寫法
var call = function(someone) {
  return someone + ' is called';
}

var plus = function(A, B) {
  return A + B;
}

// arrow function
var call = someone => someone + ' is called';

var plus = (A, B) => A + B;

arrow function 的 this 是外面的 this,跟原本 function 的 this 不同

展開符號 ...

展開運算子 (Spread Operator) / 其餘運算子 (Rest Operator)

合併數個陣列

let groupA = ['小明', '杰倫', '阿姨'];
let groupB = ['老媽', '老爸'];
let groupAll = [...groupA, ...groupB];
console.log(groupAll);
// ['小明', '杰倫', '阿姨','老媽', '老爸']

複製陣列(淺複製)

用展開符號來複製 A 陣列,則會是 call-by-value 的複製,修改 B 不會影響到 A。

如果直接 let groupB = groupA 會是 call-by-reference

let groupA = ['小明', '杰倫', '阿姨'];
let groupB = [...groupA] // 新陣列
groupB.push('阿明');
console.log(groupA); // 不會有阿明

類陣列

querySelectorAll() 取出來的 NodeList 會以類陣列的形式呈現所有被選取的節點,但它卻不是真正的陣列。透過展開符號可以複製類陣列中的內容,藉此將類陣列轉為真正的陣列。

let doms = document.querySelectorAll('li'); // doms 為類陣列
let newDoms = [...doms]; // newDoms 為陣列

函式內建的 arguments 物件也是類陣列,arguments 會包含所有傳入函式的參數。

其餘參數

如果不確定要傳入函式的參數數量,可以在函式的最後一個參數前面加上展開符號(該參數就會變成其餘參數),就能把多傳入的參數值以陣列的形式,容納在其餘參數中。注意,函式裡只能有一個其餘參數,而且一定要放在最右邊。

function moreMoney(name, ...money) {
  console.log(name, money);
  // "Grace" [100, 200, 300, 400, 500]
}
moreMoney('Grace', 100, 200, 300, 400, 500);

ES Module 與 import 、 export

可將每個 JS 檔案當作一個獨立模組,A.js 匯出 export,在 B.js 匯入 import

// A.js
export const aString = "A String";

export function aFunction() {
    console.log("aFunction");
}

export const aObject = {a:1};


// B.js
import {aString, aFunction, aObject} from './A.js';

console.log(aString);
console.log(aObject);
aFunction();


// test.html
<!DOCTYPE html>
<html lang="en">

<head>
</head>

<body>
  <script type="module" src="./A.js"></script>
  <script type="module" src="./B.js"></script>

</body>
</html>

String Template

以往要在

  • 裡面加上圖片

    // HTML
    <ul class="list">
      <li>
        <img src="logo.png">
      </li>
    </ul>
    // JS
    // 用 const 選取 DOM
    const list = document.querySelector('.list')
    // 用 const 儲存要放入的圖片路徑
    const imgURL = 'logo.png';
    
    // 舊的寫法,要用字串連接
    list.innerHTML = '<li><img src='+ imgURL +'></li>'
    
    // 新的寫法,可用 `` 裡面放入 ${變數}
    list.innetHTML = `<li><img src=" ${imgURL} "></li>`

    可搭配陣列輸出

    • // 陣列
      const people = [
        {
          name: '小明',
          friends: 2
        },
        {
          name: '阿姨',
          friends: 999
        },
        {
          name: '杰倫',
          friends: 0
        }
      ]
      
      let newUl = `
        <ul> ${people.map(person =>
          `<li>我叫做 ${person.name}</li>`)
          .join('')}
        </ul>`

      Destructing Assignment

      將陣列或物件的資料直接拆開對應賦值

      const user = {
          id: 42,
          name: 'juice',
          fullname: {
              first: 'orange',
              last: 'juice'
          }
      };
      
      
      const {id, name, fullname} =  user;
      
      console.log(id);
      console.log(name);
      console.log(fullname);
      const num = [1,2,3,4,5];
      
      const [x,y] = num;
      
      //1
      console.log(x);
      //2
      console.log(y);

      常用陣列function

      forEach()

      ​ 對每一個元素運算一次 fuction

      map()

      ​ 運算後,回傳新的陣列

      reduce()

      find()

      ​ 只回傳第一個符合條件的元素

      filter()

      ​ 回傳符合條件的元素的新陣列

      every()

      ​ 所有元素都符合條件才回傳 true

      some()

      ​ 有部分元素符合條件就回傳 true

      Promise

      為解決非同步的問題,提供 Promise 物件

      成功時要呼叫 resolve,失敗時呼叫 reject。

      用 then 綁定 fullfilled 或 rejected 時,分別要執行的 function

      // 宣告 promise 建構式
      let newPromise = new Promise((resolve, reject) => {
        // 傳入 resolve 與 reject,表示資料成功與失敗
        let ran = parseInt(Math.random() * 2); // 隨機成功或失敗
        console.log('Promise 開始')
        if (ran) {
          setTimeout(function(){
            // 3 秒時間後,透過 resolve 來表示完成
            resolve('3 秒時間(fulfilled)');
          }, 3000);
        } else {
          // 回傳失敗
          reject('失敗中的失敗(rejected)')
        }
      
      });
      
      newPromise.then((data)=> {
        // 成功訊息 (需要 3 秒)
        console.log(data);
      }).catch((err)=> {
        // 失敗訊息 (立即)
        console.log(err)
      });

      async, await

      let runPromise = (someone, timer, success = true) => {
        console.log(`${someone} 開跑`);
        return new Promise((resolve, reject) => {
          // 傳入 resolve 與 reject,表示資料成功與失敗
          if (success) {
            setTimeout(function () {
              // 3 秒時間後,透過 resolve 來表示完成
              resolve(`${someone} 跑 ${timer / 1000} 秒時間(fulfilled)`);
            }, timer);
          } else {
            // 回傳失敗
            reject(`${someone} 跌倒失敗(rejected)`)
          }
        });
      }
      
      // 此段函式並不會影響其它函示的執行
      runPromise('小明', 3000).then(someone => {
        console.log('小明', someone)
      });
      // 以下這段 console 會在 promise 結束前就執行
      console.log('這裡執行了一段 console');

      await 會等待 promise 的處理結果

      // 此段函示會中斷其它函式的運行
      let mingRun = await runPromise('小明', 2000)
      console.log('跑完了:', mingRun);
      let auntieRun = await runPromise('漂亮阿姨', 2500);
      console.log('跑完了:', auntieRun);

      async 類似 promise,但會將 await 包裝在裡面

      const asyncRun = async () => {
        let mingRun = await runPromise('小明', 2000);
        let auntieRun = await runPromise('漂亮阿姨', 2500);
        return `${mingRun}, ${auntieRun}`
      }
      asyncRun().then(string => {
        console.log(string)
      }).catch(response => {
        console.log(string)
      })

      References

      [JS] JavaScript ES6 常用語法筆記

      JavaScript Await 與 Async

2022/02/14

Mosquitto MQTT in CentOS 7

MQTT: Message Queuing Telemetry Transport,是 M2M (machine-to-machine) 訊息傳遞 protocol,設計用在 IoT (Internet of Things) 的設備之間,互相傳遞訊息。client 之間,不因訊息傳遞需求,而要互相以 mesh 方式連接在一起,而是透過 MQTT Broker (在 MQTT 稱為 broker 不是 server) 代理互傳訊息,發送與接收訊息時,需要指定 topic,也就是透過 topic 進行訊息分類,讓 client 自行決定要處理哪些 topic 的訊息。

Mosquitto 是開放常用的 MQTT broker,以下了解如何在 CentOS7 安裝與測試 Mosquitto。

安裝

yum -y install epel-release
yum -y install mosquitto

#啟動
systemctl enable mosquitto
systemctl start mosquitto

安裝後可直接啟動並測試使用

用一個 terminal 執行

mosquitto_sub -h localhost -t test

另一個 terminal 執行

mosquitto_pub -h localhost -t test -m "hello world"

第一個 terminal 就能收到 "hello world"

Topic

MQ Telemetry Transport

Topic 是以階層式的概念定義,每個階層用 / 左斜線區隔。

ex: sensors/COMPUTER_NAME/temperature/HARDDRIVE_NAME

訊息的 subscriber 與 publisher 都需要指定 topic,subscriber 可在 topic 使用兩個 wildcards 符號

  • +

    代表某一個階層的全部

    ex: sensors/+/temperature/+ 就表示要取得所有 COMPUTER_NAME 以及 HARDDRIVE_NAME 的溫度

    ex: "a/b/c/d" 可用以下 pattern

    a/b/c/d
    +/b/c/d
    a/+/c/d
    a/+/+/d
    +/+/+/+
  • #

    代表後面所有的階層

    ex: "a/b/c/d" 可用以下 pattern

    a/b/c/d
    #
    a/#
    a/b/#
    +/b/c/#

QoS

[ Protocol ] 認識 MQTT

  • 0

    • at most once 最多只會傳送一次
    • 訊息送出後,就不管
    • 發送速度快
    • 有可能會遺失訊息
  • 1

    • at least once 至少傳一次
    • 當 broker 收到 publisher 訊息後,會回應 PUBACK,確認有收到要發布的訊息
    • 如果 publisher 沒有收到 PUBACK,會自動重傳
    • subscriber 有可能會收到重覆的訊息
  • 2

    • exactly once 只會傳送一次
    • broker 收到 publisher 訊息後,會回應 PUREC,確認有收到要發布的訊息
    • publisher 收到 PUREC 後,會再傳送 PUBREL 給 broker,告訴 broker 可以將訊息發布出去
    • broker 會把訊息傳給有訂閱該 topic 的 subscribers,傳送完成後,會回應 PUBCOMP 給 publisher,通知訊息已經發布完成
    • subscriber 不會收到重覆的訊息

密碼

mosquitto_passwd -c /etc/mosquitto/passwd test

修改 /etc/mosquitto/mosquitto.conf

allow_anonymous false
password_file /etc/mosquitto/passwd

直接發布會被拒絕

mosquitto_pub -h localhost -t "test" -m "hello world"
Connection error: Connection Refused: not authorised.

要加上帳號/密碼

mosquitto_pub -h localhost -t "test" -m "hello world" -u test -P password

TLS

產生 self signed SSL key 的步驟

  1. Create a CA key pair

  2. Create CA certificate and sign it with the private key from step 1

  3. Create the broker key pair

  4. Create a CA certificate sign request using the key from step 3

  5. Use the CA certificate from step 2 to sign the request from step 4

  6. 產生 CA key pair

    m2mqtt_ca.key

openssl genrsa -des3 -out m2mqtt_ca.key 2048

# 下面會詢問 pass phrase,是用來保護 ca private key: m2mqtt_ca.key
Generating RSA private key, 2048 bit long modulus
..+++
.........+++
e is 65537 (0x10001)
Enter pass phrase for m2mqtt_ca.key:
Verifying - Enter pass phrase for m2mqtt_ca.key:
  1. 產生 CA 的憑證

    m2mqtt_ca.crt

openssl req -new -x509 -days 3650 -key m2mqtt_ca.key -out m2mqtt_ca.crt

##
Enter pass phrase for m2mqtt_ca.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:TW
State or Province Name (full name) []:Taiwan
Locality Name (eg, city) [Default City]:Taichung
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:
Email Address []:
  1. 產生 mosquitto broker key pair

    m2mqtt_srv.key

openssl genrsa -out m2mqtt_srv.key 2048

####
Generating RSA private key, 2048 bit long modulus
..............................+++
.............+++
e is 65537 (0x10001)
  1. 產生 ceritificate request from CA

    m2mqtt_srv.csr

openssl req -new -out m2mqtt_srv.csr -key m2mqtt_srv.key

###
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:TW
State or Province Name (full name) []:Taiwan
Locality Name (eg, city) [Default City]:Taichung
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
  1. 驗證,並對 certificate request 簽章

    m2mqtt_srv.crt

openssl x509 -req -in m2mqtt_srv.csr -CA m2mqtt_ca.crt -CAkey m2mqtt_ca.key -CAcreateserial -out m2mqtt_srv.crt -days 3650

得到的檔案

m2mqtt_ca.crt  : CA Certificate
m2mqtt_ca.key  : CA key pair (private, public)
m2mqtt_ca.srl  : CA serial number file
m2mqtt_srv.crt : server certificate
m2mqtt_srv.csr : certificate sign request, not needed any more
m2mqtt_srv.key : server key pair

mosquito 需要用到

m2mqtt_ca.crt  : CA Certificate
m2mqtt_srv.crt : server certificate
m2mqtt_srv.key : server key pair

複製檔案

mkdir /etc/mosquitto/certs
cp m2mqtt_ca.crt /etc/mosquitto/certs/
cp m2mqtt_srv.crt /etc/mosquitto/certs/
cp m2mqtt_srv.key /etc/mosquitto/certs/

修改 mosquitto.conf

# 修改 default listener
port 1883

# wss
listener 9001
protocol websockets
cafile /etc/mosquitto/certs/m2mqtt_ca.crt
certfile /etc/mosquitto/certs/m2mqtt_srv.crt
keyfile /etc/mosquitto/certs/m2mqtt_srv.key

如果啟動 mosquitto 在 /var/log/messages 看到這樣的錯誤,表示 openssl 必須要更新

relocation error: /lib64/libwebsockets.so.13: symbol SSL_CTX_set_alpn_select_cb, version libssl.so.10 not defined in file libssl.so.10 with link time reference
yum install openssl

重新啟動 mosquitto

systemctl restart mosquitto.service

從 netstat 發現 9001 只有對 ipv6 開啟,目前不曉得怎麼對 ipv4 也開啟

# netstat -anlp|grep 1883
tcp        0      0 0.0.0.0:1883            0.0.0.0:*               LISTEN      31430/mosquitto
tcp6       0      0 :::1883                 :::*                    LISTEN      31430/mosquitto

# netstat -anlp|grep 9001
tcp6       0      0 :::9001                 :::*                    LISTEN      31430/mosquitto

References

樹莓派安裝 Mosquitto 輕量級 MQTT Broker 教學,連接各種物聯網設備

How To Install and Secure the Mosquitto MQTT Messaging Broker on CentOS 7

如何在CentOS 7上安装和保护Mosquitto MQTT消息传递代理

CentOS7.0 mosquitto的安裝和配置

Enable Secure Communication with TLS and the Mosquitto Broker

mosquitto-tls — Configure SSL/TLS support for Mosquitto

2022/02/07

JSON Web Token

JSON Web Token (JWT) 提供一種方法,可在 client 跟 server 之間使用,client 儲存的 JWT,可傳送到 server 進行身份驗證。因為 JWT 是以 base64 編碼原始資料,故 JWT 裡面儲存的資料,並不適合存放真實的使用者相關資料,而是要存放一份可讓 client 存取 server 資源的 token,JWT 透過內建的憑證簽章機制,驗證 JWT 是否有被竄改。

JWT 內容

JWT 就是一個單純的字串,內容有三個部分,header, payload, signature,三個部分用 . 句號連接起來。

{"typ":"JWT","alg":"HS256"}

有兩個欄位

  • typ

    是用 MIME type 定義的 "JWT" 說明這是一個 Json Web Token

  • alg

    簽章演算法,在 JWT 列出來有這些演算法

    HS256, HS384, HS512

    PS256, PS384, PS512

    RS256, RS384, RS512

    ES256, ES256K, ES384, ES512

    EdDSA

以 base64 編碼後得到

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Payload

除了一些標準定義的欄位外,也可以放自訂的欄位

{"sub":"1234567890","name":"John Doe","iat":1516239022}
  • iss

    issuer

    核發此 JWT 的單位

  • sub

    subject

    此 JWT 使用的目標對象

  • aud

    audience

    誰會使用這個 JWT

  • exp

    expiration

    JWT 逾時時間,定義為 UNIX timestamp 數字

  • nbf

    Not Before

    JWT 啟用時間,定義為 UNIX timestamp 數字

  • jat

    Issued At

    核發此 JWT 的時間 timestamp

  • jti

    JWT ID

    unique identifier for the JWT

用 base64 編碼

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

Signature

把前兩個部分合併在一起

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

然後用 HS256 簽章

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  "your-secret" 
)

再把三個部分合併成 JWT

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SARsBE5x_ua2ye823r2zKpQNaew3Daq8riKz5A4h3o4

HS256 裡面的密碼,是存放在 server,故可讓 server 驗證此 JWT 是否正確,沒有被竄改。

傳送/存放 JWT

Cookie

可直接透過 Cookie 方式,傳給 client。但設定時,必須加上 HttpOnly,防止 Cookie 被 javascript 讀取,避免遇到 XSS 攻擊

Set-Cookie: jwt=xxx; HttpOnly; max-age=....

接下來每次 request 都會夾帶 jwt cookie 到 server 進行驗證

Sesssion

存放在 Server 的 Session Store 裡面。

JWT 的規格,就是為了把 access token 發送給 client 存放,並在後續的溝通中,使用 JWT 進行身份驗證。透過 JWT,也可以實現 stateless 的 Server。

References

JSON Web Token (JWT) draft-ietf-oauth-json-web-token-32

JSON Web Token - 在Web应用间安全地传递信息

以 JSON Web Token 替代傳統 Token

八幅漫画理解使用JSON Web Token设计单点登录系统